Initial commit - EENE Dashboard

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
EENE Dashboard
2026-05-29 18:07:10 +09:00
commit 22366dde72
64 changed files with 10483 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
import type { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
export interface JwtPayload {
userId: string;
email: string;
role: string;
}
declare global {
namespace Express {
interface Request {
user?: JwtPayload;
}
}
}
export function authenticate(req: Request, res: Response, next: NextFunction): void {
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
res.status(401).json({ message: '인증 토큰이 없습니다.' });
return;
}
const token = authHeader.slice(7);
try {
const payload = jwt.verify(token, process.env.JWT_SECRET!) as JwtPayload;
req.user = payload;
next();
} catch {
res.status(401).json({ message: '유효하지 않은 토큰입니다.' });
}
}
export function requireAdmin(req: Request, res: Response, next: NextFunction): void {
if (req.user?.role !== 'ADMIN') {
res.status(403).json({ message: '관리자 권한이 필요합니다.' });
return;
}
next();
}

View File

@@ -0,0 +1,26 @@
import type { Request, Response, NextFunction } from 'express';
export class AppError extends Error {
constructor(
public statusCode: number,
message: string,
) {
super(message);
this.name = 'AppError';
}
}
export function errorHandler(
err: Error,
_req: Request,
res: Response,
_next: NextFunction,
): void {
if (err instanceof AppError) {
res.status(err.statusCode).json({ message: err.message });
return;
}
console.error('[Error]', err);
res.status(500).json({ message: '서버 오류가 발생했습니다.' });
}

View File

@@ -0,0 +1,26 @@
import multer from 'multer';
import path from 'path';
import { v4 as uuidv4 } from 'uuid';
import fs from 'fs';
const MAX_SIZE_MB = Number(process.env.MAX_FILE_SIZE_MB) || 20;
const UPLOAD_DIR = path.resolve(process.env.UPLOAD_DIR || '../uploads');
if (!fs.existsSync(UPLOAD_DIR)) {
fs.mkdirSync(UPLOAD_DIR, { recursive: true });
}
const storage = multer.diskStorage({
destination(_req, _file, cb) {
cb(null, UPLOAD_DIR);
},
filename(_req, file, cb) {
const ext = path.extname(file.originalname);
cb(null, `${uuidv4()}${ext}`);
},
});
export const upload = multer({
storage,
limits: { fileSize: MAX_SIZE_MB * 1024 * 1024 },
});