This commit is contained in:
2025-05-14 00:56:29 +09:00
parent 1db3a03ffd
commit 8e9b941a28
6 changed files with 64 additions and 44 deletions
+20 -11
View File
@@ -22,15 +22,17 @@ exports.publicEventEntry = async (req, res) => {
return res.status(403).json({ message: '취소된 이벤트입니다.' });
}
// 클럽의 구독 활성 여부 확인
// 클럽의 구독 활성 여부 확인 - 가장 최근 구독 정보 가져오기
const clubSubscription = await sequelize.models.ClubSubscription.findOne({
where: { clubId: event.clubId, status: 'active' },
include: [{ model: sequelize.models.SubscriptionPlan }]
where: { clubId: event.clubId },
include: [{ model: sequelize.models.SubscriptionPlan }],
order: [['endDate', 'DESC']] // 가장 최근 구독 정보를 가져오기 위해 만료일 기준 내림차순 정렬
});
// 구독이 비활성화된 경우 접근 제한
if (!clubSubscription) {
return res.status(403).json({ message: '클럽의 구독이 비활성화되어 이벤트에 접근할 수 없습니다. 클럽 관리자에게 문의하세요.' });
// 구독이 없거나 만료된 경우 접근 제한
const now = new Date();
if (!clubSubscription || new Date(clubSubscription.endDate) < now) {
return res.status(403).json({ message: '클럽의 구독이 만료되어 이벤트에 접근할 수 없습니다. 클럽 관리자에게 문의하세요.' });
}
let token = null;
@@ -263,14 +265,21 @@ exports.addPublicGuest = async (req, res) => {
// 클럽의 회원 수와 구독 플랜의 최대 회원 수 확인
const memberCount = await Member.count({ where: { clubId: event.clubId } });
// 클럽의 구독 정보 가져오기
// 클럽의 구독 정보 가져오기 - 가장 최근 구독 정보
const clubSubscription = await sequelize.models.ClubSubscription.findOne({
where: { clubId: event.clubId, status: 'active' },
include: [{ model: sequelize.models.SubscriptionPlan }]
where: { clubId: event.clubId },
include: [{ model: sequelize.models.SubscriptionPlan }],
order: [['endDate', 'DESC']] // 가장 최근 구독 정보를 가져오기 위해 만료일 기준 내림차순 정렬
});
// 구독 플랜이 있고 회원 수가 최대치를 초과하는 경우
if (clubSubscription && clubSubscription.SubscriptionPlan) {
// 구독이 없거나 만료된 경우 접근 제한
const now = new Date();
if (!clubSubscription || new Date(clubSubscription.endDate) < now) {
return res.status(403).json({ message: '클럽의 구독이 만료되어 이벤트에 접근할 수 없습니다. 클럽 관리자에게 문의하세요.' });
}
// 회원 수가 최대치를 초과하는 경우
if (clubSubscription.SubscriptionPlan) {
const maxMembers = clubSubscription.SubscriptionPlan.maxMembers;
if (memberCount >= maxMembers) {
return res.status(403).json({
@@ -2,10 +2,11 @@ module.exports = {
up: async (queryInterface, Sequelize) => {
// 각 클럽 5개에 대해 2개의 구독(플랜 1, 2)을 연결
const clubSubscriptions = [];
const now = new Date();
for (let clubId = 1; clubId <= 5; clubId++) {
const planId = Math.floor(Math.random() * 2) + 1; // 랜덤으로 플랜 1, 2 중 하나 선택
const startDate = new Date(2025, 4, 1 + clubId);
const endDate = new Date(startDate.getFullYear(), startDate.getMonth()+1, startDate.getDate());
const startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() + clubId);
const endDate = new Date(startDate.getFullYear(), startDate.getMonth()+clubId, startDate.getDate());
clubSubscriptions.push({
clubId,
planId,
+5 -4
View File
@@ -2,6 +2,7 @@ module.exports = {
up: async (queryInterface, Sequelize) => {
// 각 클럽마다 3개의 이벤트 생성
const events = [];
const now = new Date();
for (let clubId = 1; clubId <= 5; clubId++) {
for (let i = 1; i <= 3; i++) {
events.push({
@@ -9,15 +10,15 @@ module.exports = {
title: `클럽${clubId} 이벤트${i}`,
description: `클럽${clubId}의 이벤트${i} 설명입니다.`,
eventType: '정기전', // ENUM 값 중 하나
startDate: new Date(2025, 3, 10 + i, 19, 0, 0),
endDate: new Date(2025, 3, 10 + i, 22, 0, 0),
startDate: new Date(now.getFullYear(), now.getMonth(), now.getDate() + i, 20, 0, 0),
endDate: new Date(now.getFullYear(), now.getMonth(), now.getDate() + i, 23, 0, 0),
location: `클럽${clubId} 이벤트장소${i}`,
gameCount: 3,
participantFee: 10000,
maxParticipants: 30,
registrationDeadline: new Date(2025, 3, 9 + i, 23, 59, 59),
registrationDeadline: new Date(now.getFullYear(), now.getMonth(), now.getDate() + i, 18, 0, 0),
participantCount: 0,
status: '활성', // ENUM 값 중 하나
status: '준비', // ENUM 값 중 하나
createdAt: new Date(),
updatedAt: new Date()
});
@@ -112,8 +112,8 @@
</div>
<div class="plan-details">
<div class="plan-detail-item">
<span class="detail-label">최대 회원수:</span>
<div>
<span class="detail-label mr-3">최대 회원수:</span>
<span class="detail-value">{{ selectedPlan.maxMembers || '제한 없음' }}</span>
</div>
@@ -1403,7 +1403,6 @@ onMounted(async () => {
display: flex;
align-items: flex-start;
padding: 8px 12px;
background-color: #f8f9fa;
border-radius: 8px;
flex: 1 1 calc(33% - 15px);
min-width: 200px;
@@ -1411,11 +1410,6 @@ onMounted(async () => {
transition: all 0.2s ease;
}
.feature-item:hover {
background-color: #f1f3f5;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.feature-item i {
color: #4CAF50;
margin-right: 10px;
@@ -1,3 +1,6 @@
.text-red {
color: var(--p-red-500) !important;
}
.event-public-wrapper {
width: 100%;
max-width: 700px;
+31 -19
View File
@@ -2,26 +2,29 @@
<div class="event-public-wrapper">
<h1 v-if="clubName" class="p-text-primary p-text-bold p-text-lg p-mb-2">{{ clubName }}</h1>
<div class="event-public-card">
<div v-if="loading" class="p-text-center p-mb-4">로딩 중...</div>
<div v-else-if="error" class="p-text-center p-mb-4">{{ error }}</div>
<div v-if="loading" class="text-center">로딩 중...</div>
<div v-else-if="error" class="text-center">
<i class="pi pi-exclamation-triangle text-red block"></i>
<span class="text-red">{{ error }}</span>
</div>
<div v-else-if="!isAuthenticated">
<Card>
<template #content>
<div class="event-password-form flex flex-column p-ai-center p-mt-6 p-mb-6">
<i class="pi pi-lock p-mb-3 event-password-lock"></i>
<div class="p-text-center p-mb-3 p-text-lg event-password-desc"> 이벤트는 비밀번호가 필요합니다.<br>비밀번호를 입력해 주세요.</div>
<div class="event-password-form flex flex-column p-ai-center mt-6 mb-6">
<i class="pi pi-lock mb-3 event-password-lock"></i>
<div class="p-text-center mb-3 p-text-lg event-password-desc"> 이벤트는 비밀번호가 필요합니다.<br>비밀번호를 입력해 주세요.</div>
<InputText v-model="inputPassword" type="password" placeholder="비밀번호 입력" class="p-mb-2 event-password-input"
@keyup.enter="verifyPassword" autofocus />
<Button label="확인" @click="verifyPassword" class="p-mb-2 event-password-btn" :loading="loading" />
<div v-if="passwordError" class="p-error p-mt-1 event-password-error">{{ passwordError }}</div>
<div v-if="passwordError" class="p-error mt-1 event-password-error">{{ passwordError }}</div>
</div>
</template>
</Card>
</div>
<div v-else>
<template v-if="event">
<Divider class="p-mb-4" />
<div class="event-tag">
<Divider class="mb-4" />
<div class="event-tag mb-2">
<Tag :value="event.eventType" class="event-type-tag" />
<Tag :value="event.status"
:severity="event.status === '완료' ? 'success' : event.status === '활성' ? 'info' : 'warning'"
@@ -31,34 +34,34 @@
<div class="flex justify-content-end gap-2">
<Button :label="joinButtonLabel" class="p-button-lg p-button-primary" @click="showJoinDialog = true" v-if="canJoin" />
</div>
<div class="flex justify-content-start p-ai-center p-mb-4">
<div class="flex justify-content-start p-ai-center mb-4">
<h2 class="event-title-inline p-text-lg p-text-bold">
{{ event.title }}
</h2>
</div>
<div class="p-text-secondary p-mb-3">
<div class="p-text-secondary mb-3">
<div class="info-row">
<i class="pi pi-calendar p-mr-5" /><strong>기간</strong>
<i class="pi pi-calendar mr-1" /><strong>기간:</strong>
{{ formatDate(event.startDate) }}
<template v-if="event.endDate">~ {{ formatDate(event.endDate) }}</template>
</div>
<div v-if="event.registrationDeadline" class="info-row">
<i class="pi pi-calendar-times p-mr-2" /><strong>마감</strong> {{ formatDate(event.registrationDeadline)
<div v-if="event.registrationDeadline" class="info-row" :class="isRegistrationClosed ? 'text-red' : ''">
<i class="pi pi-calendar-times mr-1" /><strong>마감:</strong> {{ formatDate(event.registrationDeadline)
}}
</div>
<div v-if="event.location" class="info-row">
<i class="pi pi-map-marker p-mr-2" /><strong>장소</strong> {{ event.location }}
<i class="pi pi-map-marker mr-1" /><strong>장소:</strong> {{ event.location }}
</div>
<div v-if="event.participantFee" class="info-row">
<i class="pi pi-credit-card p-mr-2" /><strong>참가비</strong> {{
<i class="pi pi-credit-card mr-1" /><strong>참가비:</strong> {{
Number(event.participantFee).toLocaleString('ko-KR') }}
</div>
<div v-if="event.maxParticipants" class="info-row">
<i class="pi pi-user p-mr-2" /><strong>참여</strong> {{ participants.length }}/{{ event.maxParticipants
<i class="pi pi-user mr-1" /><strong>참여:</strong> {{ participants.length }}/{{ event.maxParticipants
}}
</div>
</div>
<div class="p-text-secondary p-mb-4">{{ event.description || '' }}</div>
<div class="p-text-secondary mb-4">{{ event.description || '' }}</div>
<Divider />
<!-- 팀별 게임별 점수/등수 -->
</div>
@@ -208,12 +211,21 @@ const isReady = computed(() => event.value.status === '준비');
const isActive = computed(() => event.value.status === '활성');
const isFinished = computed(() => event.value.status === '완료');
const canJoin = computed(() => (isReady.value || isActive.value) && isAuthenticated.value);
const canAddGuest = computed(() => isReady.value && isAuthenticated.value);
//
const isRegistrationClosed = computed(() => {
if (!event.value.registrationDeadline) return false;
const now = new Date();
const deadline = new Date(event.value.registrationDeadline);
return now > deadline;
});
// , ,
const canJoin = computed(() => (isReady.value || isActive.value) && isAuthenticated.value && !isRegistrationClosed.value);
const canEditParticipant = computed(() => (isReady.value || isActive.value) && isAuthenticated.value);
const canInputScore = computed(() => isActive.value && isAuthenticated.value);
const joinButtonLabel = computed(() => {
if (isRegistrationClosed.value) return '신청 마감';
if (isReady.value) return '참가 신청/수정';
if (isActive.value) return '참가자 정보 수정';
return '참가자 정보';