From ef73aae1179597063c9a34d0d4cd8ff14b91831a Mon Sep 17 00:00:00 2001
From: bangdk <bangdk@snatbook.com>
Date: Mon, 25 Nov 2024 08:40:59 +0900
Subject: [PATCH] auto commit

---
 .gitignore                                    |  20 +-
 fems-app/src/config/permissions.ts            | 208 ++++++++++++++++++
 fems-mqtt/data/mosquitto.db                   | Bin 0 -> 47 bytes
 fems-mqtt/data/passwd                         |   2 +
 fems-mqtt/log/mosquitto.log                   | 103 +++++++++
 fems-realtime-api/src/config/config.js        |  45 ++++
 fems-realtime-api/src/config/database.js      |  55 +++++
 fems-realtime-api/src/config/logger.js        | 190 ++++++++++++++++
 fems-timescaledb/init-scripts/00-init-user.sh |  17 ++
 9 files changed, 634 insertions(+), 6 deletions(-)
 create mode 100644 fems-app/src/config/permissions.ts
 create mode 100644 fems-mqtt/data/mosquitto.db
 create mode 100644 fems-mqtt/data/passwd
 create mode 100644 fems-realtime-api/src/config/config.js
 create mode 100644 fems-realtime-api/src/config/database.js
 create mode 100644 fems-realtime-api/src/config/logger.js
 create mode 100755 fems-timescaledb/init-scripts/00-init-user.sh

diff --git a/.gitignore b/.gitignore
index 64c1c5d..e3c5b36 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,13 +1,21 @@
+# Env files
 .env*
 !.env.example
-backups/
-logs/
+
+# Dependencies
 node_modules/
+
+# OS files
 .DS_Store
-config/
+
+# Data directories
 backups/
-init-scripts/
 logs/
 shared/
-fems-mqtt/log/mosquitto.log
-fems-mqtt/data/
+
+# Mosquitto specific
+fems-mqtt/config/
+!fems-mqtt/log/
+!fems-mqtt/log/mosquitto.log
+!fems-mqtt/data/
+!fems-mqtt/data/mosquitto.db
\ No newline at end of file
diff --git a/fems-app/src/config/permissions.ts b/fems-app/src/config/permissions.ts
new file mode 100644
index 0000000..1932c22
--- /dev/null
+++ b/fems-app/src/config/permissions.ts
@@ -0,0 +1,208 @@
+// src/config/permissions.ts
+
+// 기본 권한 정의
+export const PERMISSIONS = {
+  // 회사 관련 권한
+  COMPANY_VIEW: "company:view",
+  COMPANY_EDIT: "company:edit",
+  COMPANY_DELETE: "company:delete",
+
+  // 지점/공장 관련 권한
+  BRANCH_VIEW: "branch:view",
+  BRANCH_CREATE: "branch:create",
+  BRANCH_EDIT: "branch:edit",
+  BRANCH_DELETE: "branch:delete",
+
+  // 사용자 관련 권한
+  USER_VIEW: "user:view",
+  USER_CREATE: "user:create",
+  USER_EDIT: "user:edit",
+  USER_DELETE: "user:delete",
+
+  // 부서 관련 권한
+  DEPARTMENT_VIEW: "department:view",
+  DEPARTMENT_CREATE: "department:create",
+  DEPARTMENT_EDIT: "department:edit",
+  DEPARTMENT_DELETE: "department:delete",
+
+  // 설비 관련 권한
+  EQUIPMENT_VIEW: "equipment:view",
+  EQUIPMENT_CREATE: "equipment:create",
+  EQUIPMENT_EDIT: "equipment:edit",
+  EQUIPMENT_DELETE: "equipment:delete",
+
+  // 에너지 모니터링 권한
+  ENERGY_VIEW: "energy:view",
+  ENERGY_ANALYZE: "energy:analyze",
+
+  // 알람 관련 권한
+  ALARM_VIEW: "alarm:view",
+  ALARM_CREATE: "alarm:create",
+  ALARM_EDIT: "alarm:edit",
+  ALARM_DELETE: "alarm:delete",
+
+  // 보고서 관련 권한
+  REPORT_VIEW: "report:view",
+  REPORT_CREATE: "report:create",
+  REPORT_EDIT: "report:edit",
+  REPORT_DELETE: "report:delete",
+
+  // 설정 관련 권한
+  SETTINGS_VIEW: "settings:view",
+  SETTINGS_EDIT: "settings:edit",
+} as const;
+
+// 경로별 필요 권한 정의
+export const PATH_PERMISSIONS: Record<string, string[]> = {
+  // 대시보드
+  "/dashboard": [PERMISSIONS.ENERGY_VIEW],
+  "/dashboard/overview": [PERMISSIONS.ENERGY_VIEW],
+  "/dashboard/kpi": [PERMISSIONS.ENERGY_VIEW],
+  "/dashboard/costs": [PERMISSIONS.ENERGY_VIEW],
+
+  // 에너지 모니터링
+  "/monitoring/electricity": [PERMISSIONS.ENERGY_VIEW],
+  "/monitoring/gas": [PERMISSIONS.ENERGY_VIEW],
+  "/monitoring/water": [PERMISSIONS.ENERGY_VIEW],
+  "/monitoring/steam": [PERMISSIONS.ENERGY_VIEW],
+
+  // 설비 관리
+  "/equipment": [PERMISSIONS.EQUIPMENT_VIEW],
+  "/equipment/inventory": [PERMISSIONS.EQUIPMENT_VIEW],
+  "/equipment/monitoring": [PERMISSIONS.EQUIPMENT_VIEW],
+  "/equipment/maintenance": [PERMISSIONS.EQUIPMENT_VIEW],
+  "/equipment/create": [PERMISSIONS.EQUIPMENT_CREATE],
+  "/equipment/edit": [PERMISSIONS.EQUIPMENT_EDIT],
+
+  // 분석/리포트
+  "/analysis": [PERMISSIONS.ENERGY_ANALYZE],
+  "/analysis/energy": [PERMISSIONS.ENERGY_ANALYZE],
+  "/analysis/efficiency": [PERMISSIONS.ENERGY_ANALYZE],
+  "/analysis/reports": [PERMISSIONS.REPORT_VIEW],
+  "/analysis/reports/create": [PERMISSIONS.REPORT_CREATE],
+  "/analysis/reports/edit": [PERMISSIONS.REPORT_EDIT],
+
+  // 알람/이벤트
+  "/alarm": [PERMISSIONS.ALARM_VIEW],
+  "/alarm/realtime": [PERMISSIONS.ALARM_VIEW],
+  "/alarm/history": [PERMISSIONS.ALARM_VIEW],
+  "/alarm/settings": [PERMISSIONS.ALARM_EDIT],
+
+  // 관리자 설정
+  "/admin/company": [PERMISSIONS.COMPANY_VIEW],
+  "/admin/company/profile": [PERMISSIONS.COMPANY_EDIT],
+  "/admin/company/branches": [PERMISSIONS.BRANCH_VIEW],
+  "/admin/company/billing": [PERMISSIONS.COMPANY_VIEW],
+
+  // 사용자 관리
+  "/admin/user": [PERMISSIONS.USER_VIEW],
+  "/admin/user/departments": [PERMISSIONS.DEPARTMENT_VIEW],
+  "/admin/user/roles": [PERMISSIONS.USER_VIEW],
+  "/admin/user/accounts": [PERMISSIONS.USER_VIEW],
+  "/admin/user/create": [PERMISSIONS.USER_CREATE],
+  "/admin/user/edit": [PERMISSIONS.USER_EDIT],
+
+  // 시스템 설정
+  "/admin/system": [PERMISSIONS.SETTINGS_VIEW],
+  "/admin/system/general": [PERMISSIONS.SETTINGS_EDIT],
+  "/admin/system/integration": [PERMISSIONS.SETTINGS_EDIT],
+  "/admin/system/backup": [PERMISSIONS.SETTINGS_EDIT],
+};
+
+// 역할별 기본 권한 정의
+export const ROLE_PERMISSIONS: Record<string, string[]> = {
+  super_admin: Object.values(PERMISSIONS),
+
+  company_admin: [
+    PERMISSIONS.COMPANY_VIEW,
+    PERMISSIONS.COMPANY_EDIT,
+    PERMISSIONS.BRANCH_VIEW,
+    PERMISSIONS.BRANCH_CREATE,
+    PERMISSIONS.BRANCH_EDIT,
+    PERMISSIONS.USER_VIEW,
+    PERMISSIONS.USER_CREATE,
+    PERMISSIONS.USER_EDIT,
+    PERMISSIONS.DEPARTMENT_VIEW,
+    PERMISSIONS.DEPARTMENT_CREATE,
+    PERMISSIONS.DEPARTMENT_EDIT,
+    PERMISSIONS.EQUIPMENT_VIEW,
+    PERMISSIONS.EQUIPMENT_CREATE,
+    PERMISSIONS.EQUIPMENT_EDIT,
+    PERMISSIONS.ENERGY_VIEW,
+    PERMISSIONS.ENERGY_ANALYZE,
+    PERMISSIONS.ALARM_VIEW,
+    PERMISSIONS.ALARM_CREATE,
+    PERMISSIONS.ALARM_EDIT,
+    PERMISSIONS.REPORT_VIEW,
+    PERMISSIONS.REPORT_CREATE,
+    PERMISSIONS.SETTINGS_VIEW,
+    PERMISSIONS.SETTINGS_EDIT,
+  ],
+
+  branch_admin: [
+    PERMISSIONS.BRANCH_VIEW,
+    PERMISSIONS.USER_VIEW,
+    PERMISSIONS.USER_CREATE,
+    PERMISSIONS.EQUIPMENT_VIEW,
+    PERMISSIONS.EQUIPMENT_CREATE,
+    PERMISSIONS.ENERGY_VIEW,
+    PERMISSIONS.ENERGY_ANALYZE,
+    PERMISSIONS.ALARM_VIEW,
+    PERMISSIONS.ALARM_CREATE,
+    PERMISSIONS.REPORT_VIEW,
+    PERMISSIONS.REPORT_CREATE,
+  ],
+
+  user: [
+    PERMISSIONS.ENERGY_VIEW,
+    PERMISSIONS.EQUIPMENT_VIEW,
+    PERMISSIONS.ALARM_VIEW,
+    PERMISSIONS.REPORT_VIEW,
+  ],
+};
+
+// 권한 그룹 정의
+export const PERMISSION_GROUPS = {
+  COMPANY_MANAGEMENT: [
+    PERMISSIONS.COMPANY_VIEW,
+    PERMISSIONS.COMPANY_EDIT,
+    PERMISSIONS.COMPANY_DELETE,
+  ],
+
+  BRANCH_MANAGEMENT: [
+    PERMISSIONS.BRANCH_VIEW,
+    PERMISSIONS.BRANCH_CREATE,
+    PERMISSIONS.BRANCH_EDIT,
+    PERMISSIONS.BRANCH_DELETE,
+  ],
+
+  USER_MANAGEMENT: [
+    PERMISSIONS.USER_VIEW,
+    PERMISSIONS.USER_CREATE,
+    PERMISSIONS.USER_EDIT,
+    PERMISSIONS.USER_DELETE,
+  ],
+
+  EQUIPMENT_MANAGEMENT: [
+    PERMISSIONS.EQUIPMENT_VIEW,
+    PERMISSIONS.EQUIPMENT_CREATE,
+    PERMISSIONS.EQUIPMENT_EDIT,
+    PERMISSIONS.EQUIPMENT_DELETE,
+  ],
+
+  ENERGY_MONITORING: [PERMISSIONS.ENERGY_VIEW, PERMISSIONS.ENERGY_ANALYZE],
+
+  ALARM_MANAGEMENT: [
+    PERMISSIONS.ALARM_VIEW,
+    PERMISSIONS.ALARM_CREATE,
+    PERMISSIONS.ALARM_EDIT,
+    PERMISSIONS.ALARM_DELETE,
+  ],
+
+  REPORT_MANAGEMENT: [
+    PERMISSIONS.REPORT_VIEW,
+    PERMISSIONS.REPORT_CREATE,
+    PERMISSIONS.REPORT_EDIT,
+    PERMISSIONS.REPORT_DELETE,
+  ],
+} as const;
diff --git a/fems-mqtt/data/mosquitto.db b/fems-mqtt/data/mosquitto.db
new file mode 100644
index 0000000000000000000000000000000000000000..dbeb8fb8c185ec1d31c94a03b261f8a1a6a52391
GIT binary patch
literal 47
mcmZSB%8;91Tv(b}Qj)KblEeT3Y(R<;hz0iXKm-^$AT$8$#Rhu-

literal 0
HcmV?d00001

diff --git a/fems-mqtt/data/passwd b/fems-mqtt/data/passwd
new file mode 100644
index 0000000..b269bfe
--- /dev/null
+++ b/fems-mqtt/data/passwd
@@ -0,0 +1,2 @@
+fems:$7$101$p+0HKWVdeWzlO1o4$+GB5GrYz2g6TO75UHWzMDZH+eC8w/ZA4u3qk/0tMBoeSVnqTaInIdANM7trDQsEnT+G2wOr9G6ND+5YOySqxYQ==
+nodered_user:$7$101$bnRKi1qgGP06mJIZ$ksMvwVPG9Dp7+gWHKZnEXkgTV1W+Md9HHuOKSh4yFylwyZ2yEsRYOb/7oQ58Xxeu1YZNNrICiRCjnb9iDz0dpQ==
diff --git a/fems-mqtt/log/mosquitto.log b/fems-mqtt/log/mosquitto.log
index 4e4b410..d91b8c0 100755
--- a/fems-mqtt/log/mosquitto.log
+++ b/fems-mqtt/log/mosquitto.log
@@ -16152,3 +16152,106 @@ To fix this, use `chmod 0700 /mosquitto/config/passwd`.
 1732490536: New connection from ::1:43836 on port 1883.
 1732490536: New client connected from ::1:43836 as auto-7B9071EC-9348-5AC2-2FE5-3F75319EAB71 (p2, c1, k60, u'fems').
 1732490536: Client auto-7B9071EC-9348-5AC2-2FE5-3F75319EAB71 closed its connection.
+1732490687: mosquitto version 2.0.20 starting
+1732490687: Config loaded from /mosquitto/config/mosquitto.conf.
+1732490687: Opening ipv4 listen socket on port 1883.
+1732490687: Opening ipv6 listen socket on port 1883.
+1732490687: mosquitto version 2.0.20 running
+1732490691: New connection from 172.19.0.9:33118 on port 1883.
+1732490691: New client connected from 172.19.0.9:33118 as fems_realtime_40 (p2, c1, k60, u'fems').
+1732490718: New connection from ::1:54086 on port 1883.
+1732490718: New client connected from ::1:54086 as auto-2D4F3C8B-9D04-17FC-D813-CB77A364BAE6 (p2, c1, k60, u'fems').
+1732490718: Client auto-2D4F3C8B-9D04-17FC-D813-CB77A364BAE6 closed its connection.
+1732490748: New connection from ::1:55062 on port 1883.
+1732490748: New client connected from ::1:55062 as auto-92F8F9BC-0250-47AB-F726-9AC0F1894772 (p2, c1, k60, u'fems').
+1732490748: Client auto-92F8F9BC-0250-47AB-F726-9AC0F1894772 closed its connection.
+1732490778: New connection from ::1:51410 on port 1883.
+1732490778: New client connected from ::1:51410 as auto-73A6A1E9-3235-19AB-1C6A-78810845C0EF (p2, c1, k60, u'fems').
+1732490778: Client auto-73A6A1E9-3235-19AB-1C6A-78810845C0EF closed its connection.
+1732490808: New connection from ::1:60058 on port 1883.
+1732490808: New client connected from ::1:60058 as auto-0D81837A-5F32-0F2A-5A84-F1CD500A34BB (p2, c1, k60, u'fems').
+1732490808: Client auto-0D81837A-5F32-0F2A-5A84-F1CD500A34BB closed its connection.
+1732490838: New connection from ::1:40688 on port 1883.
+1732490838: New client connected from ::1:40688 as auto-C92A19DE-7AFF-91B8-D9B7-E783E566B0AE (p2, c1, k60, u'fems').
+1732490838: Client auto-C92A19DE-7AFF-91B8-D9B7-E783E566B0AE closed its connection.
+1732490868: New connection from ::1:51326 on port 1883.
+1732490868: New client connected from ::1:51326 as auto-77597FD6-78CB-5036-3503-424EC4E59336 (p2, c1, k60, u'fems').
+1732490868: Client auto-77597FD6-78CB-5036-3503-424EC4E59336 closed its connection.
+1732490898: New connection from ::1:36716 on port 1883.
+1732490898: New client connected from ::1:36716 as auto-71130DEF-B28B-83D2-7860-DB3A1ED0BBF5 (p2, c1, k60, u'fems').
+1732490898: Client auto-71130DEF-B28B-83D2-7860-DB3A1ED0BBF5 closed its connection.
+1732490928: New connection from ::1:46640 on port 1883.
+1732490928: New client connected from ::1:46640 as auto-ABA29341-7728-9CA0-CC93-0B941290691D (p2, c1, k60, u'fems').
+1732490928: Client auto-ABA29341-7728-9CA0-CC93-0B941290691D closed its connection.
+1732490958: New connection from ::1:42752 on port 1883.
+1732490958: New client connected from ::1:42752 as auto-243B1078-D0D2-ED05-A6C8-F0BF776AEB6B (p2, c1, k60, u'fems').
+1732490959: Client auto-243B1078-D0D2-ED05-A6C8-F0BF776AEB6B closed its connection.
+1732490989: New connection from ::1:41906 on port 1883.
+1732490989: New client connected from ::1:41906 as auto-9C33A78D-8294-B6C1-58C3-FF516DD1E0B8 (p2, c1, k60, u'fems').
+1732490989: Client auto-9C33A78D-8294-B6C1-58C3-FF516DD1E0B8 closed its connection.
+1732491019: New connection from ::1:41178 on port 1883.
+1732491019: New client connected from ::1:41178 as auto-A139E080-C259-3D39-0329-7E7A1573E190 (p2, c1, k60, u'fems').
+1732491019: Client auto-A139E080-C259-3D39-0329-7E7A1573E190 closed its connection.
+1732491049: New connection from ::1:38994 on port 1883.
+1732491049: New client connected from ::1:38994 as auto-AF07BC5F-6663-3268-0FA1-3DE70038B9B8 (p2, c1, k60, u'fems').
+1732491049: Client auto-AF07BC5F-6663-3268-0FA1-3DE70038B9B8 closed its connection.
+1732491079: New connection from ::1:48114 on port 1883.
+1732491079: New client connected from ::1:48114 as auto-7D97DF2B-7E14-B971-9639-EFA16634D860 (p2, c1, k60, u'fems').
+1732491079: Client auto-7D97DF2B-7E14-B971-9639-EFA16634D860 closed its connection.
+1732491109: New connection from ::1:58464 on port 1883.
+1732491109: New client connected from ::1:58464 as auto-8ADAE7A2-8E93-8DA1-758C-A3F3AE0D4764 (p2, c1, k60, u'fems').
+1732491109: Client auto-8ADAE7A2-8E93-8DA1-758C-A3F3AE0D4764 closed its connection.
+1732491139: New connection from ::1:36580 on port 1883.
+1732491139: New client connected from ::1:36580 as auto-D058BA2D-FDB5-448B-BF4F-3A246A151455 (p2, c1, k60, u'fems').
+1732491139: Client auto-D058BA2D-FDB5-448B-BF4F-3A246A151455 closed its connection.
+1732491169: New connection from ::1:36214 on port 1883.
+1732491169: New client connected from ::1:36214 as auto-7E3A3113-C7D5-E795-8321-61B07DF68BBD (p2, c1, k60, u'fems').
+1732491169: Client auto-7E3A3113-C7D5-E795-8321-61B07DF68BBD closed its connection.
+1732491199: New connection from ::1:50750 on port 1883.
+1732491199: New client connected from ::1:50750 as auto-154D1239-5A81-35AB-1A45-140DC068F894 (p2, c1, k60, u'fems').
+1732491199: Client auto-154D1239-5A81-35AB-1A45-140DC068F894 closed its connection.
+1732491229: New connection from ::1:60768 on port 1883.
+1732491229: New client connected from ::1:60768 as auto-278B5CC2-EE66-DDDC-FDFD-0328223CD5F5 (p2, c1, k60, u'fems').
+1732491229: Client auto-278B5CC2-EE66-DDDC-FDFD-0328223CD5F5 closed its connection.
+1732491259: New connection from ::1:44754 on port 1883.
+1732491259: New client connected from ::1:44754 as auto-7E2B7353-2DA6-5780-6E11-4D95D3FA7C1D (p2, c1, k60, u'fems').
+1732491259: Client auto-7E2B7353-2DA6-5780-6E11-4D95D3FA7C1D closed its connection.
+1732491289: New connection from ::1:33370 on port 1883.
+1732491289: New client connected from ::1:33370 as auto-4E6B0FAD-1679-EC09-5B7D-7D78E9239449 (p2, c1, k60, u'fems').
+1732491289: Client auto-4E6B0FAD-1679-EC09-5B7D-7D78E9239449 closed its connection.
+1732491319: New connection from ::1:43742 on port 1883.
+1732491319: New client connected from ::1:43742 as auto-4962452D-D915-2E77-3F10-B4210A376198 (p2, c1, k60, u'fems').
+1732491319: Client auto-4962452D-D915-2E77-3F10-B4210A376198 closed its connection.
+1732491349: New connection from ::1:38400 on port 1883.
+1732491349: New client connected from ::1:38400 as auto-E64FFAD5-8EC3-AB42-32AC-9B47A0E869DA (p2, c1, k60, u'fems').
+1732491350: Client auto-E64FFAD5-8EC3-AB42-32AC-9B47A0E869DA closed its connection.
+1732491380: New connection from ::1:50554 on port 1883.
+1732491380: New client connected from ::1:50554 as auto-49A2BA59-20A9-86EC-738E-6C0747EACBC5 (p2, c1, k60, u'fems').
+1732491380: Client auto-49A2BA59-20A9-86EC-738E-6C0747EACBC5 closed its connection.
+1732491410: New connection from ::1:51472 on port 1883.
+1732491410: New client connected from ::1:51472 as auto-926B3BC4-A256-C884-31B3-B769F5D556A5 (p2, c1, k60, u'fems').
+1732491410: Client auto-926B3BC4-A256-C884-31B3-B769F5D556A5 closed its connection.
+1732491440: New connection from ::1:38310 on port 1883.
+1732491440: New client connected from ::1:38310 as auto-008FD4ED-5203-D152-D76E-2F5D9F8DD0A9 (p2, c1, k60, u'fems').
+1732491440: Client auto-008FD4ED-5203-D152-D76E-2F5D9F8DD0A9 closed its connection.
+1732491470: New connection from ::1:45430 on port 1883.
+1732491470: New client connected from ::1:45430 as auto-460F179A-BA65-9CAB-D343-D578EDC0E52A (p2, c1, k60, u'fems').
+1732491470: Client auto-460F179A-BA65-9CAB-D343-D578EDC0E52A closed its connection.
+1732491500: New connection from ::1:35036 on port 1883.
+1732491500: New client connected from ::1:35036 as auto-73657D0E-57B0-E4B4-203D-695B9272A1D2 (p2, c1, k60, u'fems').
+1732491500: Client auto-73657D0E-57B0-E4B4-203D-695B9272A1D2 closed its connection.
+1732491530: New connection from ::1:41792 on port 1883.
+1732491530: New client connected from ::1:41792 as auto-D9A66FCF-9FA2-681C-6219-94E21F9ACC56 (p2, c1, k60, u'fems').
+1732491530: Client auto-D9A66FCF-9FA2-681C-6219-94E21F9ACC56 closed its connection.
+1732491560: New connection from ::1:38248 on port 1883.
+1732491560: New client connected from ::1:38248 as auto-94A398BB-6697-A0FE-51D2-B03938E2A628 (p2, c1, k60, u'fems').
+1732491560: Client auto-94A398BB-6697-A0FE-51D2-B03938E2A628 closed its connection.
+1732491590: New connection from ::1:55668 on port 1883.
+1732491590: New client connected from ::1:55668 as auto-A7E0C188-D363-E61F-6705-41E2F1CF00FC (p2, c1, k60, u'fems').
+1732491590: Client auto-A7E0C188-D363-E61F-6705-41E2F1CF00FC closed its connection.
+1732491620: New connection from ::1:41604 on port 1883.
+1732491620: New client connected from ::1:41604 as auto-EE19F34E-4E04-1D13-CE67-A1FFBA9612D4 (p2, c1, k60, u'fems').
+1732491620: Client auto-EE19F34E-4E04-1D13-CE67-A1FFBA9612D4 closed its connection.
+1732491650: New connection from ::1:55334 on port 1883.
+1732491650: New client connected from ::1:55334 as auto-698C8684-502F-6421-8FBE-C6C6DFC9CBB0 (p2, c1, k60, u'fems').
+1732491650: Client auto-698C8684-502F-6421-8FBE-C6C6DFC9CBB0 closed its connection.
diff --git a/fems-realtime-api/src/config/config.js b/fems-realtime-api/src/config/config.js
new file mode 100644
index 0000000..f664796
--- /dev/null
+++ b/fems-realtime-api/src/config/config.js
@@ -0,0 +1,45 @@
+// src/config/config.js
+require("dotenv").config({
+  path: `.env.${process.env.NODE_ENV || "development"}`,
+});
+
+const config = {
+  port: process.env.REALTIME_API_PORT || 3004,
+
+  timescaledb: {
+    host: process.env.TIMESCALEDB_HOST || "localhost",
+    port: parseInt(process.env.TIMESCALEDB_PORT) || 5433,
+    database: process.env.TIMESCALEDB_DB,
+    user: process.env.TIMESCALEDB_USER,
+    password: process.env.TIMESCALEDB_PASSWORD,
+  },
+
+  redis: {
+    host: process.env.REDIS_HOST || "localhost",
+    port: parseInt(process.env.REDIS_PORT) || 6379,
+    password: process.env.REDIS_PASSWORD,
+    db: parseInt(process.env.REDIS_DB) || 0,
+    retryStrategy: (times) => {
+      return Math.min(times * 50, 2000);
+    },
+  },
+
+  mqtt: {
+    url: process.env.MQTT_BROKER_URL || "mqtt://localhost:1883",
+    options: {
+      username: process.env.MQTT_USERNAME,
+      password: process.env.MQTT_PASSWORD,
+      clientId: `fems_realtime_${process.pid}`,
+      clean: true,
+      reconnectPeriod: 5000,
+      connectTimeout: 30000,
+    },
+  },
+
+  mainBackend: {
+    url: process.env.NEXT_PUBLIC_API_URL || "http://localhost:3000",
+    adminApiKey: process.env.ADMIN_API_KEY,
+  },
+};
+
+module.exports = config;
diff --git a/fems-realtime-api/src/config/database.js b/fems-realtime-api/src/config/database.js
new file mode 100644
index 0000000..60c7fab
--- /dev/null
+++ b/fems-realtime-api/src/config/database.js
@@ -0,0 +1,55 @@
+// src/config/database.js
+const logger = require("./logger");
+
+module.exports = {
+  development: {
+    host: process.env.POSTGRES_HOST || "fems-timescaledb",
+    port: parseInt(process.env.DB_PORT) || 5442,
+    database: process.env.POSTGRES_DB || "fems_timeseries",
+    username: process.env.POSTGRES_USER || "fems_user",
+    password: process.env.POSTGRES_PASSWORD,
+    dialect: "postgres",
+    logging: (msg) => logger.debug(msg),
+    dialectOptions: {
+      ssl: false,
+    },
+    define: {
+      timestamps: true,
+      underscored: true,
+    },
+    pool: {
+      max: 5,
+      min: 0,
+      acquire: 60000,
+      idle: 10000,
+    },
+    retry: {
+      max: 5,
+    },
+  },
+  production: {
+    host: process.env.POSTGRES_HOST,
+    port: parseInt(process.env.DB_PORT),
+    database: process.env.POSTGRES_DB,
+    username: process.env.POSTGRES_USER,
+    password: process.env.POSTGRES_PASSWORD,
+    dialect: "postgres",
+    logging: false,
+    dialectOptions: {
+      ssl: process.env.DB_SSL === "true",
+    },
+    define: {
+      timestamps: true,
+      underscored: true,
+    },
+    pool: {
+      max: 10,
+      min: 2,
+      acquire: 60000,
+      idle: 10000,
+    },
+    retry: {
+      max: 5,
+    },
+  },
+};
diff --git a/fems-realtime-api/src/config/logger.js b/fems-realtime-api/src/config/logger.js
new file mode 100644
index 0000000..d09d095
--- /dev/null
+++ b/fems-realtime-api/src/config/logger.js
@@ -0,0 +1,190 @@
+// src/config/logger.js
+const winston = require("winston");
+require("winston-daily-rotate-file");
+const path = require("path");
+const fs = require("fs");
+
+class Logger {
+  constructor() {
+    if (Logger.instance) {
+      return Logger.instance;
+    }
+
+    this.initializeDirs();
+    this.createLogger();
+    Logger.instance = this;
+    return this;
+  }
+
+  initializeDirs() {
+    // 로그 디렉토리 설정
+    this.logDir = path.join(__dirname, "../../logs");
+    this.errorLogDir = path.join(this.logDir, "error");
+    this.infoLogDir = path.join(this.logDir, "info");
+    this.systemLogDir = path.join(this.logDir, "system");
+
+    // 로그 디렉토리 생성
+    [this.errorLogDir, this.infoLogDir, this.systemLogDir].forEach((dir) => {
+      if (!fs.existsSync(dir)) {
+        fs.mkdirSync(dir, { recursive: true });
+      }
+    });
+  }
+
+  createLogger() {
+    // 공통 로그 포맷
+    const commonLogFormat = winston.format.combine(
+      winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
+      winston.format.errors({ stack: true }),
+      winston.format.splat(),
+      winston.format.json()
+    );
+
+    // 콘솔 출력용 포맷
+    const consoleFormat = winston.format.combine(
+      winston.format.colorize(),
+      winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
+      winston.format.printf(
+        ({ level, message, timestamp, stack, ...metadata }) => {
+          let msg = `${timestamp} [${level}]: ${message}`;
+          if (stack) msg += `\n${stack}`;
+          if (Object.keys(metadata).length > 0) {
+            msg += `\n${JSON.stringify(metadata, null, 2)}`;
+          }
+          return msg;
+        }
+      )
+    );
+
+    // 트랜스포트 설정
+    this.transports = {
+      error: new winston.transports.DailyRotateFile({
+        level: "error",
+        dirname: this.errorLogDir,
+        filename: "error-%DATE%.log",
+        datePattern: "YYYY-MM-DD",
+        zippedArchive: true,
+        maxSize: "20m",
+        maxFiles: "14d",
+        format: commonLogFormat,
+      }),
+      info: new winston.transports.DailyRotateFile({
+        level: "info",
+        dirname: this.infoLogDir,
+        filename: "info-%DATE%.log",
+        datePattern: "YYYY-MM-DD",
+        zippedArchive: true,
+        maxSize: "20m",
+        maxFiles: "14d",
+        format: commonLogFormat,
+      }),
+      system: new winston.transports.DailyRotateFile({
+        dirname: this.systemLogDir,
+        filename: "system-%DATE%.log",
+        datePattern: "YYYY-MM-DD",
+        zippedArchive: true,
+        maxSize: "20m",
+        maxFiles: "14d",
+        format: commonLogFormat,
+      }),
+    };
+
+    // Winston 로거 생성
+    this.logger = winston.createLogger({
+      level: process.env.NODE_ENV === "production" ? "info" : "debug",
+      defaultMeta: {
+        service: "fems-edge",
+        environment: process.env.NODE_ENV || "development",
+      },
+      transports: Object.values(this.transports),
+      // exitOnError: false를 추가하여 에러 발생 시 프로세스가 종료되지 않도록 함
+      exitOnError: false,
+    });
+
+    // 개발 환경에서만 콘솔 출력 추가
+    if (process.env.NODE_ENV !== "production") {
+      this.logger.add(
+        new winston.transports.Console({
+          level: "debug",
+          format: consoleFormat,
+        })
+      );
+    }
+
+    // 로그 회전 이벤트 핸들러
+    this.setupRotateHandlers();
+  }
+
+  setupRotateHandlers() {
+    this.transports.error.on("rotate", (oldFilename, newFilename) => {
+      this.logger.info("Error log rotated", { oldFilename, newFilename });
+    });
+
+    this.transports.info.on("rotate", (oldFilename, newFilename) => {
+      this.logger.info("Info log rotated", { oldFilename, newFilename });
+    });
+  }
+
+  // 유틸리티 메서드들
+  logHttpRequest(req) {
+    this.logger.info("HTTP Request", {
+      method: req.method,
+      url: req.originalUrl,
+      params: req.params,
+      query: req.query,
+      body: req.body,
+      ip: req.ip,
+      userAgent: req.get("user-agent"),
+    });
+  }
+
+  logMqttMessage(topic, payload) {
+    this.logger.debug("MQTT Message", {
+      topic,
+      payload,
+      timestamp: new Date().toISOString(),
+    });
+  }
+
+  logPerformance(operation, duration) {
+    this.logger.info("Performance Metric", {
+      operation,
+      duration,
+      timestamp: new Date().toISOString(),
+    });
+  }
+
+  logQuery(query, duration) {
+    this.logger.debug("Database Query", {
+      query: typeof query === "string" ? query.trim() : query,
+      duration: `${duration}ms`,
+      timestamp: new Date().toISOString(),
+    });
+  }
+
+  // Express 미들웨어용 스트림
+  get stream() {
+    return {
+      write: (message) => {
+        this.logger.info(message.trim());
+      },
+    };
+  }
+}
+
+// 싱글톤 인스턴스 생성
+const loggerInstance = new Logger();
+
+// 편의를 위한 메서드 바인딩
+module.exports = {
+  error: loggerInstance.logger.error.bind(loggerInstance.logger),
+  warn: loggerInstance.logger.warn.bind(loggerInstance.logger),
+  info: loggerInstance.logger.info.bind(loggerInstance.logger),
+  debug: loggerInstance.logger.debug.bind(loggerInstance.logger),
+  logHttpRequest: loggerInstance.logHttpRequest.bind(loggerInstance),
+  logMqttMessage: loggerInstance.logMqttMessage.bind(loggerInstance),
+  logPerformance: loggerInstance.logPerformance.bind(loggerInstance),
+  logQuery: loggerInstance.logQuery.bind(loggerInstance),
+  stream: loggerInstance.stream,
+  logger: loggerInstance.logger,
+};
diff --git a/fems-timescaledb/init-scripts/00-init-user.sh b/fems-timescaledb/init-scripts/00-init-user.sh
new file mode 100755
index 0000000..7722faa
--- /dev/null
+++ b/fems-timescaledb/init-scripts/00-init-user.sh
@@ -0,0 +1,17 @@
+# config/postgres/init-scripts/00-init-user.sh
+#!/bin/bash
+set -e
+
+psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
+    -- TimescaleDB 확장 활성화
+    CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;
+
+    -- 사용자 권한 설정
+    ALTER USER $POSTGRES_USER WITH PASSWORD '$POSTGRES_PASSWORD';
+    ALTER USER $POSTGRES_USER SET client_encoding TO 'utf8';
+    ALTER USER $POSTGRES_USER SET timezone TO 'UTC';
+
+    -- 데이터베이스 설정
+    ALTER DATABASE $POSTGRES_DB SET timezone TO 'UTC';
+EOSQL
+