import { Response } from "express"; import { AuthenticatedRequest } from "../types/auth"; import { logger } from "../utils/logger"; import { query } from "../database/db"; /** * GET /api/system-notices * 공지사항 목록 조회 * - 최고 관리자(*): 전체 조회 * - 일반 회사: 자신의 company_code 데이터만 조회 * - is_active 필터 옵션 지원 */ export const getSystemNotices = async ( req: AuthenticatedRequest, res: Response ): Promise => { try { const companyCode = req.user!.companyCode; const { is_active } = req.query; logger.info("공지사항 목록 조회 요청", { companyCode, is_active }); const conditions: string[] = []; const params: any[] = []; let paramIndex = 1; // 최고 관리자가 아닌 경우 company_code 필터링 if (companyCode !== "*") { conditions.push(`company_code = $${paramIndex}`); params.push(companyCode); paramIndex++; } // is_active 필터 (true/false 문자열 처리) if (is_active !== undefined && is_active !== "") { const activeValue = is_active === "true" || is_active === "1"; conditions.push(`is_active = $${paramIndex}`); params.push(activeValue); paramIndex++; } const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : ""; const rows = await query( `SELECT id, company_code, title, content, is_active, created_by, created_at, updated_at FROM system_notice ${whereClause} ORDER BY created_at DESC`, params ); logger.info("공지사항 목록 조회 성공", { companyCode, count: rows.length, }); res.status(200).json({ success: true, data: rows, total: rows.length, }); } catch (error) { logger.error("공지사항 목록 조회 실패", { error }); res.status(500).json({ success: false, message: "공지사항 목록 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } }; /** * POST /api/system-notices * 공지사항 등록 * - company_code는 req.user.companyCode에서 자동 추출 (클라이언트 입력 신뢰 금지) */ export const createSystemNotice = async ( req: AuthenticatedRequest, res: Response ): Promise => { try { const companyCode = req.user!.companyCode; const userId = req.user!.userId; const { title, content, is_active = true } = req.body; logger.info("공지사항 등록 요청", { companyCode, userId, title }); if (!title || !title.trim()) { res.status(400).json({ success: false, message: "제목을 입력해주세요.", }); return; } if (!content || !content.trim()) { res.status(400).json({ success: false, message: "내용을 입력해주세요.", }); return; } const [created] = await query( `INSERT INTO system_notice (company_code, title, content, is_active, created_by) VALUES ($1, $2, $3, $4, $5) RETURNING *`, [companyCode, title.trim(), content.trim(), is_active, userId] ); logger.info("공지사항 등록 성공", { id: created.id, companyCode, title: created.title, }); res.status(201).json({ success: true, data: created, message: "공지사항이 등록되었습니다.", }); } catch (error) { logger.error("공지사항 등록 실패", { error }); res.status(500).json({ success: false, message: "공지사항 등록 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } }; /** * PUT /api/system-notices/:id * 공지사항 수정 * - WHERE id=$1 AND company_code=$2 로 타 회사 데이터 수정 차단 * - 최고 관리자는 company_code 조건 없이 id만으로 수정 가능 */ export const updateSystemNotice = async ( req: AuthenticatedRequest, res: Response ): Promise => { try { const companyCode = req.user!.companyCode; const { id } = req.params; const { title, content, is_active } = req.body; logger.info("공지사항 수정 요청", { id, companyCode }); if (!title || !title.trim()) { res.status(400).json({ success: false, message: "제목을 입력해주세요.", }); return; } if (!content || !content.trim()) { res.status(400).json({ success: false, message: "내용을 입력해주세요.", }); return; } let result: any[]; if (companyCode === "*") { // 최고 관리자: id만으로 수정 result = await query( `UPDATE system_notice SET title = $1, content = $2, is_active = $3, updated_at = NOW() WHERE id = $4 RETURNING *`, [title.trim(), content.trim(), is_active ?? true, id] ); } else { // 일반 회사: company_code 추가 조건으로 타 회사 데이터 수정 차단 result = await query( `UPDATE system_notice SET title = $1, content = $2, is_active = $3, updated_at = NOW() WHERE id = $4 AND company_code = $5 RETURNING *`, [title.trim(), content.trim(), is_active ?? true, id, companyCode] ); } if (!result || result.length === 0) { res.status(404).json({ success: false, message: "공지사항을 찾을 수 없거나 수정 권한이 없습니다.", }); return; } logger.info("공지사항 수정 성공", { id, companyCode }); res.status(200).json({ success: true, data: result[0], message: "공지사항이 수정되었습니다.", }); } catch (error) { logger.error("공지사항 수정 실패", { error, id: req.params.id }); res.status(500).json({ success: false, message: "공지사항 수정 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } }; /** * DELETE /api/system-notices/:id * 공지사항 삭제 * - WHERE id=$1 AND company_code=$2 로 타 회사 데이터 삭제 차단 * - 최고 관리자는 company_code 조건 없이 id만으로 삭제 가능 */ export const deleteSystemNotice = async ( req: AuthenticatedRequest, res: Response ): Promise => { try { const companyCode = req.user!.companyCode; const { id } = req.params; logger.info("공지사항 삭제 요청", { id, companyCode }); let result: any[]; if (companyCode === "*") { // 최고 관리자: id만으로 삭제 result = await query( `DELETE FROM system_notice WHERE id = $1 RETURNING id`, [id] ); } else { // 일반 회사: company_code 추가 조건으로 타 회사 데이터 삭제 차단 result = await query( `DELETE FROM system_notice WHERE id = $1 AND company_code = $2 RETURNING id`, [id, companyCode] ); } if (!result || result.length === 0) { res.status(404).json({ success: false, message: "공지사항을 찾을 수 없거나 삭제 권한이 없습니다.", }); return; } logger.info("공지사항 삭제 성공", { id, companyCode }); res.status(200).json({ success: true, message: "공지사항이 삭제되었습니다.", }); } catch (error) { logger.error("공지사항 삭제 실패", { error, id: req.params.id }); res.status(500).json({ success: false, message: "공지사항 삭제 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "Unknown error", }); } };