const express = require('express'); const bodyParser = require('body-parser'); const cors = require('cors'); const http = require('http'); const socketIo = require('socket.io'); const adminRoutes = require('./routes/adminRoutes'); const authRoutes = require('./routes/authRoutes'); const eventRoutes = require('./routes/eventRoutes'); const publicEventRoutes = require('./routes/publicEventRoutes'); const clubRoutes = require('./routes/clubRoutes'); const userRoutes = require('./routes/userRoutes'); const notificationRoutes = require('./routes/notificationRoutes'); const menuRoutes = require('./routes/menuRoutes'); const subscriptionRoutes = require('./routes/subscriptionRoutes'); const featureRoutes = require('./routes/featureRoutes'); const { sequelize, syncModels } = require('./models'); const app = express(); const server = http.createServer(app); const io = socketIo(server, { cors: { origin: '*', methods: ['GET', 'POST'] } }); function getDomain(origin) { try { const url = new URL(origin); return url.hostname; // 'lanebow.com', 'localhost' } catch (e) { return null; } } const allowedDomains = ['lanebow.com', 'localhost']; app.use(cors({ origin: function(origin, callback) { if (!origin) return callback(null, true); const domain = getDomain(origin); if (allowedDomains.includes(domain)) { return callback(null, true); } else { return callback(new Error('Not allowed by CORS'), false); } }, credentials: true })); app.use(bodyParser.json()); // Socket.IO 연결 처리 io.on('connection', (socket) => { console.log('새로운 클라이언트 연결:', socket.id); // 사용자 인증 및 알림 룸 참여 socket.on('authenticate', (userData) => { if (userData && userData.id) { socket.join(`user:${userData.id}`); console.log(`사용자 ${userData.id}가 알림 룸에 참여했습니다.`); } }); // 연결 해제 처리 socket.on('disconnect', () => { console.log('클라이언트 연결 해제:', socket.id); }); }); // 전역 Socket.IO 인스턴스 설정 app.set('io', io); // 치명적 예외/비동기 에러 글로벌 로그 process.on('uncaughtException', (err) => { console.error('[FATAL] Uncaught Exception:', err); }); process.on('unhandledRejection', (reason, promise) => { console.error('[FATAL] Unhandled Rejection:', reason); }); // app.js 또는 서버 초기화 파일 const session = require('express-session'); const MySQLStore = require('express-mysql-session')(session); // MySQL 세션 저장소 옵션 설정 const options = { host: process.env.DB_HOST || 'localhost', port: process.env.DB_PORT || 3306, user: process.env.DB_USER || 'root', password: process.env.DB_PASSWORD || '', database: process.env.DB_NAME || 'bowlingmanager', // 세션 테이블 설정 schema: { tableName: 'sessions', columnNames: { session_id: 'session_id', expires: 'expires', data: 'data' } } }; // MySQL 세션 저장소 생성 const sessionStore = new MySQLStore(options); const baseSessionOptions = { key: 'bowling_session', secret: process.env.JWT_SECRET, store: sessionStore, resave: false, saveUninitialized: false, cookie: { maxAge: 24 * 60 * 60 * 1000, secure: false, sameSite: 'lax' } }; app.use(session(baseSessionOptions)); // 요청마다 secure/sameSite를 동적으로 할당 app.use((req, res, next) => { const origin = req.headers.origin; const domain = getDomain(origin); if (req.session && req.session.cookie) { if (domain === 'lanebow.com') { req.session.cookie.secure = true; req.session.cookie.sameSite = 'none'; } else { req.session.cookie.secure = false; req.session.cookie.sameSite = 'lax'; } } next(); }); // 데이터베이스 연결 및 모델 동기화 실행 sequelize.authenticate() .then(() => { console.log('데이터베이스에 연결되었습니다.'); return syncModels(); }) .then(() => { console.log('데이터베이스 모델이 동기화되었습니다.'); }) .catch(err => { console.error('데이터베이스 연결 또는 모델 동기화 오류:', err); }); // Routes 설정 app.use('/api/auth', authRoutes); app.use('/api/admin', adminRoutes); app.use('/api/club', clubRoutes); app.use('/api/club/events', eventRoutes); app.use('/api/public', publicEventRoutes); app.use('/api/features', featureRoutes); app.use('/api/subscriptions', subscriptionRoutes); app.use('/api/users', userRoutes); app.use('/api/notifications', notificationRoutes); app.use('/api/menus', menuRoutes); // 정적 파일 제공 설정 const path = require('path'); app.use(express.static(path.join(__dirname, '../frontend/dist'))); // API 요청이 아닌 모든 요청은 Vue 앱으로 라우팅 app.get('*', (req, res, next) => { // API 요청은 무시하고 다음 미들웨어로 전달 if (req.path.startsWith('/api/')) { return next(); } // Vue 앱의 index.html 반환 res.sendFile(path.join(__dirname, '../frontend/dist/index.html')); }); // 서버 시작 const PORT = process.env.PORT || 3030; server.listen(PORT, () => { console.log(`서버가 ${process.env.NODE_ENV} 환경으로 포트 ${PORT}에서 실행 중입니다.`); }); module.exports = app;