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 = { + // 대시보드 + "/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 = { + 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 0000000..dbeb8fb Binary files /dev/null and b/fems-mqtt/data/mosquitto.db differ 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 +