에러 유틸리티 함수를 모든 라우트에 적용
utils/error.js에 정의된 헬퍼 함수들(badRequest, unauthorized, notFound, conflict, serverError)을 전체 라우트 파일에 적용하여 에러 응답 처리 일관성 확보 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
897bdc471c
commit
1c9b30b783
12 changed files with 78 additions and 96 deletions
|
|
@ -1,6 +1,7 @@
|
|||
import bots from '../../config/bots.js';
|
||||
import { errorResponse } from '../../schemas/index.js';
|
||||
import { syncAllSchedules } from '../../services/meilisearch/index.js';
|
||||
import { badRequest, notFound, serverError } from '../../utils/error.js';
|
||||
|
||||
// 봇 관련 스키마
|
||||
const botResponse = {
|
||||
|
|
@ -113,7 +114,7 @@ export default async function botsRoutes(fastify) {
|
|||
await scheduler.startBot(id);
|
||||
return { success: true, message: '봇이 시작되었습니다.' };
|
||||
} catch (err) {
|
||||
return reply.code(400).send({ error: err.message });
|
||||
return badRequest(reply, err.message);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -147,7 +148,7 @@ export default async function botsRoutes(fastify) {
|
|||
await scheduler.stopBot(id);
|
||||
return { success: true, message: '봇이 정지되었습니다.' };
|
||||
} catch (err) {
|
||||
return reply.code(400).send({ error: err.message });
|
||||
return badRequest(reply, err.message);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -182,7 +183,7 @@ export default async function botsRoutes(fastify) {
|
|||
|
||||
const bot = bots.find(b => b.id === id);
|
||||
if (!bot) {
|
||||
return reply.code(404).send({ error: '봇을 찾을 수 없습니다.' });
|
||||
return notFound(reply, '봇을 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -195,7 +196,7 @@ export default async function botsRoutes(fastify) {
|
|||
const count = await syncAllSchedules(fastify.meilisearch, fastify.db);
|
||||
result = { addedCount: count, total: count };
|
||||
} else {
|
||||
return reply.code(400).send({ error: '지원하지 않는 봇 타입입니다.' });
|
||||
return badRequest(reply, '지원하지 않는 봇 타입입니다.');
|
||||
}
|
||||
|
||||
// 상태 업데이트
|
||||
|
|
@ -215,7 +216,7 @@ export default async function botsRoutes(fastify) {
|
|||
};
|
||||
} catch (err) {
|
||||
fastify.log.error(`[${id}] 전체 동기화 오류:`, err);
|
||||
return reply.code(500).send({ error: err.message });
|
||||
return serverError(reply, err.message);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
xPostInfoQuery,
|
||||
xScheduleCreate,
|
||||
} from '../../schemas/index.js';
|
||||
import { badRequest, conflict, serverError } from '../../utils/error.js';
|
||||
|
||||
const X_CATEGORY_ID = CATEGORY_IDS.X;
|
||||
const NITTER_URL = config.nitter?.url || process.env.NITTER_URL || 'http://nitter:8080';
|
||||
|
|
@ -61,7 +62,7 @@ export default async function xRoutes(fastify) {
|
|||
|
||||
// 게시글 ID 유효성 검사
|
||||
if (!/^\d+$/.test(postId)) {
|
||||
return reply.code(400).send({ error: '유효하지 않은 게시글 ID입니다.' });
|
||||
return badRequest(reply, '유효하지 않은 게시글 ID입니다.');
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -80,7 +81,7 @@ export default async function xRoutes(fastify) {
|
|||
};
|
||||
} catch (err) {
|
||||
fastify.log.error(`X 게시글 조회 오류: ${err.message}`);
|
||||
return reply.code(500).send({ error: err.message });
|
||||
return serverError(reply, err.message);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -118,7 +119,7 @@ export default async function xRoutes(fastify) {
|
|||
[postId]
|
||||
);
|
||||
if (existing.length > 0) {
|
||||
return reply.code(409).send({ error: '이미 등록된 게시글입니다.' });
|
||||
return conflict(reply, '이미 등록된 게시글입니다.');
|
||||
}
|
||||
|
||||
// schedules 테이블에 저장
|
||||
|
|
@ -155,7 +156,7 @@ export default async function xRoutes(fastify) {
|
|||
return { success: true, scheduleId };
|
||||
} catch (err) {
|
||||
fastify.log.error(`X 일정 저장 오류: ${err.message}`);
|
||||
return reply.code(500).send({ error: err.message });
|
||||
return serverError(reply, err.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import {
|
|||
youtubeScheduleUpdate,
|
||||
idParam,
|
||||
} from '../../schemas/index.js';
|
||||
import { badRequest, notFound, conflict, serverError } from '../../utils/error.js';
|
||||
|
||||
const YOUTUBE_CATEGORY_ID = CATEGORY_IDS.YOUTUBE;
|
||||
|
||||
|
|
@ -54,13 +55,13 @@ export default async function youtubeRoutes(fastify) {
|
|||
// YouTube URL에서 video ID 추출
|
||||
const videoId = extractVideoId(url);
|
||||
if (!videoId) {
|
||||
return reply.code(400).send({ error: '유효하지 않은 YouTube URL입니다.' });
|
||||
return badRequest(reply, '유효하지 않은 YouTube URL입니다.');
|
||||
}
|
||||
|
||||
try {
|
||||
const video = await fetchVideoInfo(videoId);
|
||||
if (!video) {
|
||||
return reply.code(404).send({ error: '영상을 찾을 수 없습니다.' });
|
||||
return notFound(reply, '영상을 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -76,7 +77,7 @@ export default async function youtubeRoutes(fastify) {
|
|||
};
|
||||
} catch (err) {
|
||||
fastify.log.error(`YouTube 영상 조회 오류: ${err.message}`);
|
||||
return reply.code(500).send({ error: err.message });
|
||||
return serverError(reply, err.message);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -114,7 +115,7 @@ export default async function youtubeRoutes(fastify) {
|
|||
[videoId]
|
||||
);
|
||||
if (existing.length > 0) {
|
||||
return reply.code(409).send({ error: '이미 등록된 영상입니다.' });
|
||||
return conflict(reply, '이미 등록된 영상입니다.');
|
||||
}
|
||||
|
||||
// schedules 테이블에 저장
|
||||
|
|
@ -151,7 +152,7 @@ export default async function youtubeRoutes(fastify) {
|
|||
return { success: true, scheduleId };
|
||||
} catch (err) {
|
||||
fastify.log.error(`YouTube 일정 저장 오류: ${err.message}`);
|
||||
return reply.code(500).send({ error: err.message });
|
||||
return serverError(reply, err.message);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -190,7 +191,7 @@ export default async function youtubeRoutes(fastify) {
|
|||
[id, YOUTUBE_CATEGORY_ID]
|
||||
);
|
||||
if (schedules.length === 0) {
|
||||
return reply.code(404).send({ error: 'YouTube 일정을 찾을 수 없습니다.' });
|
||||
return notFound(reply, 'YouTube 일정을 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
// 영상 유형 수정
|
||||
|
|
@ -254,7 +255,7 @@ export default async function youtubeRoutes(fastify) {
|
|||
return { success: true };
|
||||
} catch (err) {
|
||||
fastify.log.error(`YouTube 일정 수정 오류: ${err.message}`);
|
||||
return reply.code(500).send({ error: err.message });
|
||||
return serverError(reply, err.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
import photosRoutes from './photos.js';
|
||||
import teasersRoutes from './teasers.js';
|
||||
import { errorResponse, successResponse, idParam } from '../../schemas/index.js';
|
||||
import { notFound, badRequest } from '../../utils/error.js';
|
||||
|
||||
/**
|
||||
* 앨범 라우트
|
||||
|
|
@ -67,7 +68,7 @@ export default async function albumsRoutes(fastify) {
|
|||
|
||||
const album = await getAlbumByName(db, albumName);
|
||||
if (!album) {
|
||||
return reply.code(404).send({ error: '앨범을 찾을 수 없습니다.' });
|
||||
return notFound(reply, '앨범을 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
const [tracks] = await db.query(
|
||||
|
|
@ -76,7 +77,7 @@ export default async function albumsRoutes(fastify) {
|
|||
);
|
||||
|
||||
if (tracks.length === 0) {
|
||||
return reply.code(404).send({ error: '트랙을 찾을 수 없습니다.' });
|
||||
return notFound(reply, '트랙을 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
const track = tracks[0];
|
||||
|
|
@ -123,7 +124,7 @@ export default async function albumsRoutes(fastify) {
|
|||
}, async (request, reply) => {
|
||||
const album = await getAlbumByName(db, decodeURIComponent(request.params.name));
|
||||
if (!album) {
|
||||
return reply.code(404).send({ error: '앨범을 찾을 수 없습니다.' });
|
||||
return notFound(reply, '앨범을 찾을 수 없습니다.');
|
||||
}
|
||||
return getAlbumDetails(db, album, redis);
|
||||
});
|
||||
|
|
@ -144,7 +145,7 @@ export default async function albumsRoutes(fastify) {
|
|||
}, async (request, reply) => {
|
||||
const album = await getAlbumById(db, request.params.id);
|
||||
if (!album) {
|
||||
return reply.code(404).send({ error: '앨범을 찾을 수 없습니다.' });
|
||||
return notFound(reply, '앨범을 찾을 수 없습니다.');
|
||||
}
|
||||
return getAlbumDetails(db, album, redis);
|
||||
});
|
||||
|
|
@ -187,13 +188,13 @@ export default async function albumsRoutes(fastify) {
|
|||
}
|
||||
|
||||
if (!data) {
|
||||
return reply.code(400).send({ error: '데이터가 필요합니다.' });
|
||||
return badRequest(reply, '데이터가 필요합니다.');
|
||||
}
|
||||
|
||||
const { title, album_type, release_date, folder_name } = data;
|
||||
|
||||
if (!title || !album_type || !release_date || !folder_name) {
|
||||
return reply.code(400).send({ error: '필수 필드를 모두 입력해주세요.' });
|
||||
return badRequest(reply, '필수 필드를 모두 입력해주세요.');
|
||||
}
|
||||
|
||||
const result = await createAlbum(db, data, coverBuffer);
|
||||
|
|
@ -234,12 +235,12 @@ export default async function albumsRoutes(fastify) {
|
|||
}
|
||||
|
||||
if (!data) {
|
||||
return reply.code(400).send({ error: '데이터가 필요합니다.' });
|
||||
return badRequest(reply, '데이터가 필요합니다.');
|
||||
}
|
||||
|
||||
const result = await updateAlbum(db, id, data, coverBuffer);
|
||||
if (!result) {
|
||||
return reply.code(404).send({ error: '앨범을 찾을 수 없습니다.' });
|
||||
return notFound(reply, '앨범을 찾을 수 없습니다.');
|
||||
}
|
||||
await invalidateAlbumCache(redis, id);
|
||||
return result;
|
||||
|
|
@ -265,7 +266,7 @@ export default async function albumsRoutes(fastify) {
|
|||
const { id } = request.params;
|
||||
const result = await deleteAlbum(db, id);
|
||||
if (!result) {
|
||||
return reply.code(404).send({ error: '앨범을 찾을 수 없습니다.' });
|
||||
return notFound(reply, '앨범을 찾을 수 없습니다.');
|
||||
}
|
||||
await invalidateAlbumCache(redis, id);
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {
|
|||
uploadAlbumVideo,
|
||||
} from '../../services/image.js';
|
||||
import { withTransaction } from '../../utils/transaction.js';
|
||||
import { notFound } from '../../utils/error.js';
|
||||
|
||||
/**
|
||||
* 앨범 사진 라우트
|
||||
|
|
@ -25,7 +26,7 @@ export default async function photosRoutes(fastify) {
|
|||
|
||||
const [albums] = await db.query('SELECT folder_name FROM albums WHERE id = ?', [albumId]);
|
||||
if (albums.length === 0) {
|
||||
return reply.code(404).send({ error: '앨범을 찾을 수 없습니다.' });
|
||||
return notFound(reply, '앨범을 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
const [photos] = await db.query(
|
||||
|
|
@ -227,7 +228,7 @@ export default async function photosRoutes(fastify) {
|
|||
);
|
||||
|
||||
if (photos.length === 0) {
|
||||
return reply.code(404).send({ error: '사진을 찾을 수 없습니다.' });
|
||||
return notFound(reply, '사진을 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
const photo = photos[0];
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
deleteAlbumVideo,
|
||||
} from '../../services/image.js';
|
||||
import { withTransaction } from '../../utils/transaction.js';
|
||||
import { notFound } from '../../utils/error.js';
|
||||
|
||||
/**
|
||||
* 앨범 티저 라우트
|
||||
|
|
@ -24,7 +25,7 @@ export default async function teasersRoutes(fastify) {
|
|||
|
||||
const [albums] = await db.query('SELECT folder_name FROM albums WHERE id = ?', [albumId]);
|
||||
if (albums.length === 0) {
|
||||
return reply.code(404).send({ error: '앨범을 찾을 수 없습니다.' });
|
||||
return notFound(reply, '앨범을 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
const [teasers] = await db.query(
|
||||
|
|
@ -61,7 +62,7 @@ export default async function teasersRoutes(fastify) {
|
|||
);
|
||||
|
||||
if (teasers.length === 0) {
|
||||
return reply.code(404).send({ error: '티저를 찾을 수 없습니다.' });
|
||||
return notFound(reply, '티저를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
const teaser = teasers[0];
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import bcrypt from 'bcrypt';
|
||||
import { badRequest, unauthorized, serverError } from '../utils/error.js';
|
||||
|
||||
/**
|
||||
* 인증 라우트
|
||||
|
|
@ -42,7 +43,7 @@ export default async function authRoutes(fastify, opts) {
|
|||
const { username, password } = request.body || {};
|
||||
|
||||
if (!username || !password) {
|
||||
return reply.code(400).send({ error: '아이디와 비밀번호를 입력해주세요.' });
|
||||
return badRequest(reply, '아이디와 비밀번호를 입력해주세요.');
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -52,14 +53,14 @@ export default async function authRoutes(fastify, opts) {
|
|||
);
|
||||
|
||||
if (users.length === 0) {
|
||||
return reply.code(401).send({ error: '아이디 또는 비밀번호가 올바르지 않습니다.' });
|
||||
return unauthorized(reply, '아이디 또는 비밀번호가 올바르지 않습니다.');
|
||||
}
|
||||
|
||||
const user = users[0];
|
||||
const isValidPassword = await bcrypt.compare(password, user.password_hash);
|
||||
|
||||
if (!isValidPassword) {
|
||||
return reply.code(401).send({ error: '아이디 또는 비밀번호가 올바르지 않습니다.' });
|
||||
return unauthorized(reply, '아이디 또는 비밀번호가 올바르지 않습니다.');
|
||||
}
|
||||
|
||||
// JWT 토큰 생성
|
||||
|
|
@ -75,7 +76,7 @@ export default async function authRoutes(fastify, opts) {
|
|||
};
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: '로그인 처리 중 오류가 발생했습니다.' });
|
||||
return serverError(reply, '로그인 처리 중 오류가 발생했습니다.');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { uploadMemberImage } from '../../services/image.js';
|
||||
import { getAllMembers, getMemberByName, getMemberBasicByName, invalidateMemberCache } from '../../services/member.js';
|
||||
import { notFound, serverError } from '../../utils/error.js';
|
||||
|
||||
/**
|
||||
* 멤버 라우트
|
||||
|
|
@ -22,7 +23,7 @@ export default async function membersRoutes(fastify, opts) {
|
|||
return await getAllMembers(db, redis);
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: '멤버 목록 조회 실패' });
|
||||
return serverError(reply, '멤버 목록 조회 실패');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -45,12 +46,12 @@ export default async function membersRoutes(fastify, opts) {
|
|||
try {
|
||||
const member = await getMemberByName(db, decodeURIComponent(request.params.name));
|
||||
if (!member) {
|
||||
return reply.code(404).send({ error: '멤버를 찾을 수 없습니다' });
|
||||
return notFound(reply, '멤버를 찾을 수 없습니다');
|
||||
}
|
||||
return member;
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: '멤버 조회 실패' });
|
||||
return serverError(reply, '멤버 조회 실패');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -80,7 +81,7 @@ export default async function membersRoutes(fastify, opts) {
|
|||
// 기존 멤버 조회
|
||||
const existing = await getMemberBasicByName(db, decodedName);
|
||||
if (!existing) {
|
||||
return reply.code(404).send({ error: '멤버를 찾을 수 없습니다' });
|
||||
return notFound(reply, '멤버를 찾을 수 없습니다');
|
||||
}
|
||||
|
||||
const memberId = existing.id;
|
||||
|
|
@ -161,7 +162,7 @@ export default async function membersRoutes(fastify, opts) {
|
|||
return { message: '멤버 정보가 수정되었습니다', id: memberId };
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: '멤버 수정 실패: ' + err.message });
|
||||
return serverError(reply, '멤버 수정 실패: ' + err.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
scheduleSearchResponse,
|
||||
idParam,
|
||||
} from '../../schemas/index.js';
|
||||
import { badRequest, notFound, serverError } from '../../utils/error.js';
|
||||
|
||||
export default async function schedulesRoutes(fastify) {
|
||||
const { db, meilisearch, redis } = fastify;
|
||||
|
|
@ -42,7 +43,7 @@ export default async function schedulesRoutes(fastify) {
|
|||
return await getCategories(db, redis);
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: '카테고리 목록 조회 실패' });
|
||||
return serverError(reply, '카테고리 목록 조회 실패');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -82,13 +83,13 @@ export default async function schedulesRoutes(fastify) {
|
|||
|
||||
// 월별 조회 모드
|
||||
if (!year || !month) {
|
||||
return reply.code(400).send({ error: 'search, startDate, 또는 year/month는 필수입니다.' });
|
||||
return badRequest(reply, 'search, startDate, 또는 year/month는 필수입니다.');
|
||||
}
|
||||
|
||||
return await getMonthlySchedules(db, parseInt(year), parseInt(month));
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: '일정 조회 실패' });
|
||||
return serverError(reply, '일정 조회 실패');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -119,7 +120,7 @@ export default async function schedulesRoutes(fastify) {
|
|||
return { success: true, synced: count };
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: '동기화 실패' });
|
||||
return serverError(reply, '동기화 실패');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -146,13 +147,13 @@ export default async function schedulesRoutes(fastify) {
|
|||
);
|
||||
|
||||
if (!result) {
|
||||
return reply.code(404).send({ error: '일정을 찾을 수 없습니다.' });
|
||||
return notFound(reply, '일정을 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: '일정 상세 조회 실패' });
|
||||
return serverError(reply, '일정 상세 조회 실패');
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -185,7 +186,7 @@ export default async function schedulesRoutes(fastify) {
|
|||
// 일정 존재 확인
|
||||
const [existing] = await db.query('SELECT id FROM schedules WHERE id = ?', [id]);
|
||||
if (existing.length === 0) {
|
||||
return reply.code(404).send({ error: '일정을 찾을 수 없습니다.' });
|
||||
return notFound(reply, '일정을 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
// 관련 테이블 삭제 (외래 키)
|
||||
|
|
@ -208,7 +209,7 @@ export default async function schedulesRoutes(fastify) {
|
|||
return { success: true };
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: '일정 삭제 실패' });
|
||||
return serverError(reply, '일정 삭제 실패');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import { readFileSync, writeFileSync } from 'fs';
|
||||
import { SuggestionService } from '../../services/suggestions/index.js';
|
||||
import { reloadMorpheme, getUserDictPath } from '../../services/suggestions/morpheme.js';
|
||||
import { badRequest, serverError } from '../../utils/error.js';
|
||||
|
||||
let suggestionService = null;
|
||||
|
||||
|
|
@ -109,7 +110,7 @@ export default async function suggestionsRoutes(fastify) {
|
|||
const { query } = request.body;
|
||||
|
||||
if (!query || query.trim().length === 0) {
|
||||
return reply.code(400).send({ error: '검색어가 필요합니다.' });
|
||||
return badRequest(reply, '검색어가 필요합니다.');
|
||||
}
|
||||
|
||||
await suggestionService.saveSearchQuery(query);
|
||||
|
|
@ -187,7 +188,7 @@ export default async function suggestionsRoutes(fastify) {
|
|||
return { message: '사전이 저장되었습니다.' };
|
||||
} catch (error) {
|
||||
fastify.log.error(`[Suggestions] 사전 저장 오류: ${error.message}`);
|
||||
return reply.code(500).send({ error: '사전 저장 중 오류가 발생했습니다.' });
|
||||
return serverError(reply, '사전 저장 중 오류가 발생했습니다.');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
* 통계 라우트
|
||||
* 인증 필요
|
||||
*/
|
||||
import { serverError } from '../../utils/error.js';
|
||||
|
||||
export default async function statsRoutes(fastify, opts) {
|
||||
const { db } = fastify;
|
||||
|
||||
|
|
@ -70,7 +72,7 @@ export default async function statsRoutes(fastify, opts) {
|
|||
};
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
return reply.code(500).send({ error: '통계 조회 실패' });
|
||||
return serverError(reply, '통계 조회 실패');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,20 +25,12 @@
|
|||
|
||||
## 개선 필요 사항
|
||||
|
||||
### 1. 보안 (우선순위: 높음)
|
||||
### ~~1. 보안 (우선순위: 높음)~~ ✅ 완료
|
||||
|
||||
#### 1.1 JWT Secret 하드코딩
|
||||
**파일**: `backend/src/config/index.js:37`
|
||||
#### ~~1.1 JWT Secret 하드코딩~~ ✅ 완료
|
||||
**파일**: `backend/src/config/index.js`
|
||||
|
||||
```javascript
|
||||
// 현재 - 기본값 노출 위험
|
||||
secret: process.env.JWT_SECRET || 'fromis9-admin-secret-key-2026',
|
||||
|
||||
// 권장 - 환경변수 필수화
|
||||
secret: process.env.JWT_SECRET,
|
||||
```
|
||||
|
||||
서버 시작 시 JWT_SECRET 환경변수 존재 여부를 검증하는 로직 추가 필요.
|
||||
기본값을 제거하고 서버 시작 시 필수 환경변수 검증 로직을 추가함.
|
||||
|
||||
### 2. Docker 설정 (우선순위: 중간)
|
||||
|
||||
|
|
@ -69,17 +61,9 @@ meilisearch:
|
|||
|
||||
### 3. 백엔드 코드 (우선순위: 중간)
|
||||
|
||||
#### 3.1 에러 유틸리티 미활용
|
||||
`src/utils/error.js`에 에러 헬퍼가 있지만 라우트에서 직접 처리.
|
||||
|
||||
```javascript
|
||||
// 현재 - routes/albums/index.js
|
||||
return reply.code(404).send({ error: '앨범을 찾을 수 없습니다.' });
|
||||
|
||||
// 권장
|
||||
import { notFound } from '../../utils/error.js';
|
||||
return notFound(reply, '앨범을 찾을 수 없습니다.');
|
||||
```
|
||||
#### ~~3.1 에러 유틸리티 미활용~~ ✅ 완료
|
||||
모든 라우트 파일에 에러 유틸리티(`utils/error.js`) 적용 완료.
|
||||
- `badRequest`, `unauthorized`, `notFound`, `conflict`, `serverError` 함수 사용
|
||||
|
||||
#### 3.2 SELECT * 사용
|
||||
**파일**: `services/album.js:16-19`
|
||||
|
|
@ -92,24 +76,10 @@ return notFound(reply, '앨범을 찾을 수 없습니다.');
|
|||
'SELECT id, title, folder_name, album_type, album_type_short, release_date, cover_original_url, cover_medium_url, cover_thumb_url, description FROM albums WHERE folder_name = ? OR title = ?'
|
||||
```
|
||||
|
||||
#### 3.3 앨범 삭제 시 관련 데이터 누락
|
||||
**파일**: `services/album.js:301-312`
|
||||
#### ~~3.3 앨범 삭제 시 관련 데이터 누락~~ ✅ 완료
|
||||
**파일**: `services/album.js`
|
||||
|
||||
```javascript
|
||||
// 현재
|
||||
await connection.query('DELETE FROM album_tracks WHERE album_id = ?', [id]);
|
||||
await connection.query('DELETE FROM albums WHERE id = ?', [id]);
|
||||
|
||||
// 수정 필요 - 관련 테이블 모두 삭제
|
||||
await connection.query(
|
||||
'DELETE FROM album_photo_members WHERE photo_id IN (SELECT id FROM album_photos WHERE album_id = ?)',
|
||||
[id]
|
||||
);
|
||||
await connection.query('DELETE FROM album_photos WHERE album_id = ?', [id]);
|
||||
await connection.query('DELETE FROM album_teasers WHERE album_id = ?', [id]);
|
||||
await connection.query('DELETE FROM album_tracks WHERE album_id = ?', [id]);
|
||||
await connection.query('DELETE FROM albums WHERE id = ?', [id]);
|
||||
```
|
||||
앨범 삭제 시 관련 테이블(photos, teasers, photo_members) 및 S3 파일도 함께 삭제하도록 개선됨.
|
||||
|
||||
### 4. 프론트엔드 코드 (우선순위: 낮음)
|
||||
|
||||
|
|
@ -175,15 +145,15 @@ export const authDel = authApi.del;
|
|||
|
||||
## 개선 우선순위 요약
|
||||
|
||||
| 순위 | 항목 | 파일 | 난이도 |
|
||||
| 순위 | 항목 | 파일 | 상태 |
|
||||
|:----:|------|------|:------:|
|
||||
| 1 | JWT Secret 기본값 제거 | `config/index.js` | 낮음 |
|
||||
| 2 | 앨범 삭제 시 관련 테이블 정리 | `services/album.js` | 중간 |
|
||||
| 3 | 에러 유틸리티 통일 | 라우트 파일들 | 중간 |
|
||||
| 4 | App.jsx 라우트 분리 | `App.jsx` | 중간 |
|
||||
| 5 | 레거시 export 정리 | `api/client.js` | 낮음 |
|
||||
| 1 | ~~JWT Secret 기본값 제거~~ | `config/index.js` | ✅ 완료 |
|
||||
| 2 | ~~앨범 삭제 시 관련 테이블 정리~~ | `services/album.js` | ✅ 완료 |
|
||||
| 3 | ~~에러 유틸리티 통일~~ | 라우트 파일들 | ✅ 완료 |
|
||||
| 4 | App.jsx 라우트 분리 | `App.jsx` | 미완료 |
|
||||
| 5 | 레거시 export 정리 | `api/client.js` | 미완료 |
|
||||
| 6 | ~~Meilisearch 버전 업데이트~~ | `docker-compose.yml` | ✅ 완료 |
|
||||
| 7 | 테스트 코드 작성 | 전체 | 높음 |
|
||||
| 7 | 테스트 코드 작성 | 전체 | 미완료 |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue