diff --git a/.env b/.env
index dc00469..d620991 100644
--- a/.env
+++ b/.env
@@ -5,10 +5,6 @@ DB_USER=maplestory
DB_PASSWORD=xSMK3sG9DG9Vn2dQ
DB_NAME=maplestory
-# Redis (세션 저장)
-REDIS_HOST=redis
-REDIS_PORT=6379
-
# RustFS (S3 호환 스토리지)
S3_ENDPOINT=http://rustfs:9000
S3_PUBLIC_URL=https://s3.caadiq.co.kr
@@ -16,16 +12,8 @@ S3_ACCESS_KEY=
S3_SECRET_KEY=
S3_BUCKET=maplestory
-# 넥슨 OAuth
-NEXON_CLIENT_ID=
-NEXON_CLIENT_SECRET=
-NEXON_REDIRECT_URI=https://maple.caadiq.co.kr/api/auth/callback
-
-# 넥슨 API (캐릭터 상세 조회용)
-NEXON_API_KEY=
-
-# 세션
-SESSION_SECRET=
+# 넥슨 API
+NEXON_API_KEY=test_d32f00908105a5803bf0ce5cf717747c0f06152c00f907ea7f9bb68d3541d2b6efe8d04e6d233bd35cf2fabdeb93fb0d
# 앱
NODE_ENV=development
diff --git a/backend/lib/redis.js b/backend/lib/redis.js
deleted file mode 100644
index b113099..0000000
--- a/backend/lib/redis.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import Redis from 'ioredis';
-
-export const redis = new Redis({
- host: process.env.REDIS_HOST || 'redis',
- port: parseInt(process.env.REDIS_PORT || '6379'),
-});
diff --git a/backend/middleware/auth.js b/backend/middleware/auth.js
deleted file mode 100644
index ac81d9c..0000000
--- a/backend/middleware/auth.js
+++ /dev/null
@@ -1,6 +0,0 @@
-export function requireAuth(req, res, next) {
- if (!req.session?.userId) {
- return res.status(401).json({ error: '로그인이 필요합니다' });
- }
- next();
-}
diff --git a/backend/middleware/session.js b/backend/middleware/session.js
deleted file mode 100644
index 46d07dc..0000000
--- a/backend/middleware/session.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import session from 'express-session';
-import { RedisStore } from 'connect-redis';
-import { redis } from '../lib/redis.js';
-
-export const sessionMiddleware = session({
- store: new RedisStore({ client: redis, prefix: 'maple:sess:' }),
- secret: process.env.SESSION_SECRET || 'dev-secret',
- resave: false,
- saveUninitialized: false,
- cookie: {
- secure: process.env.NODE_ENV === 'production',
- httpOnly: true,
- maxAge: 14 * 24 * 60 * 60 * 1000, // 14일
- sameSite: 'lax',
- },
-});
diff --git a/backend/models/User.js b/backend/models/User.js
deleted file mode 100644
index 633b72e..0000000
--- a/backend/models/User.js
+++ /dev/null
@@ -1,10 +0,0 @@
-import { DataTypes } from 'sequelize';
-import { sequelize } from '../lib/db.js';
-
-export const User = sequelize.define('User', {
- id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
- nexon_uid: { type: DataTypes.STRING(50), allowNull: false, unique: true },
-}, {
- tableName: 'users',
- underscored: true,
-});
diff --git a/backend/models/UserCharacter.js b/backend/models/UserCharacter.js
deleted file mode 100644
index ab94701..0000000
--- a/backend/models/UserCharacter.js
+++ /dev/null
@@ -1,19 +0,0 @@
-import { DataTypes } from 'sequelize';
-import { sequelize } from '../lib/db.js';
-
-export const UserCharacter = sequelize.define('UserCharacter', {
- id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
- user_id: { type: DataTypes.INTEGER, allowNull: false },
- character_name: { type: DataTypes.STRING(50), allowNull: false },
- ocid: { type: DataTypes.STRING(100) },
- world_name: { type: DataTypes.STRING(20) },
- job_name: { type: DataTypes.STRING(50) },
- character_level: { type: DataTypes.INTEGER },
- character_image: { type: DataTypes.STRING(255) },
-}, {
- tableName: 'user_characters',
- underscored: true,
- indexes: [
- { unique: true, fields: ['user_id', 'character_name'] },
- ],
-});
diff --git a/backend/models/boss/UserBossSelection.js b/backend/models/boss/UserBossSelection.js
deleted file mode 100644
index 352b3c1..0000000
--- a/backend/models/boss/UserBossSelection.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import { DataTypes } from 'sequelize';
-import { sequelize } from '../../lib/db.js';
-
-export const UserBossSelection = sequelize.define('UserBossSelection', {
- id: { type: DataTypes.INTEGER, autoIncrement: true, primaryKey: true },
- user_id: { type: DataTypes.INTEGER, allowNull: false },
- user_character_id: { type: DataTypes.INTEGER, allowNull: false },
- boss_difficulty_id: { type: DataTypes.INTEGER, allowNull: false },
- party_size: { type: DataTypes.TINYINT, allowNull: false, defaultValue: 1 },
-}, {
- tableName: 'user_boss_selections',
- underscored: true,
- timestamps: false,
- indexes: [
- { unique: true, fields: ['user_character_id', 'boss_difficulty_id'] },
- ],
-});
diff --git a/backend/models/index.js b/backend/models/index.js
index 7dd5b9e..055d298 100644
--- a/backend/models/index.js
+++ b/backend/models/index.js
@@ -1,21 +1,8 @@
-import { User } from './User.js';
-import { UserCharacter } from './UserCharacter.js';
import { Boss } from './boss/Boss.js';
import { BossDifficulty } from './boss/BossDifficulty.js';
-import { UserBossSelection } from './boss/UserBossSelection.js';
-
-// User <-> UserCharacter
-User.hasMany(UserCharacter, { foreignKey: 'user_id', as: 'characters' });
-UserCharacter.belongsTo(User, { foreignKey: 'user_id' });
// Boss <-> BossDifficulty
Boss.hasMany(BossDifficulty, { foreignKey: 'boss_id', as: 'difficulties' });
BossDifficulty.belongsTo(Boss, { foreignKey: 'boss_id' });
-// UserBossSelection 관계
-UserBossSelection.belongsTo(User, { foreignKey: 'user_id' });
-UserBossSelection.belongsTo(UserCharacter, { foreignKey: 'user_character_id', as: 'character' });
-UserBossSelection.belongsTo(BossDifficulty, { foreignKey: 'boss_difficulty_id', as: 'difficulty' });
-UserCharacter.hasMany(UserBossSelection, { foreignKey: 'user_character_id', as: 'selections' });
-
-export { User, UserCharacter, Boss, BossDifficulty, UserBossSelection };
+export { Boss, BossDifficulty };
diff --git a/backend/package-lock.json b/backend/package-lock.json
index a099360..43180cd 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -10,12 +10,8 @@
"dependencies": {
"@aws-sdk/client-s3": "^3.800.0",
"axios": "^1.9.0",
- "connect-redis": "^8.0.1",
"cors": "^2.8.5",
- "crypto": "^1.0.1",
"express": "^5.1.0",
- "express-session": "^1.18.1",
- "ioredis": "^5.6.1",
"mariadb": "^3.4.0",
"mysql2": "^3.14.1",
"sequelize": "^6.37.5"
@@ -872,12 +868,6 @@
"node": ">=18.0.0"
}
},
- "node_modules/@ioredis/commands": {
- "version": "1.5.1",
- "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.5.1.tgz",
- "integrity": "sha512-JH8ZL/ywcJyR9MmJ5BNqZllXNZQqQbnVZOqpPQqE1vHiFgAw4NHbvE0FOduNU8IX9babitBT46571OnPTT0Zcw==",
- "license": "MIT"
- },
"node_modules/@smithy/chunked-blob-reader": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-5.2.2.tgz",
@@ -1740,15 +1730,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/cluster-key-slot": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
- "integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
- "license": "Apache-2.0",
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -1761,18 +1742,6 @@
"node": ">= 0.8"
}
},
- "node_modules/connect-redis": {
- "version": "8.1.0",
- "resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-8.1.0.tgz",
- "integrity": "sha512-Km0EYLDlmExF52UCss5gLGTtrukGC57G6WCC2aqEMft5Vr4xNWuM4tL+T97kWrw+vp40SXFteb6Xk/7MxgpwdA==",
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "peerDependencies": {
- "express-session": ">=1"
- }
- },
"node_modules/content-disposition": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
@@ -1830,13 +1799,6 @@
"url": "https://opencollective.com/express"
}
},
- "node_modules/crypto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz",
- "integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==",
- "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.",
- "license": "ISC"
- },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -2020,50 +1982,6 @@
"url": "https://opencollective.com/express"
}
},
- "node_modules/express-session": {
- "version": "1.19.0",
- "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.19.0.tgz",
- "integrity": "sha512-0csaMkGq+vaiZTmSMMGkfdCOabYv192VbytFypcvI0MANrp+4i/7yEkJ0sbAEhycQjntaKGzYfjfXQyVb7BHMA==",
- "license": "MIT",
- "dependencies": {
- "cookie": "~0.7.2",
- "cookie-signature": "~1.0.7",
- "debug": "~2.6.9",
- "depd": "~2.0.0",
- "on-headers": "~1.1.0",
- "parseurl": "~1.3.3",
- "safe-buffer": "~5.2.1",
- "uid-safe": "~2.1.5"
- },
- "engines": {
- "node": ">= 0.8.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/express"
- }
- },
- "node_modules/express-session/node_modules/cookie-signature": {
- "version": "1.0.7",
- "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
- "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
- "license": "MIT"
- },
- "node_modules/express-session/node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
- "license": "MIT",
- "dependencies": {
- "ms": "2.0.0"
- }
- },
- "node_modules/express-session/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "license": "MIT"
- },
"node_modules/fast-xml-builder": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz",
@@ -2352,30 +2270,6 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
- "node_modules/ioredis": {
- "version": "5.10.1",
- "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.10.1.tgz",
- "integrity": "sha512-HuEDBTI70aYdx1v6U97SbNx9F1+svQKBDo30o0b9fw055LMepzpOOd0Ccg9Q6tbqmBSJaMuY0fB7yw9/vjBYCA==",
- "license": "MIT",
- "dependencies": {
- "@ioredis/commands": "1.5.1",
- "cluster-key-slot": "^1.1.0",
- "debug": "^4.3.4",
- "denque": "^2.1.0",
- "lodash.defaults": "^4.2.0",
- "lodash.isarguments": "^3.1.0",
- "redis-errors": "^1.2.0",
- "redis-parser": "^3.0.0",
- "standard-as-callback": "^2.1.0"
- },
- "engines": {
- "node": ">=12.22.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/ioredis"
- }
- },
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@@ -2403,18 +2297,6 @@
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"license": "MIT"
},
- "node_modules/lodash.defaults": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
- "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==",
- "license": "MIT"
- },
- "node_modules/lodash.isarguments": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
- "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==",
- "license": "MIT"
- },
"node_modules/long": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
@@ -2616,15 +2498,6 @@
"node": ">= 0.8"
}
},
- "node_modules/on-headers": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
- "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -2711,15 +2584,6 @@
"url": "https://github.com/sponsors/ljharb"
}
},
- "node_modules/random-bytes": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
- "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -2744,27 +2608,6 @@
"node": ">= 0.10"
}
},
- "node_modules/redis-errors": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
- "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
- "node_modules/redis-parser": {
- "version": "3.0.0",
- "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
- "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
- "license": "MIT",
- "dependencies": {
- "redis-errors": "^1.0.0"
- },
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/retry-as-promised": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.1.1.tgz",
@@ -2787,26 +2630,6 @@
"node": ">= 18"
}
},
- "node_modules/safe-buffer": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
- "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
- "funding": [
- {
- "type": "github",
- "url": "https://github.com/sponsors/feross"
- },
- {
- "type": "patreon",
- "url": "https://www.patreon.com/feross"
- },
- {
- "type": "consulting",
- "url": "https://feross.org/support"
- }
- ],
- "license": "MIT"
- },
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -3034,12 +2857,6 @@
"url": "https://github.com/mysqljs/sql-escaper?sponsor=1"
}
},
- "node_modules/standard-as-callback": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
- "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==",
- "license": "MIT"
- },
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
@@ -3096,18 +2913,6 @@
"node": ">= 0.6"
}
},
- "node_modules/uid-safe": {
- "version": "2.1.5",
- "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
- "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
- "license": "MIT",
- "dependencies": {
- "random-bytes": "~1.0.0"
- },
- "engines": {
- "node": ">= 0.8"
- }
- },
"node_modules/undici-types": {
"version": "7.18.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
diff --git a/backend/package.json b/backend/package.json
index b578895..983a9a7 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -13,11 +13,7 @@
"sequelize": "^6.37.5",
"mysql2": "^3.14.1",
"mariadb": "^3.4.0",
- "express-session": "^1.18.1",
- "connect-redis": "^8.0.1",
- "ioredis": "^5.6.1",
"@aws-sdk/client-s3": "^3.800.0",
- "axios": "^1.9.0",
- "crypto": "^1.0.1"
+ "axios": "^1.9.0"
}
}
diff --git a/backend/routes/admin.js b/backend/routes/admin.js
new file mode 100644
index 0000000..7d596c6
--- /dev/null
+++ b/backend/routes/admin.js
@@ -0,0 +1,28 @@
+import { Router } from 'express';
+
+const router = Router();
+
+// 관리자 인증 미들웨어
+function requireAdmin(req, res, next) {
+ const key = req.headers['x-admin-key'];
+ if (!key || key !== process.env.NEXON_API_KEY) {
+ return res.status(403).json({ error: '접근 권한이 없습니다' });
+ }
+ next();
+}
+
+// 관리자 키 검증
+router.post('/verify', (req, res) => {
+ const { key } = req.body;
+ if (key === process.env.NEXON_API_KEY) {
+ return res.json({ verified: true });
+ }
+ res.status(403).json({ error: '유효하지 않은 키입니다' });
+});
+
+// 관리자 API에 미들웨어 적용
+router.use(requireAdmin);
+
+// TODO: 보스 관리 API 추가 예정
+
+export default router;
diff --git a/backend/routes/auth.js b/backend/routes/auth.js
deleted file mode 100644
index d842754..0000000
--- a/backend/routes/auth.js
+++ /dev/null
@@ -1,66 +0,0 @@
-import { Router } from 'express';
-import crypto from 'crypto';
-import { exchangeToken, getUserInfo, refreshToken } from '../services/nexon.js';
-import { User } from '../models/index.js';
-
-const router = Router();
-
-router.get('/login', (req, res) => {
- const state = crypto.randomBytes(16).toString('hex');
- req.session.oauthState = state;
-
- const params = new URLSearchParams({
- response_type: 'code',
- client_id: process.env.NEXON_CLIENT_ID,
- redirect_uri: process.env.NEXON_REDIRECT_URI,
- scope: 'maplestory.characterlist',
- state,
- });
-
- res.redirect(`https://openid.nexon.com/oauth2/authorize?${params}`);
-});
-
-router.get('/callback', async (req, res) => {
- const { code, state } = req.query;
-
- if (!code || state !== req.session.oauthState) {
- return res.status(400).json({ error: '잘못된 요청입니다' });
- }
- delete req.session.oauthState;
-
- try {
- const tokens = await exchangeToken(code);
- const userInfo = await getUserInfo(tokens.access_token);
-
- const [user] = await User.findOrCreate({
- where: { nexon_uid: userInfo.uid },
- });
-
- req.session.userId = user.id;
- req.session.accessToken = tokens.access_token;
- req.session.refreshToken = tokens.refresh_token;
- req.session.tokenExpiresAt = Date.now() + tokens.expires_in * 1000;
-
- res.redirect('/');
- } catch (err) {
- console.error('OAuth 콜백 오류:', err.message);
- res.status(500).json({ error: '로그인 처리 중 오류가 발생했습니다' });
- }
-});
-
-router.post('/logout', (req, res) => {
- req.session.destroy((err) => {
- if (err) return res.status(500).json({ error: '로그아웃 실패' });
- res.clearCookie('connect.sid');
- res.json({ success: true });
- });
-});
-
-router.get('/me', (req, res) => {
- if (!req.session?.userId) {
- return res.json({ authenticated: false });
- }
- res.json({ authenticated: true, userId: req.session.userId });
-});
-
-export default router;
diff --git a/backend/routes/characters.js b/backend/routes/characters.js
index 3d3a646..4c311f3 100644
--- a/backend/routes/characters.js
+++ b/backend/routes/characters.js
@@ -1,62 +1,32 @@
import { Router } from 'express';
-import { requireAuth } from '../middleware/auth.js';
-import { getCharacterList, getCharacterOcid, getCharacterBasic } from '../services/nexon.js';
-import { UserCharacter } from '../models/index.js';
+import { getCharacterOcid, getCharacterBasic } from '../services/nexon.js';
const router = Router();
-// 내 캐릭터 목록 조회
-router.get('/', requireAuth, async (req, res) => {
- try {
- const characters = await UserCharacter.findAll({
- where: { user_id: req.session.userId },
- order: [['character_level', 'DESC']],
- });
- res.json(characters);
- } catch (err) {
- console.error('캐릭터 목록 조회 오류:', err.message);
- res.status(500).json({ error: '캐릭터 목록 조회 실패' });
+// 캐릭터 닉네임으로 정보 조회
+router.get('/search', async (req, res) => {
+ const { name } = req.query;
+ if (!name) {
+ return res.status(400).json({ error: '캐릭터 닉네임을 입력해주세요' });
}
-});
-// 캐릭터 목록 갱신 (넥슨 API에서 다시 가져오기)
-router.post('/refresh', requireAuth, async (req, res) => {
try {
- const charList = await getCharacterList(req.session.accessToken);
+ const ocid = await getCharacterOcid(name);
+ const basic = await getCharacterBasic(ocid);
- if (!charList?.account_list) {
- return res.status(400).json({ error: '캐릭터 목록을 가져올 수 없습니다' });
- }
-
- const results = [];
-
- for (const account of charList.account_list) {
- for (const char of account.character_list || []) {
- try {
- const ocid = await getCharacterOcid(char.character_name);
- const basic = await getCharacterBasic(ocid);
-
- const [userChar] = await UserCharacter.upsert({
- user_id: req.session.userId,
- character_name: char.character_name,
- ocid,
- world_name: basic.world_name,
- job_name: basic.character_class,
- character_level: basic.character_level,
- character_image: basic.character_image,
- });
-
- results.push(userChar);
- } catch (err) {
- console.error(`캐릭터 조회 실패: ${char.character_name}`, err.message);
- }
- }
- }
-
- res.json(results);
+ res.json({
+ character_name: basic.character_name,
+ world_name: basic.world_name,
+ job_name: basic.character_class,
+ character_level: basic.character_level,
+ character_image: basic.character_image,
+ });
} catch (err) {
- console.error('캐릭터 갱신 오류:', err.message);
- res.status(500).json({ error: '캐릭터 목록 갱신 실패' });
+ if (err.response?.status === 400) {
+ return res.status(404).json({ error: '존재하지 않는 캐릭터입니다' });
+ }
+ console.error('캐릭터 조회 오류:', err.message);
+ res.status(500).json({ error: '캐릭터 조회 실패' });
}
});
diff --git a/backend/server.js b/backend/server.js
index f9287b8..83ba3f9 100644
--- a/backend/server.js
+++ b/backend/server.js
@@ -1,11 +1,8 @@
import express from 'express';
import cors from 'cors';
-import { sessionMiddleware } from './middleware/session.js';
-import authRoutes from './routes/auth.js';
import characterRoutes from './routes/characters.js';
import bossRoutes from './routes/boss/bosses.js';
-import selectionRoutes from './routes/boss/selections.js';
-import calculateRoutes from './routes/boss/calculate.js';
+import adminRoutes from './routes/admin.js';
import { sequelize } from './lib/db.js';
const app = express();
@@ -18,13 +15,10 @@ app.use(cors({
credentials: true,
}));
app.use(express.json());
-app.use(sessionMiddleware);
-app.use('/api/auth', authRoutes);
app.use('/api/characters', characterRoutes);
app.use('/api/boss', bossRoutes);
-app.use('/api/boss/selections', selectionRoutes);
-app.use('/api/boss/calculate', calculateRoutes);
+app.use('/api/admin', adminRoutes);
app.get('/api/health', (_req, res) => {
res.json({ status: 'ok' });
diff --git a/backend/services/nexon.js b/backend/services/nexon.js
index 4d8b47b..0ae961d 100644
--- a/backend/services/nexon.js
+++ b/backend/services/nexon.js
@@ -1,49 +1,6 @@
import axios from 'axios';
const NEXON_API_BASE = 'https://open.api.nexon.com';
-const NEXON_OPENID_BASE = 'https://openid.nexon.com';
-
-export async function exchangeToken(code) {
- const { data } = await axios.post(
- `${NEXON_OPENID_BASE}/oauth2/token`,
- new URLSearchParams({
- grant_type: 'authorization_code',
- client_id: process.env.NEXON_CLIENT_ID,
- client_secret: process.env.NEXON_CLIENT_SECRET,
- code,
- }),
- { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
- );
- return data;
-}
-
-export async function refreshToken(refreshToken) {
- const { data } = await axios.post(
- `${NEXON_OPENID_BASE}/oauth2/token`,
- new URLSearchParams({
- grant_type: 'refresh_token',
- client_id: process.env.NEXON_CLIENT_ID,
- client_secret: process.env.NEXON_CLIENT_SECRET,
- refresh_token: refreshToken,
- }),
- { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
- );
- return data;
-}
-
-export async function getUserInfo(accessToken) {
- const { data } = await axios.get(`${NEXON_OPENID_BASE}/api/v1/user/info`, {
- headers: { Authorization: `Bearer ${accessToken}` },
- });
- return data.result;
-}
-
-export async function getCharacterList(accessToken) {
- const { data } = await axios.get(`${NEXON_API_BASE}/maplestory/v1/character/list`, {
- headers: { Authorization: `Bearer ${accessToken}` },
- });
- return data;
-}
export async function getCharacterOcid(characterName) {
const { data } = await axios.get(`${NEXON_API_BASE}/maplestory/v1/id`, {
diff --git a/docker-compose.yml b/docker-compose.yml
index 70d0130..84b87cb 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -28,17 +28,9 @@ services:
- db
- app
- redis:
- image: redis:7-alpine
- volumes:
- - redis-data:/data
- networks:
- - app
-
volumes:
frontend_modules:
backend_modules:
- redis-data:
networks:
caddy:
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 093529e..0a317e6 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -2,6 +2,7 @@ import { Routes, Route } from 'react-router-dom'
import Layout from './components/Layout'
import Home from './pages/Home'
import BossPage from './features/boss/BossPage'
+import Admin from './pages/Admin'
export default function App() {
return (
@@ -9,6 +10,7 @@ export default function App() {
{error}
} -