팀 생성 임시 완료

This commit is contained in:
2025-04-26 07:06:03 +09:00
parent f01bcbdf39
commit d1b22d4272
20 changed files with 730 additions and 171 deletions
+9
View File
@@ -48,6 +48,15 @@ io.on('connection', (socket) => {
// 전역 Socket.IO 인스턴스 설정
app.set('io', io);
// app.js 또는 서버 초기화 파일
const session = require('express-session');
app.use(session({
secret: process.env.JWT_SECRET,
resave: false,
saveUninitialized: false,
cookie: { secure: process.env.NODE_ENV === 'production' } // https 환경이면 true, 개발환경은 false
}));
// 데이터베이스 연결 및 모델 동기화 실행
sequelize.authenticate()
.then(() => {
+43 -1
View File
@@ -16,6 +16,48 @@ const isAdmin = (user) => {
return user && (user.role === 'admin' || user.role === 'superadmin');
};
// 클럽 선택
const allowedMemberTypes = ['owner', 'manager'];
exports.selectClub = async (req, res) => {
try {
const { clubId } = req.body;
const userId = req.user && req.user.id;
// 클럽 존재 여부 확인
const club = await Club.findByPk(clubId);
if (!club) {
return res.status(404).json({ message: '클럽을 찾을 수 없습니다.' });
}
// 관리자 여부 체크
const isAdmin = req.user && (req.user.role === 'admin' || req.user.role === 'superadmin');
let isClubManager = false;
if (!isAdmin) {
// Members 테이블에서 모임장/운영진 여부 확인
const member = await Member.findOne({
where: {
clubId,
userId,
memberType: ['모임장', '운영진']
}
});
isClubManager = !!member;
}
if (!isAdmin && !isClubManager) {
return res.status(403).json({ message: '해당 클럽에 대한 권한이 없습니다.' });
}
req.session.clubId = clubId;
res.status(200).json({ message: '클럽을 선택했습니다.' });
} catch (error) {
console.error('클럽 선택 오류:', error);
res.status(500).json({ message: '클럽을 선택하는 중 오류가 발생했습니다.' });
}
};
// 모든 클럽 조회
exports.getAllClubs = async (req, res) => {
try {
@@ -57,7 +99,7 @@ exports.getAllClubs = async (req, res) => {
exports.getClubById = async (req, res) => {
try {
const { clubId } = req.body;
console.log(`clubId: ${clubId}`);
// console.log(`clubId: ${clubId}`);
if (!clubId) {
return res.status(400).json({ message: "유효하지 않은 클럽 ID입니다." });
}
+98 -12
View File
@@ -2,7 +2,7 @@
* 이벤트 컨트롤러
* 이벤트 관련 기능을 처리하는 컨트롤러
*/
const { Event, EventParticipant, Member, Club, EventScore, ClubMember } = require('../models');
const { Event, EventParticipant, Member, Club, EventScore, ClubMember, EventTeam, EventTeamMember } = require('../models');
const { Op } = require('sequelize');
const { sequelize } = require('../models');
@@ -78,7 +78,7 @@ exports.getEventById = async (req, res) => {
},
{
model: EventParticipant,
attributes: ['id', 'status', 'paymentStatus', 'teamNumber'],
attributes: ['id', 'status', 'paymentStatus'],
where: { status: { [Op.ne]: '취소' } },
required: false,
include: [
@@ -538,7 +538,6 @@ exports.updateEventScore = async (req, res) => {
const newScore = await EventScore.create({
eventId,
participantId: existingScore.participantId,
teamNumber: existingScore.teamNumber,
gameNumber,
score,
handicap,
@@ -1004,9 +1003,8 @@ exports.addEventParticipant = async (req, res) => {
const {
clubId,
memberId,
teamNumber,
paymentStatus,
paymentAmount
status,
paymentStatus
} = req.body;
const eventId = req.params.id;
if (!eventId || !clubId || !memberId) {
@@ -1071,12 +1069,10 @@ exports.addEventParticipant = async (req, res) => {
const participant = await EventParticipant.create({
eventId,
memberId,
teamNumber: teamNumber || 1,
status: req.body.status || '참가예정',
attendance: false,
paymentStatus: req.body.paymentStatus || '미납',
paymentAmount: paymentAmount || event.entryFee || 0,
registeredAt: new Date()
status: status || '참가예정',
paymentStatus: paymentStatus || '미납',
createdAt: new Date(),
updatedAt: new Date()
});
// 참가자 정보 조회
@@ -1166,6 +1162,96 @@ exports.updateEventParticipant = async (req, res) => {
}
};
// [팀 생성 기능] 이벤트별 팀 목록 조회
exports.getEventTeams = async (req, res) => {
try {
const eventId = req.params.id;
if (!eventId) {
return res.status(400).json({ message: '이벤트 ID가 필요합니다.' });
}
const teams = await EventTeam.findAll({
where: { eventId },
include: [
{
model: EventTeamMember,
include: [
{
model: EventParticipant,
include: [{ model: Member }]
}
]
}
],
order: [['teamNumber', 'ASC']]
});
const result = teams.map(team => ({
id: team.id,
teamNumber: team.teamNumber,
handicap: team.handicap,
members: (team.EventTeamMembers || [])
.sort((a, b) => a.order - b.order)
.map(tm => ({
id: tm.eventParticipantId,
name: tm.EventParticipant?.Member?.name || `참가자 ${tm.eventParticipantId}`,
order: tm.order
}))
}));
res.json({ teams: result });
} catch (error) {
console.error('팀 목록 조회 오류:', error);
res.status(500).json({ message: '팀 목록을 가져오는 중 오류가 발생했습니다.' });
}
};
// [팀 생성 기능] 이벤트별 팀 배정 저장
exports.createOrUpdateEventTeams = async (req, res) => {
const eventId = req.params.id;
const { teams } = req.body; // [{teamNumber, handicap, members:[id]}]
if (!eventId || !Array.isArray(teams)) {
return res.status(400).json({ message: '이벤트 ID와 팀 정보가 필요합니다.' });
}
try {
await sequelize.transaction(async (t) => {
// 기존 팀 및 팀멤버 데이터 삭제
await EventTeam.destroy({ where: { eventId }, transaction: t });
await EventTeamMember.destroy({
where: {}, // eventId로만 삭제하려면, eventTeamId로 팀 id 목록을 먼저 조회해서 삭제
transaction: t
});
// 새 팀 및 팀멤버 데이터 저장
for (const team of teams) {
// 팀 생성
const createdTeam = await EventTeam.create({
eventId,
teamNumber: team.teamNumber,
handicap: team.handicap || 0
}, { transaction: t });
// 팀 멤버(순서 포함) 저장
if (Array.isArray(team.members) && team.members.length > 0) {
const teamMemberRows = team.members
.filter(m => m.id != null)
.map(m => ({
eventTeamId: createdTeam.id,
eventParticipantId: m.id,
order: m.order ?? 1
}));
if (teamMemberRows.length > 0) {
await EventTeamMember.bulkCreate(teamMemberRows, { transaction: t });
}
}
}
});
res.json({ message: '팀 배정이 저장되었습니다.' });
} catch (error) {
console.error('팀 배정 저장 오류:', error);
res.status(500).json({ message: '팀 배정 저장 중 오류가 발생했습니다.' });
}
};
// 참가자 삭제 (소프트 삭제)
exports.deleteEventParticipant = async (req, res) => {
try {
+2 -1
View File
@@ -4,7 +4,8 @@ const { clubHasFeature } = require('../utils/featureAccess');
function requireFeature(featureCode) {
return async function(req, res, next) {
try {
const clubId = req.clubId || req.body.clubId || req.params.clubId;
const clubId = req.session.clubId || req.clubId || req.body.clubId || req.params.clubId;
console.log('backendClubId:', clubId);
if (!clubId) {
return res.status(400).json({ error: '클럽 정보가 필요합니다.' });
}
+1
View File
@@ -31,6 +31,7 @@ const EventTeam = sequelize.define('EventTeam', {
EventTeam.associate = (models) => {
EventTeam.belongsTo(models.Event, { foreignKey: 'eventId' });
EventTeam.hasMany(models.EventParticipant, { foreignKey: 'teamId' });
EventTeam.hasMany(models.EventTeamMember, { foreignKey: 'eventTeamId' });
};
module.exports = EventTeam;
+30
View File
@@ -0,0 +1,30 @@
const { DataTypes } = require('sequelize');
const { sequelize } = require('../config/database');
const EventTeamMember = sequelize.define('EventTeamMember', {
eventTeamId: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
eventParticipantId: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
primaryKey: true
},
order: {
type: DataTypes.INTEGER.UNSIGNED,
allowNull: false,
defaultValue: 1
}
}, {
tableName: 'EventTeamMembers',
timestamps: false
});
EventTeamMember.associate = (models) => {
EventTeamMember.belongsTo(models.EventTeam, { foreignKey: 'eventTeamId' });
EventTeamMember.belongsTo(models.EventParticipant, { foreignKey: 'eventParticipantId' });
};
module.exports = EventTeamMember;
+3
View File
@@ -15,6 +15,7 @@ const SubscriptionPlan = require('./SubscriptionPlan');
const SubscriptionPlanFeature = require('./SubscriptionPlanFeature');
const ClubSubscription = require('./ClubSubscription');
const ActivityLog = require('./activityLog');
const EventTeamMember = require('./EventTeamMember');
// 모델 동기화 함수
const syncModels = async () => {
@@ -50,6 +51,7 @@ const db = {
SubscriptionPlanFeature,
ClubSubscription,
ActivityLog,
EventTeamMember,
};
Object.values(db).forEach(model => {
if (model.associate) model.associate(db);
@@ -72,5 +74,6 @@ module.exports = {
SubscriptionPlanFeature,
ClubSubscription,
ActivityLog,
EventTeamMember,
syncModels
};
+56
View File
@@ -15,6 +15,7 @@
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"express-session": "^1.18.1",
"express-validator": "^7.2.1",
"helmet": "^8.0.0",
"jsonwebtoken": "^9.0.2",
@@ -1204,6 +1205,40 @@
"url": "https://opencollective.com/express"
}
},
"node_modules/express-session": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz",
"integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==",
"license": "MIT",
"dependencies": {
"cookie": "0.7.2",
"cookie-signature": "1.0.7",
"debug": "2.6.9",
"depd": "~2.0.0",
"on-headers": "~1.0.2",
"parseurl": "~1.3.3",
"safe-buffer": "5.2.1",
"uid-safe": "~2.1.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/express-session/node_modules/cookie": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"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-validator": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/express-validator/-/express-validator-7.2.1.tgz",
@@ -2721,6 +2756,15 @@
"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",
@@ -3474,6 +3518,18 @@
"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/umzug": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/umzug/-/umzug-2.3.0.tgz",
+1
View File
@@ -19,6 +19,7 @@
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"express-session": "^1.18.1",
"express-validator": "^7.2.1",
"helmet": "^8.0.0",
"jsonwebtoken": "^9.0.2",
+2
View File
@@ -5,6 +5,8 @@ const clubController = require('../controllers/clubController');
const { authenticateJWT } = require('../middleware/authMiddleware');
const requireFeature = require('../middleware/requireFeature');
// 클럽 선택
router.post('/select-club', authenticateJWT, clubController.selectClub);
// 모든 클럽 조회
router.get('/', clubController.getAllClubs);
+4
View File
@@ -17,6 +17,10 @@ router.post('/:id/participants', authenticateJWT, requireFeature('event_manageme
router.put('/:id/participants', authenticateJWT, requireFeature('event_management'), eventController.updateEventParticipant);
router.delete('/:id/participants', authenticateJWT, requireFeature('event_management'), eventController.deleteEventParticipant);
// [팀 생성 기능] 이벤트별 팀 배정 저장
router.post('/:id/teams', authenticateJWT, requireFeature('team_create'), eventController.createOrUpdateEventTeams);
router.get('/:id/teams', authenticateJWT, requireFeature('team_create'), eventController.getEventTeams);
// 이벤트 점수 관리
router.get('/:id/scores', authenticateJWT, requireFeature('event_management'), eventController.getEventScores);
router.post('/:id/scores', authenticateJWT, requireFeature('event_management'), eventController.addEventScore);