From b3907ec48f5858cada85439a89fa219f5e71f1f2 Mon Sep 17 00:00:00 2001 From: caadiq Date: Tue, 21 Apr 2026 20:31:43 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B3=B5=EC=9A=A9=20=EC=83=81=EC=88=98=20backe?= =?UTF-8?q?nd/constants.js=EB=A1=9C=20=EC=B6=94=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DIFFICULTIES(난이도 enum), PARTY_SIZE(1~6), SYMBOL_MASTER_LEVEL(2~99), UPLOAD_FILE_SIZE_LIMIT(10MB)를 backend/constants.js로 추출하고 admin.js, boss-crystal.js, symbol.js, BossDifficulty 모델에서 참조. Co-Authored-By: Claude Opus 4.7 (1M context) --- backend/constants.js | 11 +++++++++++ backend/models/boss-crystal/BossDifficulty.js | 3 ++- backend/routes/admin.js | 3 ++- backend/routes/admin/boss-crystal.js | 10 ++++++---- backend/routes/admin/symbol.js | 7 +++++-- 5 files changed, 26 insertions(+), 8 deletions(-) create mode 100644 backend/constants.js diff --git a/backend/constants.js b/backend/constants.js new file mode 100644 index 0000000..722f48e --- /dev/null +++ b/backend/constants.js @@ -0,0 +1,11 @@ +// 난이도 ENUM — 모델과 validation 에서 공용 +export const DIFFICULTIES = ['easy', 'normal', 'hard', 'chaos', 'extreme']; + +// 파티 인원수 범위 +export const PARTY_SIZE = { min: 1, max: 6 }; + +// 심볼 마스터 레벨 범위 +export const SYMBOL_MASTER_LEVEL = { min: 2, max: 99 }; + +// 업로드 파일 크기 상한 (10MB) +export const UPLOAD_FILE_SIZE_LIMIT = 10 * 1024 * 1024; diff --git a/backend/models/boss-crystal/BossDifficulty.js b/backend/models/boss-crystal/BossDifficulty.js index ccc9be5..794a43e 100644 --- a/backend/models/boss-crystal/BossDifficulty.js +++ b/backend/models/boss-crystal/BossDifficulty.js @@ -1,11 +1,12 @@ import { DataTypes } from 'sequelize'; import { sequelize } from '../../lib/db.js'; +import { DIFFICULTIES } from '../../constants.js'; export const BossCrystalBossDifficulty = sequelize.define('BossCrystalBossDifficulty', { id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true }, boss_id: { type: DataTypes.INTEGER, allowNull: false }, difficulty: { - type: DataTypes.ENUM('easy', 'normal', 'hard', 'chaos', 'extreme'), + type: DataTypes.ENUM(...DIFFICULTIES), allowNull: false, }, crystal_price: { type: DataTypes.BIGINT, allowNull: false }, diff --git a/backend/routes/admin.js b/backend/routes/admin.js index 52de130..047f6d2 100644 --- a/backend/routes/admin.js +++ b/backend/routes/admin.js @@ -6,11 +6,12 @@ import { getPublicUrl } from '../lib/s3.js'; import { sequelize } from '../lib/db.js'; import bossCrystalRouter from './admin/boss-crystal.js'; import symbolRouter from './admin/symbol.js'; +import { UPLOAD_FILE_SIZE_LIMIT } from '../constants.js'; const router = Router(); const upload = multer({ storage: multer.memoryStorage(), - limits: { fileSize: 10 * 1024 * 1024 }, // 10MB + limits: { fileSize: UPLOAD_FILE_SIZE_LIMIT }, }); // 관리자 인증 미들웨어 diff --git a/backend/routes/admin/boss-crystal.js b/backend/routes/admin/boss-crystal.js index ea10f50..3c4764c 100644 --- a/backend/routes/admin/boss-crystal.js +++ b/backend/routes/admin/boss-crystal.js @@ -4,11 +4,12 @@ import { BossCrystalBoss, BossCrystalBossDifficulty } from '../../models/index.j import { uploadBossImage, deleteBossImage } from '../../services/boss-crystal/image.js'; import { getPublicUrl } from '../../lib/s3.js'; import { sequelize } from '../../lib/db.js'; +import { UPLOAD_FILE_SIZE_LIMIT, PARTY_SIZE, DIFFICULTIES } from '../../constants.js'; const router = Router(); const upload = multer({ storage: multer.memoryStorage(), - limits: { fileSize: 10 * 1024 * 1024 }, + limits: { fileSize: UPLOAD_FILE_SIZE_LIMIT }, }); function serialize(boss) { @@ -39,9 +40,8 @@ function parseDifficulties(raw) { if (!Array.isArray(arr) || arr.length === 0) { throw new Error('하나 이상의 난이도가 필요합니다'); } - const valid = ['easy', 'normal', 'hard', 'chaos', 'extreme']; return arr.map((d) => { - if (!valid.includes(d.difficulty)) throw new Error(`잘못된 난이도: ${d.difficulty}`); + if (!DIFFICULTIES.includes(d.difficulty)) throw new Error(`잘못된 난이도: ${d.difficulty}`); const price = Number(d.crystal_price); if (isNaN(price) || price <= 0) throw new Error(`잘못된 가격: ${d.difficulty}`); return { difficulty: d.difficulty, crystal_price: price }; @@ -50,7 +50,9 @@ function parseDifficulties(raw) { function parseMaxParty(raw) { const n = Number(raw); - if (isNaN(n) || n < 1 || n > 6) throw new Error('인원수는 1~6이어야 합니다'); + if (isNaN(n) || n < PARTY_SIZE.min || n > PARTY_SIZE.max) { + throw new Error(`인원수는 ${PARTY_SIZE.min}~${PARTY_SIZE.max}이어야 합니다`); + } return n; } diff --git a/backend/routes/admin/symbol.js b/backend/routes/admin/symbol.js index 8d79217..bac5273 100644 --- a/backend/routes/admin/symbol.js +++ b/backend/routes/admin/symbol.js @@ -4,11 +4,12 @@ import { Symbol, SymbolLevel } from '../../models/index.js'; import { convertAndUploadTo, deleteFromS3 } from '../../services/image.js'; import { getPublicUrl } from '../../lib/s3.js'; import { sequelize } from '../../lib/db.js'; +import { UPLOAD_FILE_SIZE_LIMIT, SYMBOL_MASTER_LEVEL } from '../../constants.js'; const router = Router(); const upload = multer({ storage: multer.memoryStorage(), - limits: { fileSize: 10 * 1024 * 1024 }, + limits: { fileSize: UPLOAD_FILE_SIZE_LIMIT }, }); const VALID_TYPES = ['아케인', '어센틱', '그랜드 어센틱']; @@ -70,7 +71,9 @@ function validateBasic({ type, region, max_level }) { const r = String(region || '').trim(); if (!r) throw new Error('지역 이름을 입력해주세요'); const ml = Number(max_level); - if (!ml || ml < 2 || ml > 99) throw new Error('만렙은 2~99 사이여야 합니다'); + if (!ml || ml < SYMBOL_MASTER_LEVEL.min || ml > SYMBOL_MASTER_LEVEL.max) { + throw new Error(`만렙은 ${SYMBOL_MASTER_LEVEL.min}~${SYMBOL_MASTER_LEVEL.max} 사이여야 합니다`); + } return { type, region: r, max_level: ml }; }