From d28f525aa80051b3f8e383c82251f6d3bdb5195e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=EC=A1=B0=EB=8C=80=ED=9D=AC?= <joedaehui@ajou.ac.kr>
Date: Wed, 4 Dec 2024 18:45:58 +0900
Subject: [PATCH] =?UTF-8?q?refactor:=20=EC=8A=A4=EC=BC=80=EC=A4=84=20respo?=
 =?UTF-8?q?nse=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20=EC=84=9C=EB=B9=84?=
 =?UTF-8?q?=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 dtos/ScheduleResponseDTO.js |  27 ++--
 services/scheduleService.js | 238 +++++++++++++++++++++++-------------
 2 files changed, 170 insertions(+), 95 deletions(-)

diff --git a/dtos/ScheduleResponseDTO.js b/dtos/ScheduleResponseDTO.js
index a75316e..7527621 100644
--- a/dtos/ScheduleResponseDTO.js
+++ b/dtos/ScheduleResponseDTO.js
@@ -1,14 +1,25 @@
 // dtos/ScheduleResponseDTO.js
 
 class ScheduleResponseDTO {
-  constructor(schedule) {
-      this.id = schedule.id;
-      this.user_id = schedule.user_id;
-      this.title = schedule.title;
-      this.time_idx = schedule.time_idx; // 새로운 time_idx 필드 추가
-      this.is_fixed = schedule.is_fixed;
-      this.createdAt = schedule.createdAt;
-      this.updatedAt = schedule.updatedAt;
+  static groupSchedules(schedules) {
+      const grouped = schedules.reduce((acc, schedule) => {
+          const key = `${schedule.title}-${schedule.is_fixed}`;
+          if (!acc[key]) {
+              acc[key] = {
+                  id: schedule.id,
+                  user_id: schedule.user_id,
+                  title: schedule.title,
+                  is_fixed: schedule.is_fixed,
+                  time_indices: [],
+                  createdAt: schedule.createdAt,
+                  updatedAt: schedule.updatedAt
+              };
+          }
+          acc[key].time_indices.push(schedule.time_idx);
+          return acc;
+      }, {});
+
+      return Object.values(grouped);
   }
 }
 
diff --git a/services/scheduleService.js b/services/scheduleService.js
index e664a49..abfb66e 100644
--- a/services/scheduleService.js
+++ b/services/scheduleService.js
@@ -9,158 +9,222 @@ class ScheduleService {
      * 스케줄 생성 (벌크)
      * @param {object} [transaction] - Sequelize 트랜잭션 객체 -> 미팅방에서 쓰기위해 트랜잭션을 넘겨받는걸 추가 
      */
-    async createSchedules({ userId, title, is_fixed, events }, transaction = null) {
-        const scheduleDTOs = [];
-
-        for (const event of events) {
-            const { time_idx } = event;
-
-            // 중복 스케줄 검사
+    async createSchedules({ userId, title, is_fixed, time_indices }, transaction = null) {
+        // 중복 검사
+        for (const time_idx of time_indices) {
             const overlap = await this.checkScheduleOverlap(userId, time_idx, transaction);
             if (overlap) {
-                throw new Error(`Schedule overlaps with existing schedule at time_idx ${time_idx}`);
+                throw new Error(`Schedule overlaps at time_idx ${time_idx}`);
             }
-
-            const scheduleData = {
-                user_id: userId,
-                title,
-                time_idx,
-                is_fixed,
-            };
-
-            const schedule = await Schedule.create(scheduleData, { transaction });
-            scheduleDTOs.push(new ScheduleResponseDTO(schedule));
         }
 
-        return scheduleDTOs;
-    }
-
-    /**
-     * 스케줄 수정 (벌크)
-     * @param {Array} updates - 수정할 스케줄 배열
-     */
-    async updateSchedules(userId, updates, transaction = null) {
-        const updatedSchedules = [];
+        const createdSchedules = await Promise.all(
+            time_indices.map(time_idx =>
+                Schedule.create({
+                    user_id: userId,
+                    title,
+                    time_idx,
+                    is_fixed
+                }, { transaction })
+            )
+        );
 
-        for (const update of updates) {
-            const { time_idx, title, is_fixed } = update;
+        return {
+            id: createdSchedules[0].id,
+            user_id: userId,
+            title,
+            is_fixed,
+            time_indices,
+            createdAt: createdSchedules[0].createdAt,
+            updatedAt: createdSchedules[0].updatedAt
+        };
+    }
 
-            const schedule = await Schedule.findOne({
-                where: { user_id: userId, time_idx },
-                transaction,
+    async getAllSchedules(userId) {
+        try {
+            const schedules = await Schedule.findAll({
+                where: { user_id: userId },
+                order: [['time_idx', 'ASC']]
             });
 
-            if (!schedule) {
-                throw new Error(`Schedule not found at time_idx ${time_idx}`);
-            }
+            return ScheduleResponseDTO.groupSchedules(schedules);
+        } catch (error) {
+            throw new Error(`Failed to fetch schedules: ${error.message}`);
+        }
+    }
+
+    async updateSchedules(userId, updates, transaction = null) {
+        const { originalTitle, title, is_fixed, time_indices } = updates;
 
-            const updatedData = {};
-            if (title !== undefined) updatedData.title = title;
-            if (is_fixed !== undefined) updatedData.is_fixed = is_fixed;
+        // 기존 스케줄 조회
+        const existingSchedules = await Schedule.findAll({
+            where: {
+                user_id: userId,
+                title: originalTitle
+            },
+            transaction
+        });
 
-            const updatedSchedule = await schedule.update(updatedData, { transaction });
-            updatedSchedules.push(new ScheduleResponseDTO(updatedSchedule));
+        if (existingSchedules.length === 0) {
+            throw new Error('Schedule not found');
         }
 
-        return updatedSchedules;
-    }
+        const existingTimeIndices = existingSchedules.map(s => s.time_idx); // 기존 시간대
+        const toDelete = existingTimeIndices.filter(idx => !time_indices.includes(idx)); // 삭제할 시간대
+        const toAdd = time_indices.filter(idx => !existingTimeIndices.includes(idx)); // 추가할 시간대
+        const t = transaction || await sequelize.transaction();
 
-    /**
-     * 스케줄 삭제 (벌크)
-     * @param {number} userId - 사용자 ID
-     * @param {Array<number>} time_idxs - 삭제할 스케줄의 time_idx 배열
-     * @param {object} [transaction] - Sequelize 트랜잭션 객체
-     */
-    async deleteSchedules(userId, time_idxs, transaction = null) {
-        const deleted_time_idxs = [];
+        try {
+            // 삭제
+            if (toDelete.length > 0) {
+                await Schedule.destroy({
+                    where: {
+                        user_id: userId,
+                        title: originalTitle,
+                        time_idx: {
+                            [Op.in]: toDelete
+                        }
+                    },
+                    transaction: t
+                });
+            }
 
-        for (const time_idx of time_idxs) {
-            const deletedCount = await Schedule.destroy({
-                where: { user_id: userId, time_idx },
-                transaction,
-            });
+            // 제목, 고정/유동 업데이트
+            await Schedule.update(
+                {
+                    title,
+                    is_fixed
+                },
+                {
+                    where: {
+                        user_id: userId,
+                        title: originalTitle
+                    },
+                    transaction: t
+                }
+            );
+
+            // 새로운 time_indices 추가
+            if (toAdd.length > 0) {
+                await Promise.all(
+                    toAdd.map(time_idx =>
+                        Schedule.create({
+                            user_id: userId,
+                            title,
+                            time_idx,
+                            is_fixed
+                        }, { transaction: t })
+                    )
+                );
+            }
 
-            if (deletedCount === 0) {
-                throw new Error(`Schedule not found at time_idx ${time_idx}`);
+            if (!transaction) {
+                await t.commit();
             }
 
-            deleted_time_idxs.push(time_idx);
+            return {
+                id: existingSchedules[0].id,
+                user_id: userId,
+                title,
+                is_fixed,
+                time_indices,
+                createdAt: existingSchedules[0].createdAt,
+                updatedAt: new Date()
+            };
+
+        } catch (error) {
+            if (!transaction) {
+                await t.rollback();
+            }
+            throw error;
         }
+    }
+
+    async deleteSchedules(userId, title, transaction = null) {
+        const deletedSchedules = await Schedule.destroy({
+            where: {
+                user_id: userId,
+                title
+            },
+            transaction
+        });
 
-        return { deleted_time_idxs };
+        return { deletedCount: deletedSchedules };
     }
 
     /**
      * 특정 time_idx로 스케줄 조회
      */
     async getScheduleByTimeIdx(userId, time_idx) {
+        // 해당 time_idx의 스케줄 찾기
         const schedule = await Schedule.findOne({
-            where: { user_id: userId, time_idx },
+            where: { user_id: userId, time_idx }
         });
 
         if (!schedule) {
             throw new Error('Schedule not found');
         }
 
-        return new ScheduleResponseDTO(schedule);
+        // 같은 제목의 모든 스케줄 찾기
+        const relatedSchedules = await Schedule.findAll({
+            where: {
+                user_id: userId,
+                title: schedule.title,
+                is_fixed: schedule.is_fixed
+            },
+            order: [['time_idx', 'ASC']]
+        });
+
+        return ScheduleResponseDTO.groupSchedules(relatedSchedules)[0];
     }
 
-    /**
-     * 모든 스케줄 조회
-     */
     async getAllSchedules(userId) {
         try {
             const schedules = await Schedule.findAll({
                 where: { user_id: userId },
-                order: [['time_idx', 'ASC']],
+                order: [['time_idx', 'ASC']]
             });
-            return schedules.map((schedule) => new ScheduleResponseDTO(schedule));
+            return ScheduleResponseDTO.groupSchedules(schedules);
         } catch (error) {
             throw new Error(`Failed to fetch schedules: ${error.message}`);
         }
     }
 
-    /**
-     * 중복 스케줄 검사
-     */
     async checkScheduleOverlap(userId, time_idx, transaction = null) {
         const overlappingSchedule = await Schedule.findOne({
             where: { user_id: userId, time_idx },
-            transaction,
+            transaction
         });
-
         return !!overlappingSchedule;
     }
 
     async checkScheduleOverlapByTime(userId, time_idx_start, time_idx_end, transaction = null) {
-        console.log(
-            `checkScheduleOverlapByTime 호출: userId=${userId}, time_idx_start=${time_idx_start}, time_idx_end=${time_idx_end}`
-        );
-        const overlappingSchedule = await Schedule.findOne({
+        const overlappingSchedules = await Schedule.findAll({
             where: {
                 user_id: userId,
                 time_idx: {
-                    [Op.between]: [time_idx_start, time_idx_end] 
+                    [Op.between]: [time_idx_start, time_idx_end]
                 }
             },
-            transaction,
+            transaction
         });
-         console.log(`중복 스케줄: ${JSON.stringify(overlappingSchedule)}`);
-    const result = !!overlappingSchedule;
-    console.log(`스케줄 충돌 결과: ${result}`);
-    return result;
+
+        const groupedSchedules = ScheduleResponseDTO.groupSchedules(overlappingSchedules);
+        const result = groupedSchedules.length > 0;
+
+        console.log(`checkScheduleOverlapByTime 호출: userId=${userId}, time_idx_start=${time_idx_start}, time_idx_end=${time_idx_end}`);
+        console.log(`중복 스케줄: ${JSON.stringify(groupedSchedules)}`);
+        console.log(`스케줄 충돌 결과: ${result}`);
+
+        return result;
     }
-    
 
-    /**
-     * 만료된 스케줄 삭제
-     */
     async cleanExpiredSchedules() {
         try {
             const deletedCount = await Schedule.destroy({
-                where: { is_fixed: false },
+                where: { is_fixed: false }
             });
-            //console.log(`Deleted ${deletedCount} flexible schedules.`);
+            return { deletedCount };
         } catch (error) {
             console.error('Failed to clean expired schedules:', error);
             throw error;
-- 
GitLab