import { Response } from 'express'; import { AuthenticatedRequest } from '../middleware/authMiddleware'; import { DashboardService } from '../services/DashboardService'; import { CreateDashboardRequest, UpdateDashboardRequest, DashboardListQuery } from '../types/dashboard'; import { PostgreSQLService } from '../database/PostgreSQLService'; /** * 대시보드 컨트롤러 * - REST API 엔드포인트 처리 * - 요청 검증 및 응답 포맷팅 */ export class DashboardController { /** * 대시보드 생성 * POST /api/dashboards */ async createDashboard(req: AuthenticatedRequest, res: Response): Promise { try { const userId = req.user?.userId; if (!userId) { res.status(401).json({ success: false, message: '인증이 필요합니다.' }); return; } const { title, description, elements, isPublic = false, tags, category }: CreateDashboardRequest = req.body; // 유효성 검증 if (!title || title.trim().length === 0) { res.status(400).json({ success: false, message: '대시보드 제목이 필요합니다.' }); return; } if (!elements || !Array.isArray(elements)) { res.status(400).json({ success: false, message: '대시보드 요소 데이터가 필요합니다.' }); return; } // 제목 길이 체크 if (title.length > 200) { res.status(400).json({ success: false, message: '제목은 200자를 초과할 수 없습니다.' }); return; } // 설명 길이 체크 if (description && description.length > 1000) { res.status(400).json({ success: false, message: '설명은 1000자를 초과할 수 없습니다.' }); return; } const dashboardData: CreateDashboardRequest = { title: title.trim(), description: description?.trim(), isPublic, elements, tags, category }; // console.log('대시보드 생성 시작:', { title: dashboardData.title, userId, elementsCount: elements.length }); const savedDashboard = await DashboardService.createDashboard(dashboardData, userId); // console.log('대시보드 생성 성공:', { id: savedDashboard.id, title: savedDashboard.title }); res.status(201).json({ success: true, data: savedDashboard, message: '대시보드가 성공적으로 생성되었습니다.' }); } catch (error: any) { // console.error('Dashboard creation error:', { // message: error?.message, // stack: error?.stack, // error // }); res.status(500).json({ success: false, message: error?.message || '대시보드 생성 중 오류가 발생했습니다.', error: process.env.NODE_ENV === 'development' ? error?.message : undefined }); } } /** * 대시보드 목록 조회 * GET /api/dashboards */ async getDashboards(req: AuthenticatedRequest, res: Response): Promise { try { const userId = req.user?.userId; const query: DashboardListQuery = { page: parseInt(req.query.page as string) || 1, limit: Math.min(parseInt(req.query.limit as string) || 20, 100), // 최대 100개 search: req.query.search as string, category: req.query.category as string, isPublic: req.query.isPublic === 'true' ? true : req.query.isPublic === 'false' ? false : undefined, createdBy: req.query.createdBy as string }; // 페이지 번호 유효성 검증 if (query.page! < 1) { res.status(400).json({ success: false, message: '페이지 번호는 1 이상이어야 합니다.' }); return; } const result = await DashboardService.getDashboards(query, userId); res.json({ success: true, data: result.dashboards, pagination: result.pagination }); } catch (error) { // console.error('Dashboard list error:', error); res.status(500).json({ success: false, message: '대시보드 목록 조회 중 오류가 발생했습니다.', error: process.env.NODE_ENV === 'development' ? (error as Error).message : undefined }); } } /** * 대시보드 상세 조회 * GET /api/dashboards/:id */ async getDashboard(req: AuthenticatedRequest, res: Response): Promise { try { const { id } = req.params; const userId = req.user?.userId; if (!id) { res.status(400).json({ success: false, message: '대시보드 ID가 필요합니다.' }); return; } const dashboard = await DashboardService.getDashboardById(id, userId); if (!dashboard) { res.status(404).json({ success: false, message: '대시보드를 찾을 수 없거나 접근 권한이 없습니다.' }); return; } // 조회수 증가 (본인이 만든 대시보드가 아닌 경우에만) if (userId && dashboard.createdBy !== userId) { await DashboardService.incrementViewCount(id); } res.json({ success: true, data: dashboard }); } catch (error) { // console.error('Dashboard get error:', error); res.status(500).json({ success: false, message: '대시보드 조회 중 오류가 발생했습니다.', error: process.env.NODE_ENV === 'development' ? (error as Error).message : undefined }); } } /** * 대시보드 수정 * PUT /api/dashboards/:id */ async updateDashboard(req: AuthenticatedRequest, res: Response): Promise { try { const { id } = req.params; const userId = req.user?.userId; if (!userId) { res.status(401).json({ success: false, message: '인증이 필요합니다.' }); return; } if (!id) { res.status(400).json({ success: false, message: '대시보드 ID가 필요합니다.' }); return; } const updateData: UpdateDashboardRequest = req.body; // 유효성 검증 if (updateData.title !== undefined) { if (typeof updateData.title !== 'string' || updateData.title.trim().length === 0) { res.status(400).json({ success: false, message: '올바른 제목을 입력해주세요.' }); return; } if (updateData.title.length > 200) { res.status(400).json({ success: false, message: '제목은 200자를 초과할 수 없습니다.' }); return; } updateData.title = updateData.title.trim(); } if (updateData.description !== undefined && updateData.description && updateData.description.length > 1000) { res.status(400).json({ success: false, message: '설명은 1000자를 초과할 수 없습니다.' }); return; } const updatedDashboard = await DashboardService.updateDashboard(id, updateData, userId); if (!updatedDashboard) { res.status(404).json({ success: false, message: '대시보드를 찾을 수 없거나 수정 권한이 없습니다.' }); return; } res.json({ success: true, data: updatedDashboard, message: '대시보드가 성공적으로 수정되었습니다.' }); } catch (error) { // console.error('Dashboard update error:', error); if ((error as Error).message.includes('권한이 없습니다')) { res.status(403).json({ success: false, message: (error as Error).message }); return; } res.status(500).json({ success: false, message: '대시보드 수정 중 오류가 발생했습니다.', error: process.env.NODE_ENV === 'development' ? (error as Error).message : undefined }); } } /** * 대시보드 삭제 * DELETE /api/dashboards/:id */ async deleteDashboard(req: AuthenticatedRequest, res: Response): Promise { try { const { id } = req.params; const userId = req.user?.userId; if (!userId) { res.status(401).json({ success: false, message: '인증이 필요합니다.' }); return; } if (!id) { res.status(400).json({ success: false, message: '대시보드 ID가 필요합니다.' }); return; } const deleted = await DashboardService.deleteDashboard(id, userId); if (!deleted) { res.status(404).json({ success: false, message: '대시보드를 찾을 수 없거나 삭제 권한이 없습니다.' }); return; } res.json({ success: true, message: '대시보드가 성공적으로 삭제되었습니다.' }); } catch (error) { // console.error('Dashboard delete error:', error); res.status(500).json({ success: false, message: '대시보드 삭제 중 오류가 발생했습니다.', error: process.env.NODE_ENV === 'development' ? (error as Error).message : undefined }); } } /** * 내 대시보드 목록 조회 * GET /api/dashboards/my */ async getMyDashboards(req: AuthenticatedRequest, res: Response): Promise { try { const userId = req.user?.userId; if (!userId) { res.status(401).json({ success: false, message: '인증이 필요합니다.' }); return; } const query: DashboardListQuery = { page: parseInt(req.query.page as string) || 1, limit: Math.min(parseInt(req.query.limit as string) || 20, 100), search: req.query.search as string, category: req.query.category as string, createdBy: userId // 본인이 만든 대시보드만 }; const result = await DashboardService.getDashboards(query, userId); res.json({ success: true, data: result.dashboards, pagination: result.pagination }); } catch (error) { // console.error('My dashboards error:', error); res.status(500).json({ success: false, message: '내 대시보드 목록 조회 중 오류가 발생했습니다.', error: process.env.NODE_ENV === 'development' ? (error as Error).message : undefined }); } } /** * 쿼리 실행 * POST /api/dashboards/execute-query */ async executeQuery(req: AuthenticatedRequest, res: Response): Promise { try { // 개발용으로 인증 체크 제거 // const userId = req.user?.userId; // if (!userId) { // res.status(401).json({ // success: false, // message: '인증이 필요합니다.' // }); // return; // } const { query } = req.body; // 유효성 검증 if (!query || typeof query !== 'string' || query.trim().length === 0) { res.status(400).json({ success: false, message: '쿼리가 필요합니다.' }); return; } // SQL 인젝션 방지를 위한 기본적인 검증 const trimmedQuery = query.trim().toLowerCase(); if (!trimmedQuery.startsWith('select')) { res.status(400).json({ success: false, message: 'SELECT 쿼리만 허용됩니다.' }); return; } // 쿼리 실행 const result = await PostgreSQLService.query(query.trim()); // 결과 변환 const columns = result.fields?.map(field => field.name) || []; const rows = result.rows || []; res.status(200).json({ success: true, data: { columns, rows, rowCount: rows.length }, message: '쿼리가 성공적으로 실행되었습니다.' }); } catch (error) { // console.error('Query execution error:', error); res.status(500).json({ success: false, message: '쿼리 실행 중 오류가 발생했습니다.', error: process.env.NODE_ENV === 'development' ? (error as Error).message : '쿼리 실행 오류' }); } } }