diff --git a/backend-node/src/services/bookingService.ts b/backend-node/src/services/bookingService.ts index 79935414..b27544e1 100644 --- a/backend-node/src/services/bookingService.ts +++ b/backend-node/src/services/bookingService.ts @@ -53,13 +53,20 @@ export class BookingService { } private ensureDataDirectory(): void { - if (!fs.existsSync(BOOKING_DIR)) { - fs.mkdirSync(BOOKING_DIR, { recursive: true }); - logger.info(`πŸ“ μ˜ˆμ•½ 데이터 디렉토리 생성: ${BOOKING_DIR}`); - } - if (!fs.existsSync(BOOKING_FILE)) { - fs.writeFileSync(BOOKING_FILE, JSON.stringify([], null, 2)); - logger.info(`πŸ“„ μ˜ˆμ•½ 파일 생성: ${BOOKING_FILE}`); + try { + if (!fs.existsSync(BOOKING_DIR)) { + fs.mkdirSync(BOOKING_DIR, { recursive: true, mode: 0o755 }); + logger.info(`πŸ“ μ˜ˆμ•½ 데이터 디렉토리 생성: ${BOOKING_DIR}`); + } + if (!fs.existsSync(BOOKING_FILE)) { + fs.writeFileSync(BOOKING_FILE, JSON.stringify([], null, 2), { + mode: 0o644, + }); + logger.info(`πŸ“„ μ˜ˆμ•½ 파일 생성: ${BOOKING_FILE}`); + } + } catch (error) { + logger.error(`❌ μ˜ˆμ•½ 디렉토리 생성 μ‹€νŒ¨: ${BOOKING_DIR}`, error); + throw error; } } @@ -111,13 +118,16 @@ export class BookingService { priority?: string; }): Promise<{ bookings: BookingRequest[]; newCount: number }> { try { - const bookings = DATA_SOURCE === "database" - ? await this.loadBookingsFromDB(filter) - : this.loadBookingsFromFile(filter); + const bookings = + DATA_SOURCE === "database" + ? await this.loadBookingsFromDB(filter) + : this.loadBookingsFromFile(filter); bookings.sort((a, b) => { if (a.priority !== b.priority) return a.priority === "urgent" ? -1 : 1; - return new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(); + return ( + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() + ); }); const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000); @@ -145,7 +155,10 @@ export class BookingService { } } - public async rejectBooking(id: string, reason?: string): Promise { + public async rejectBooking( + id: string, + reason?: string + ): Promise { try { if (DATA_SOURCE === "database") { return await this.rejectBookingDB(id, reason); @@ -194,9 +207,15 @@ export class BookingService { scheduledTime: new Date(row.scheduledTime).toISOString(), createdAt: new Date(row.createdAt).toISOString(), updatedAt: new Date(row.updatedAt).toISOString(), - acceptedAt: row.acceptedAt ? new Date(row.acceptedAt).toISOString() : undefined, - rejectedAt: row.rejectedAt ? new Date(row.rejectedAt).toISOString() : undefined, - completedAt: row.completedAt ? new Date(row.completedAt).toISOString() : undefined, + acceptedAt: row.acceptedAt + ? new Date(row.acceptedAt).toISOString() + : undefined, + rejectedAt: row.rejectedAt + ? new Date(row.rejectedAt).toISOString() + : undefined, + completedAt: row.completedAt + ? new Date(row.completedAt).toISOString() + : undefined, })); } @@ -230,7 +249,10 @@ export class BookingService { }; } - private async rejectBookingDB(id: string, reason?: string): Promise { + private async rejectBookingDB( + id: string, + reason?: string + ): Promise { const rows = await query( `UPDATE booking_requests SET status = 'rejected', rejected_at = NOW(), updated_at = NOW(), rejection_reason = $2 diff --git a/backend-node/src/services/mailAccountFileService.ts b/backend-node/src/services/mailAccountFileService.ts index 7b07b531..e547171a 100644 --- a/backend-node/src/services/mailAccountFileService.ts +++ b/backend-node/src/services/mailAccountFileService.ts @@ -33,11 +33,7 @@ class MailAccountFileService { try { await fs.access(this.accountsDir); } catch { - try { - await fs.mkdir(this.accountsDir, { recursive: true }); - } catch (error) { - console.error("메일 계정 디렉토리 생성 μ‹€νŒ¨:", error); - } + await fs.mkdir(this.accountsDir, { recursive: true, mode: 0o755 }); } } diff --git a/backend-node/src/services/mailReceiveBasicService.ts b/backend-node/src/services/mailReceiveBasicService.ts index 741353fa..d5e3a78f 100644 --- a/backend-node/src/services/mailReceiveBasicService.ts +++ b/backend-node/src/services/mailReceiveBasicService.ts @@ -59,11 +59,7 @@ export class MailReceiveBasicService { try { await fs.access(this.attachmentsDir); } catch { - try { - await fs.mkdir(this.attachmentsDir, { recursive: true }); - } catch (error) { - console.error("메일 μ²¨λΆ€νŒŒμΌ 디렉토리 생성 μ‹€νŒ¨:", error); - } + await fs.mkdir(this.attachmentsDir, { recursive: true, mode: 0o755 }); } } diff --git a/backend-node/src/services/mailSentHistoryService.ts b/backend-node/src/services/mailSentHistoryService.ts index 61fd6f89..c7828888 100644 --- a/backend-node/src/services/mailSentHistoryService.ts +++ b/backend-node/src/services/mailSentHistoryService.ts @@ -20,15 +20,13 @@ const SENT_MAIL_DIR = class MailSentHistoryService { constructor() { - // 디렉토리 생성 (μ—†μœΌλ©΄) - try-catch둜 κΆŒν•œ μ—λŸ¬ λ°©μ§€ try { if (!fs.existsSync(SENT_MAIL_DIR)) { - fs.mkdirSync(SENT_MAIL_DIR, { recursive: true }); + fs.mkdirSync(SENT_MAIL_DIR, { recursive: true, mode: 0o755 }); } } catch (error) { console.error("메일 λ°œμ†‘ 이λ ₯ 디렉토리 생성 μ‹€νŒ¨:", error); - // 디렉토리가 이미 μ‘΄μž¬ν•˜κ±°λ‚˜ κΆŒν•œμ΄ 없어도 μ„œλΉ„μŠ€λŠ” 계속 μ‹€ν–‰ - // μ‹€μ œ 파일 μ“°κΈ° μ‹œμ μ— μ—λŸ¬ 처리 + throw error; } } @@ -45,13 +43,15 @@ class MailSentHistoryService { }; try { - // 디렉토리가 μ—†μœΌλ©΄ λ‹€μ‹œ μ‹œλ„ if (!fs.existsSync(SENT_MAIL_DIR)) { - fs.mkdirSync(SENT_MAIL_DIR, { recursive: true }); + fs.mkdirSync(SENT_MAIL_DIR, { recursive: true, mode: 0o755 }); } const filePath = path.join(SENT_MAIL_DIR, `${history.id}.json`); - fs.writeFileSync(filePath, JSON.stringify(history, null, 2), "utf-8"); + fs.writeFileSync(filePath, JSON.stringify(history, null, 2), { + encoding: "utf-8", + mode: 0o644, + }); console.log("λ°œμ†‘ 이λ ₯ μ €μž₯:", history.id); } catch (error) { diff --git a/backend-node/src/services/mailTemplateFileService.ts b/backend-node/src/services/mailTemplateFileService.ts index 7a8d4300..e1a878b9 100644 --- a/backend-node/src/services/mailTemplateFileService.ts +++ b/backend-node/src/services/mailTemplateFileService.ts @@ -54,17 +54,13 @@ class MailTemplateFileService { } /** - * ν…œν”Œλ¦Ώ 디렉토리 생성 (μ—†μœΌλ©΄) - try-catch둜 κΆŒν•œ μ—λŸ¬ λ°©μ§€ + * ν…œν”Œλ¦Ώ 디렉토리 생성 */ private async ensureDirectoryExists() { try { await fs.access(this.templatesDir); } catch { - try { - await fs.mkdir(this.templatesDir, { recursive: true }); - } catch (error) { - console.error("메일 ν…œν”Œλ¦Ώ 디렉토리 생성 μ‹€νŒ¨:", error); - } + await fs.mkdir(this.templatesDir, { recursive: true, mode: 0o755 }); } } diff --git a/backend-node/src/services/todoService.ts b/backend-node/src/services/todoService.ts index 1347c665..33becbb9 100644 --- a/backend-node/src/services/todoService.ts +++ b/backend-node/src/services/todoService.ts @@ -61,13 +61,20 @@ export class TodoService { * 데이터 디렉토리 생성 (파일 λͺ¨λ“œ) */ private ensureDataDirectory(): void { - if (!fs.existsSync(TODO_DIR)) { - fs.mkdirSync(TODO_DIR, { recursive: true }); - logger.info(`πŸ“ To-Do 데이터 디렉토리 생성: ${TODO_DIR}`); - } - if (!fs.existsSync(TODO_FILE)) { - fs.writeFileSync(TODO_FILE, JSON.stringify([], null, 2)); - logger.info(`πŸ“„ To-Do 파일 생성: ${TODO_FILE}`); + try { + if (!fs.existsSync(TODO_DIR)) { + fs.mkdirSync(TODO_DIR, { recursive: true, mode: 0o755 }); + logger.info(`πŸ“ To-Do 데이터 디렉토리 생성: ${TODO_DIR}`); + } + if (!fs.existsSync(TODO_FILE)) { + fs.writeFileSync(TODO_FILE, JSON.stringify([], null, 2), { + mode: 0o644, + }); + logger.info(`πŸ“„ To-Do 파일 생성: ${TODO_FILE}`); + } + } catch (error) { + logger.error(`❌ To-Do 디렉토리 생성 μ‹€νŒ¨: ${TODO_DIR}`, error); + throw error; } } @@ -80,15 +87,17 @@ export class TodoService { assignedTo?: string; }): Promise { try { - const todos = DATA_SOURCE === "database" - ? await this.loadTodosFromDB(filter) - : this.loadTodosFromFile(filter); + const todos = + DATA_SOURCE === "database" + ? await this.loadTodosFromDB(filter) + : this.loadTodosFromFile(filter); // μ •λ ¬: κΈ΄κΈ‰ > μš°μ„ μˆœμœ„ > μˆœμ„œ todos.sort((a, b) => { if (a.isUrgent !== b.isUrgent) return a.isUrgent ? -1 : 1; const priorityOrder = { urgent: 0, high: 1, normal: 2, low: 3 }; - if (a.priority !== b.priority) return priorityOrder[a.priority] - priorityOrder[b.priority]; + if (a.priority !== b.priority) + return priorityOrder[a.priority] - priorityOrder[b.priority]; return a.order - b.order; }); @@ -124,7 +133,8 @@ export class TodoService { await this.createTodoDB(newTodo); } else { const todos = this.loadTodosFromFile(); - newTodo.order = todos.length > 0 ? Math.max(...todos.map((t) => t.order)) + 1 : 0; + newTodo.order = + todos.length > 0 ? Math.max(...todos.map((t) => t.order)) + 1 : 0; todos.push(newTodo); this.saveTodosToFile(todos); } @@ -140,7 +150,10 @@ export class TodoService { /** * To-Do ν•­λͺ© μˆ˜μ • */ - public async updateTodo(id: string, updates: Partial): Promise { + public async updateTodo( + id: string, + updates: Partial + ): Promise { try { if (DATA_SOURCE === "database") { return await this.updateTodoDB(id, updates); @@ -231,7 +244,9 @@ export class TodoService { dueDate: row.dueDate ? new Date(row.dueDate).toISOString() : undefined, createdAt: new Date(row.createdAt).toISOString(), updatedAt: new Date(row.updatedAt).toISOString(), - completedAt: row.completedAt ? new Date(row.completedAt).toISOString() : undefined, + completedAt: row.completedAt + ? new Date(row.completedAt).toISOString() + : undefined, })); } @@ -263,7 +278,10 @@ export class TodoService { ); } - private async updateTodoDB(id: string, updates: Partial): Promise { + private async updateTodoDB( + id: string, + updates: Partial + ): Promise { const setClauses: string[] = ["updated_at = NOW()"]; const params: any[] = []; let paramIndex = 1; @@ -327,12 +345,17 @@ export class TodoService { dueDate: row.dueDate ? new Date(row.dueDate).toISOString() : undefined, createdAt: new Date(row.createdAt).toISOString(), updatedAt: new Date(row.updatedAt).toISOString(), - completedAt: row.completedAt ? new Date(row.completedAt).toISOString() : undefined, + completedAt: row.completedAt + ? new Date(row.completedAt).toISOString() + : undefined, }; } private async deleteTodoDB(id: string): Promise { - const rows = await query("DELETE FROM todo_items WHERE id = $1 RETURNING id", [id]); + const rows = await query( + "DELETE FROM todo_items WHERE id = $1 RETURNING id", + [id] + ); if (rows.length === 0) { throw new Error(`To-Do ν•­λͺ©μ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€: ${id}`); } @@ -443,7 +466,10 @@ export class TodoService { inProgress: todos.filter((t) => t.status === "in_progress").length, completed: todos.filter((t) => t.status === "completed").length, urgent: todos.filter((t) => t.isUrgent).length, - overdue: todos.filter((t) => t.dueDate && new Date(t.dueDate) < now && t.status !== "completed").length, + overdue: todos.filter( + (t) => + t.dueDate && new Date(t.dueDate) < now && t.status !== "completed" + ).length, }; } } diff --git a/docker/deploy/backend.Dockerfile b/docker/deploy/backend.Dockerfile index bbfd3438..a5dd1aeb 100644 --- a/docker/deploy/backend.Dockerfile +++ b/docker/deploy/backend.Dockerfile @@ -34,14 +34,11 @@ COPY --from=build /app/dist ./dist # Copy package files COPY package*.json ./ -# Create logs, uploads, and data directories and set permissions (use existing node user with UID 1000) -RUN mkdir -p logs \ - uploads/mail-attachments \ - uploads/mail-templates \ - uploads/mail-accounts \ - data/mail-sent && \ - chown -R node:node logs uploads data && \ - chmod -R 755 logs uploads data +# 루트 λ””λ ‰ν† λ¦¬λ§Œ μƒμ„±ν•˜κ³  node μœ μ €μ—κ²Œ μ“°κΈ° κΆŒν•œ λΆ€μ—¬ +# ν•˜μœ„ λ””λ ‰ν† λ¦¬λŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ λŸ°νƒ€μž„μ— μžλ™ 생성 +RUN mkdir -p logs uploads data && \ + chown -R node:node /app && \ + chmod -R 755 /app EXPOSE 3001 USER node diff --git a/docker/prod/backend.Dockerfile b/docker/prod/backend.Dockerfile index 42779b66..f5d54a9e 100644 --- a/docker/prod/backend.Dockerfile +++ b/docker/prod/backend.Dockerfile @@ -37,10 +37,11 @@ COPY --from=build /app/dist ./dist # Copy package files COPY package*.json ./ -# Create logs directory and set permissions -RUN mkdir -p logs && chown -R appuser:appgroup logs && chmod -R 755 logs - -# uploads λ””λ ‰ν† λ¦¬λŠ” λ³Όλ₯¨μœΌλ‘œ λ§ˆμš΄νŠΈλ˜λ―€λ‘œ μƒμ„±ν•˜μ§€ μ•ŠμŒ +# 루트 λ””λ ‰ν† λ¦¬λ§Œ μƒμ„±ν•˜κ³  appuserμ—κ²Œ μ“°κΈ° κΆŒν•œ λΆ€μ—¬ +# ν•˜μœ„ λ””λ ‰ν† λ¦¬λŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ΄ λŸ°νƒ€μž„μ— μžλ™ 생성 +RUN mkdir -p logs uploads data && \ + chown -R appuser:appgroup /app && \ + chmod -R 755 /app EXPOSE 3001 USER appuser diff --git a/scripts/prod/deploy.sh b/scripts/prod/deploy.sh index b5388d54..cad282cd 100755 --- a/scripts/prod/deploy.sh +++ b/scripts/prod/deploy.sh @@ -20,17 +20,15 @@ echo "" echo "[1/6] Git μ΅œμ‹  μ½”λ“œ κ°€μ Έμ˜€κΈ°..." git pull origin main -# 호슀트 디렉토리 μ€€λΉ„ +# 호슀트 디렉토리 μ€€λΉ„ (λ³Όλ₯¨ 마운트용 루트 λ””λ ‰ν† λ¦¬λ§Œ 생성) echo "" echo "[2/6] 호슀트 디렉토리 μ€€λΉ„..." -mkdir -p /home/vexplor/backend_data/data/mail-sent -mkdir -p /home/vexplor/backend_data/uploads/mail-attachments -mkdir -p /home/vexplor/backend_data/uploads/mail-templates -mkdir -p /home/vexplor/backend_data/uploads/mail-accounts +mkdir -p /home/vexplor/backend_data/uploads +mkdir -p /home/vexplor/backend_data/data mkdir -p /home/vexplor/frontend_data chmod -R 755 /home/vexplor/backend_data chmod -R 755 /home/vexplor/frontend_data -echo "디렉토리 생성 μ™„λ£Œ (mail-sent, mail-attachments, mail-templates, mail-accounts, frontend)" +echo "λ³Όλ₯¨ 마운트 디렉토리 생성 μ™„λ£Œ (ν•˜μœ„ λ””λ ‰ν† λ¦¬λŠ” μ»¨ν…Œμ΄λ„ˆκ°€ μžλ™ 생성)" # κΈ°μ‘΄ μ»¨ν…Œμ΄λ„ˆ 쀑지 및 제거 echo ""