diff --git a/fems-api/src/middleware/checkContractStatus.middleware.js b/fems-api/src/middleware/checkContractStatus.middleware.js new file mode 100644 index 0000000..d5f384a --- /dev/null +++ b/fems-api/src/middleware/checkContractStatus.middleware.js @@ -0,0 +1,145 @@ +// src/middleware/checkContractStatus.middleware.js +const { Contract, Company } = require("../models"); +const { Op } = require("sequelize"); +const logger = require("../config/logger"); + +class ContractStatusChecker { + async checkSubscriptionStatus(companyId) { + try { + // 회사 정보 조회 + const company = await Company.findOne({ + where: { + id: companyId, + isActive: true, + }, + }); + + if (!company) { + return { + isValid: false, + reason: "COMPANY_NOT_FOUND", + message: "회사 정보를 찾을 수 없습니다.", + }; + } + + // 활성 계약 조회 + const activeContract = await Contract.findOne({ + where: { + companyId, + status: "active", + startDate: { [Op.lte]: new Date() }, + endDate: { [Op.gt]: new Date() }, + }, + order: [["endDate", "DESC"]], + }); + + if (!activeContract) { + return { + isValid: false, + reason: "NO_ACTIVE_CONTRACT", + companyName: company.name, + message: "유효한 계약이 없습니다.", + }; + } + + // 계약 만료 임박 확인 (30일 이내) + const daysUntilExpiry = Math.ceil( + (new Date(activeContract.endDate) - new Date()) / (1000 * 60 * 60 * 24) + ); + + return { + isValid: true, + companyName: company.name, + contractInfo: { + contractNumber: activeContract.contractNumber, + startDate: activeContract.startDate, + endDate: activeContract.endDate, + remainingDays: daysUntilExpiry, + status: activeContract.status, + warning: + daysUntilExpiry <= 30 + ? { + type: "EXPIRING_SOON", + message: `계약이 ${daysUntilExpiry}일 후 만료됩니다.`, + } + : null, + }, + }; + } catch (error) { + logger.error("Contract status check failed:", { + companyId, + error: error.message, + }); + + return { + isValid: false, + reason: "CHECK_FAILED", + message: "계약 상태 확인 중 오류가 발생했습니다.", + }; + } + } + + // 미들웨어로 사용할 경우 + async checkSubscription(req, res, next) { + try { + const status = await this.checkSubscriptionStatus(req.params.companyId); + + if (!status.isValid) { + return res.status(403).json({ + error: "Invalid subscription", + ...status, + }); + } + + // 상태 정보를 요청 객체에 저장 + req.subscriptionStatus = status; + next(); + } catch (error) { + next(error); + } + } + + // 계약 만료 예정인 회사들 조회 + async getExpiringContracts(warningDays = 30) { + try { + const expiringDate = new Date(); + expiringDate.setDate(expiringDate.getDate() + warningDays); + + const contracts = await Contract.findAll({ + where: { + status: "active", + endDate: { + [Op.between]: [new Date(), expiringDate], + }, + }, + include: [ + { + model: Company, + where: { isActive: true }, + attributes: ["id", "name", "email"], + }, + ], + order: [["endDate", "ASC"]], + }); + + return contracts.map((contract) => ({ + companyId: contract.Company.id, + companyName: contract.Company.name, + companyEmail: contract.Company.email, + contractNumber: contract.contractNumber, + endDate: contract.endDate, + remainingDays: Math.ceil( + (new Date(contract.endDate) - new Date()) / (1000 * 60 * 60 * 24) + ), + })); + } catch (error) { + logger.error("Failed to get expiring contracts:", error); + throw error; + } + } +} + +// 싱글톤 인스턴스 생성 +const contractStatusChecker = new ContractStatusChecker(); + +module.exports = contractStatusChecker; diff --git a/fems-realtime-api/src/app.js b/fems-realtime-api/src/app.js index 09bb0e2..55a745e 100644 --- a/fems-realtime-api/src/app.js +++ b/fems-realtime-api/src/app.js @@ -7,7 +7,7 @@ const MQTTService = require("./services/mqtt.service"); const SensorDataModel = require("./models/SensorData"); const createDataController = require("./controllers/data.controller"); const logger = require("./utils/logger"); -const config = require("./config"); +const config = require("./config/config"); async function bootstrap() { try { @@ -47,7 +47,7 @@ async function bootstrap() { app.use("/api/v1/data", createDataController(pool, redis)); // 서버 시작 - const port = config.port || 3000; + const port = config.port || 3004; app.listen(port, () => { logger.info(`Realtime backend server running on port ${port}`); });