Newer
Older
const { Op } = require('sequelize');
const Schedule = require('../models/Schedule');
class schedulService {
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* transactin wrapper 함수
*/
async withTransaction(callback) {
const transaction = await Schedule.sequelize.transaction();
try {
const result = await callback(transaction);
await transaction.commit();
return result;
} catch (error) {
await transaction.rollback();
throw error;
}
}
/**
* 공통 where 절 생성
*/
getScheduleWhereClause(userId, id = null) {
const where = {
user_id: userId,
[Op.or]: [
{ is_fixed: true },
{
is_fixed: false,
expiry_date: {
[Op.gt]: new Date()
}
}
]
};
if (id) {
where.id = id;
}
return where;
}
/**
* 스케줄 유효성 검사
*/
validateScheduleTime(start_time, end_time) {
if (new Date(start_time) >= new Date(end_time)) {
throw new Error('Start time must be before end time');
}
}
/**
* 유동 스케줄 만료일 구하기
*/
getNextMonday() {
const date = new Date();
const day = date.getDay();
const daysUntilNextMonday = (8 - day) % 7;
date.setDate(date.getDate() + daysUntilNextMonday);
date.setHours(0, 0, 0, 0);
return date;
}
/**
* 사용자 스케줄 생성
*/
async createSchedule({ userId, title, start_time, end_time, is_fixed }) {
return this.withTransaction(async (transaction) => {
this.validateScheduleTime(start_time, end_time);
const overlap = await this.checkScheduleOverlap(userId, start_time, end_time);
if (overlap) {
throw new Error('Schedule overlaps with existing schedule');
}
user_id: userId,
title,
start_time,
end_time,
is_fixed,
expiry_date: is_fixed ? null : this.getNextMonday()
};
return Schedule.create(scheduleData, { transaction });
});
/**
* 사용자 스케줄 수정
*/
async updateSchedule(id, userId, updateData) {
return this.withTransaction(async (transaction) => {
this.validateScheduleTime(updateData.start_time, updateData.end_time);
const overlap = await this.checkScheduleOverlap(
userId,
updateData.start_time,
updateData.end_time,
id
);
if (overlap) {
throw new Error('Schedule overlaps with existing schedule');
}
return schedule.update(updateData, { transaction });
});
/**
* 사용자 스케줄 삭제
*/
async deleteSchedule(id, userId) {
return this.withTransaction(async (transaction) => {
const result = await Schedule.destroy({
where: { id, user_id: userId },
transaction
/**
* 해당 사용자의 스케줄 정보 조회
*/
async getAllSchedules(userId) {
try {
return Schedule.findAll({
where: this.getScheduleWhereClause(userId),
order: [['start_time', 'ASC']]
});
} catch (error) {
throw new Error(`Failed to fetch schedules: ${error.message}`);
}
}
/**
* 해당 사용자의 특정 스케줄 조회
*/
async getScheduleById(id, userId) {
try {
const schedule = await Schedule.findOne({
});
if (!schedule) {
throw new Error('Schedule not found');
}
return schedule;
} catch (error) {
throw new Error(`Failed to fetch schedule: ${error.message}`);
}
}
/**
* 만료된 유동 스케줄 정리 -> utils에 cron job 추가해서 실행하도록 설정
*/
async cleanExpiredSchedules() {
try {
await Schedule.destroy({
where: {
is_fixed: false,
expiry_date: {
[Op.lte]: new Date()
}
}
});
} catch (error) {
throw new Error(`Failed to clean expired schedules: ${error.message}`);
}
}
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
/**
* 스케줄 중복 검사 -> 기존 스케줄 시간대에 추가 못하도록
*/
async checkScheduleOverlap(userId, start_time, end_time, excludeId = null) {
try {
const where = {
user_id: userId,
[Op.or]: [
{
// 새로운 스케줄이 기존 스케줄 내 존재
[Op.and]: [
{ start_time: { [Op.lte]: start_time } },
{ end_time: { [Op.gte]: start_time } }
]
},
{
// 새로운 스케줄이 기존 스케줄을 포함
[Op.and]: [
{ start_time: { [Op.gte]: start_time } },
{ start_time: { [Op.lte]: end_time } }
]
}
]
};
if (excludeId) {
where.id = { [Op.ne]: excludeId };
}
const overlappingSchedule = await Schedule.findOne({ where });
return overlappingSchedule;
} catch (error) {
throw new Error(`Failed to check schedule overlap: ${error.message}`);
}
}