diff --git a/app.js b/app.js index fc5ef29229b18f4847a0f6c5ecfdef24352d3565..5b244850efc525f6b2393b0844c6fc9b07a9694e 100644 --- a/app.js +++ b/app.js @@ -7,6 +7,7 @@ const express = require('express'); const session = require('express-session'); const passport = require('./passport'); // 蹂�寃쎈맂 寃쎈줈 const flash = require('connect-flash'); +const { initScheduleCleaner } = require('./utils/scheduler'); const app = express(); @@ -35,6 +36,13 @@ app.use(flash()); const authRoutes = require('./routes/auth'); app.use('/auth', authRoutes); +// Schedule �쇱슦�� +const scheduleRoutes = require('./routes/schedule'); +app.use('/api/schedule', scheduleRoutes); + + +initScheduleCleaner(); + const PORT = process.env.PORT || 3000; app.get('/', (req, res) => { diff --git a/controllers/scheduleController.js b/controllers/scheduleController.js index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..6cfc2d01e469619b4172f22964a99b656da5a82e 100644 --- a/controllers/scheduleController.js +++ b/controllers/scheduleController.js @@ -0,0 +1,169 @@ +const ScheduleService = require('../services/scheduleService'); + +class scheduleController { + /** + * �ㅼ�以� �앹꽦 + * POST /api/schedule + * �대떦 �ъ슜�� id�� auth 誘몃뱾�⑥뼱�먯꽌 �ㅼ젙�� �ъ슜�� �뺣낫 �댁슜 + * req.user = User 紐⑤뜽�� �몄뒪�댁뒪 + */ + async createSchedule(req, res) { + try { + const userId = req.user.id; + const { title, start_time, end_time, is_fixed } = req.body; + + const schedule = await ScheduleService.createSchedule({ + userId, + title, + start_time, + end_time, + is_fixed + }); + + return res.status(201).json({ + success: true, + data: { + schedule + } + }); + } catch (error) { + return res.status(400).json({ + success: false, + error: { + message: error.message, + code: 'SCHEDULE_CREATE_ERROR' + } + }); + } + } + + /** + * �ㅼ�以� �섏젙 + * PUT /api/schedule/:id + */ + async updateSchedule(req, res) { + try { + const { id } = req.params; + const { title, start_time, end_time } = req.body; + + const userId = req.user.id; + const schedule = await ScheduleService.updateSchedule(id, userId, + { + title, + start_time, + end_time + }); + + return res.status(200).json({ + success: true, + data: schedule + }); + } catch (error) { + if (error.message === 'Schedule not found') { + return res.status(404).json({ + success: false, + error: { + message: error.message, + code: 'SCHEDULE_NOT_FOUND' + } + }); + } + return res.status(400).json({ + success: false, + error: { + message: error.message, + code: 'SCHEDULE_UPDATE_ERROR' + } + }); + } + } + + /** + * �ㅼ�以� ��젣 + * DELETE /api/schedule/:id + */ + async deleteSchedule(req, res) { + try { + const { id } = req.params; + const userId = req.user.id; + + await ScheduleService.deleteSchedule(id, userId); + + return res.status(200).json({ + success: true, + data: { + message: 'Schedule successfully deleted' + } + }); + } catch (error) { + return res.status(404).json({ + success: false, + error: { + message: error.message, + code: 'SCHEDULE_NOT_FOUND' + } + }); + } + } + + /** + * �대떦 �ъ슜�� �꾩껜 �ㅼ�以� 議고쉶 + * GET /api/schedule/all + */ + async getAllSchedules(req, res) { + try { + const userId = req.user.id; + const schedules = await ScheduleService.getAllSchedules(userId); + + return res.status(200).json({ + success: true, + data: schedules + }); + } catch (error) { + return res.status(500).json({ + success: false, + error: { + message: 'Failed to fetch schedules', + code: 'FETCH_ERROR' + } + }); + } + } + + /** + * �대떦 �ъ슜�� �뱀젙 �ㅼ�以� 議고쉶 + * GET /api/schedule/:id + */ + async getScheduleById(req, res) { + try { + const { id } = req.params; + const userId = req.user.id; + const schedule = await ScheduleService.getScheduleById(id, userId); + + return res.status(200).json({ + success: true, + data: schedule + }); + } catch (error) { + if (error.message === 'Schedule not found') { + return res.status(404).json({ + success: false, + error: { + message: error.message, + code: 'SCHEDULE_NOT_FOUND' + } + }); + } + + return res.status(500).json({ + success: false, + error: { + message: 'Failed to fetch schedule', + code: 'FETCH_ERROR' + } + }); + } + } +} + +module.exports = new scheduleController(); \ No newline at end of file diff --git a/models/schedule.js b/models/schedule.js index a3878997cdd54f753115bbb352641cb91a71a25e..19a78b28216454f4ebe2eceefbcdabb2047082d3 100644 --- a/models/schedule.js +++ b/models/schedule.js @@ -1,5 +1,4 @@ // models/Schedule.js - const { DataTypes } = require('sequelize'); const sequelize = require('../config/sequelize'); const User = require('./User'); @@ -17,12 +16,21 @@ const Schedule = sequelize.define('Schedule', { type: DataTypes.DATE, allowNull: false, }, + is_fixed: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + }, + expiry_date: { + type: DataTypes.DATE, + allowNull: true + } }, { tableName: 'Schedules', - timestamps: false, + timestamps: true, // created_at怨� updated_at �먮룞 愿�由� }); Schedule.belongsTo(User, { foreignKey: 'user_id', as: 'user' }); User.hasMany(Schedule, { foreignKey: 'user_id', as: 'schedules' }); -module.exports = Schedule; +module.exports = Schedule; \ No newline at end of file diff --git a/routes/schedule.js b/routes/schedule.js index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..045bc48e689f139cd72f111ecb76ff3bbe71f508 100644 --- a/routes/schedule.js +++ b/routes/schedule.js @@ -0,0 +1,44 @@ +const express = require('express'); +const router = express.Router(); +const { isLoggedIn } = require('../middleware/auth'); +const ScheduleController = require('../controllers/scheduleController'); + +/** + * �ㅼ�以� API �쇱슦�� + * 湲곕낯 寃쎈줈: /api/schedule -> app.js�먯꽌 �깅줉 + * isLoggedIn 誘몃뱾�⑥뼱 �ъ슜�댁꽌 �몄쬆 泥댄겕 + */ +router.use(isLoggedIn); + +/** + * �꾩껜 �ㅼ�以� 議고쉶 + * GET /api/schedule/all + */ +router.get('/all', ScheduleController.getAllSchedules); + +/** + * 媛쒕퀎 �ㅼ�以� 議고쉶 + * Get /api/schedule/:id + */ +router.get('/:id', ScheduleController.getScheduleById); + +/** + * �ㅼ�以� �앹꽦 + * POST /api/schedule + */ +router.post('/', ScheduleController.createSchedule); + +/** + * �ㅼ�以� �섏젙 + * PUT /api/schedule/:id + */ +router.put('/:id', ScheduleController.updateSchedule); + +/** + * �ㅼ�以� ��젣 + * DELETE /api/schedule/:id + */ +router.delete('/:id', ScheduleController.deleteSchedule); + + +module.exports = router; \ No newline at end of file diff --git a/services/scheduleService.js b/services/scheduleService.js index 79a787f9e7e3c9358a3c8e73888d75fd501879a3..1cd9d12f49b82e49bb8dd96bad6d11287e3d8831 100644 --- a/services/scheduleService.js +++ b/services/scheduleService.js @@ -1,7 +1,7 @@ const { Op } = require('sequelize'); const Schedule = require('../models/Schedule'); -class schedulService { +class scheduleService { /** * transactin wrapper �⑥닔 @@ -54,13 +54,16 @@ class schedulService { /** * �좊룞 �ㅼ�以� 留뚮즺�� 援ы븯湲� */ - getNextMonday() { - const date = new Date(); + getNextMonday(startTime) { + const date = new Date(startTime); const day = date.getDay(); - const daysUntilNextMonday = (8 - day) % 7; - date.setDate(date.getDate() + daysUntilNextMonday); - date.setHours(0, 0, 0, 0); - return date; + const daysUntilNextMonday = (7 - day + 1) % 7; + + const nextMonday = new Date(date); + nextMonday.setDate(date.getDate() + daysUntilNextMonday); + nextMonday.setHours(0, 0, 0, 0); // �먯젙�쇰줈 �ㅼ젙 + + return nextMonday; } /** @@ -81,7 +84,7 @@ class schedulService { start_time, end_time, is_fixed, - expiry_date: is_fixed ? null : this.getNextMonday() + expiry_date: is_fixed ? null : this.getNextMonday(start_time) }; return Schedule.create(scheduleData, { transaction }); @@ -114,8 +117,15 @@ class schedulService { throw new Error('Schedule overlaps with existing schedule'); } - delete updateData.is_fixed; - return schedule.update(updateData, { transaction }); + const is_fixed = schedule.is_fixed; + const updatedData = { + ...updateData, + expiry_date: is_fixed ? null : this.getNextMonday(updateData.start_time), + updatedAt: new Date() + }; + delete updatedData.is_fixed; + + return schedule.update(updatedData, { transaction }); }); } diff --git a/utils/scheduler.js b/utils/scheduler.js new file mode 100644 index 0000000000000000000000000000000000000000..e881e899d6906bc9dd8d8f822b6fe3a84e2d8660 --- /dev/null +++ b/utils/scheduler.js @@ -0,0 +1,19 @@ +const cron = require('node-cron'); +const scheduleService = require('../services/scheduleService'); + +// 留ㅼ< �붿슂�� �먯젙�� �좊룞 �ㅼ�以� ��젣�섍린 +const initScheduleCleaner = () => { + cron.schedule('0 0 * * 1', async () => { + try { + await scheduleService.cleanExpiredSchedules(); + } catch (error) { + console.error('Failed to clean expired schedules:', error); + } + }, { + timezone: "Asia/Seoul" + }); +}; + +module.exports = { + initScheduleCleaner +}; \ No newline at end of file