diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/event/service/EventService.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/event/service/EventService.kt index e52f056d..7d41b3e9 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/event/service/EventService.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/event/service/EventService.kt @@ -23,6 +23,7 @@ import com.wafflestudio.spring2025.domain.registration.repository.RegistrationRe import com.wafflestudio.spring2025.domain.registration.service.WaitlistReconciliationService import com.wafflestudio.spring2025.domain.user.repository.UserRepository import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Pageable import org.springframework.data.domain.Sort import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -96,10 +97,9 @@ class EventService( val event = getEventByPublicId(publicId) val eventId = requireNotNull(event.id) { "Event id is null: publicId=$publicId" } - val creatorUser = - userRepository.findById(event.createdBy).orElseThrow { - EventNotFoundException() - } + val userIdsToFetch = listOfNotNull(event.createdBy, requesterId).distinct() + val usersById = userRepository.findAllById(userIdsToFetch).associateBy { it.id!! } + val creatorUser = usersById[event.createdBy] ?: throw EventNotFoundException() val myReg = if (requesterId == null) { @@ -121,17 +121,16 @@ class EventService( .countByEventIdAndStatus(eventID = eventId, registrationStatus = RegistrationStatus.WAITLISTED) .toInt() - val totalApplicants = confirmedCount + waitlistedCount - val waitlistPosition: Int? = if (myReg?.status == RegistrationStatus.WAITLISTED) { - val waitlistedRegs = - registrationRepository.findByEventIdAndStatusOrderByCreatedAtAsc( - eventID = eventId, - registrationStatus = RegistrationStatus.WAITLISTED, - ) - val idx = waitlistedRegs.indexOfFirst { it.id == myReg.id } - if (idx >= 0) idx + 1 else null + registrationRepository + .findWaitlistPositionsByRegistrationPublicIds( + eventId = eventId, + status = RegistrationStatus.WAITLISTED, + registrationPublicIds = listOf(myReg.registrationPublicId), + ).firstOrNull() + ?.waitlistNumber + ?.toInt() } else { null } @@ -150,10 +149,7 @@ class EventService( } } - val viewerName: String? = - requesterId?.let { - userRepository.findById(it).orElse(null)?.name - } + val viewerName: String? = requesterId?.let { usersById[it]?.name } val viewer = if (viewerStatus == ViewerStatus.NONE) { @@ -184,18 +180,17 @@ class EventService( registrationEndsAt = event.registrationEndsAt, ) - val confirmedRegs = + val previewRegs = registrationRepository.findByEventIdAndStatusOrderByCreatedAtAsc( eventID = eventId, registrationStatus = RegistrationStatus.CONFIRMED, + pageable = Pageable.ofSize(5), ) - val previewRegs = confirmedRegs.take(5) - val previewUserIds = previewRegs.mapNotNull { it.userId }.distinct() - val usersById = + val previewUsersById = userRepository.findAllById(previewUserIds).associateBy { it.id!! } val guestsPreview = @@ -208,7 +203,7 @@ class EventService( profileImage = null, ) } else { - usersById[uid]?.let { + previewUsersById[uid]?.let { GuestPreview( id = it.id!!, name = it.name, @@ -276,19 +271,31 @@ class EventService( val sliced = fetched.take(pageSize) val nextCursor = sliced.lastOrNull()?.createdAt + val eventIds = sliced.map { requireNotNull(it.id) } + + val confirmedCounts = + if (eventIds.isEmpty()) { + emptyMap() + } else { + registrationRepository + .countByEventIdsAndStatuses(eventIds = eventIds, listOf(RegistrationStatus.CONFIRMED)) + .associate { it.eventId to it.totalCount.toInt() } + } + + val waitlistedCounts = + if (eventIds.isEmpty()) { + emptyMap() + } else { + registrationRepository + .countByEventIdsAndStatuses(eventIds = eventIds, listOf(RegistrationStatus.WAITLISTED)) + .associate { it.eventId to it.totalCount.toInt() } + } + val responses = sliced.map { event -> val eventId = requireNotNull(event.id) - - val confirmedCount = - registrationRepository - .countByEventIdAndStatus(eventID = eventId, registrationStatus = RegistrationStatus.CONFIRMED) - .toInt() - - val waitlistedCount = - registrationRepository - .countByEventIdAndStatus(eventID = eventId, registrationStatus = RegistrationStatus.WAITLISTED) - .toInt() + val confirmedCount = confirmedCounts[eventId] ?: 0 + val waitlistedCount = waitlistedCounts[eventId] ?: 0 MyEventResponse( publicId = event.publicId, @@ -378,9 +385,10 @@ class EventService( // 알림 대상: CONFIRMED + WAITLISTED (BANNED 제외) val registrationsToNotify = - registrationRepository - .findByEventId(eventId) - .filter { it.status == RegistrationStatus.CONFIRMED || it.status == RegistrationStatus.WAITLISTED } + registrationRepository.findByEventIdAndStatusIn( + eventID = eventId, + statuses = listOf(RegistrationStatus.CONFIRMED, RegistrationStatus.WAITLISTED), + ) // 이메일 데이터 구성 (삭제 전에 user 정보 조회) val hostUser = userRepository.findById(event.createdBy).orElse(null) diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/repository/RegistrationRepository.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/repository/RegistrationRepository.kt index 5ed64233..1c91aa06 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/repository/RegistrationRepository.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/repository/RegistrationRepository.kt @@ -41,6 +41,11 @@ interface RegistrationRepository : eventId: Long, ): Registration? + fun findByEventIdAndStatusIn( + eventID: Long, + statuses: Collection, + ): List + fun countByEventId(eventID: Long): Long fun countByEventIdAndStatus( @@ -66,6 +71,12 @@ interface RegistrationRepository : registrationStatus: RegistrationStatus, ): List + fun findByEventIdAndStatusOrderByCreatedAtAsc( + eventID: Long, + registrationStatus: RegistrationStatus, + pageable: Pageable, + ): List + @Query( """ SELECT * FROM registrations @@ -175,7 +186,7 @@ interface RegistrationRepository : ranked.waitlist_number AS waitlist_number FROM ( SELECT r.registration_public_id, - ROW_NUMBER() OVER (ORDER BY r.created_at ASC, r.registration_public_id ASC) AS waitlist_number + ROW_NUMBER() OVER (ORDER BY r.created_at ASC, r.id ASC) AS waitlist_number FROM registrations r WHERE r.event_id = :eventId AND r.status = :status ) ranked diff --git a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/service/RegistrationService.kt b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/service/RegistrationService.kt index a87b211a..00df08fc 100644 --- a/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/service/RegistrationService.kt +++ b/src/main/kotlin/com/wafflestudio/spring2025/domain/registration/service/RegistrationService.kt @@ -28,6 +28,7 @@ import com.wafflestudio.spring2025.domain.user.model.User import com.wafflestudio.spring2025.domain.user.repository.UserRepository import org.springframework.dao.DuplicateKeyException import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional import org.springframework.transaction.support.TransactionSynchronization @@ -96,6 +97,11 @@ RegistrationService( registrationRepository.findByUserIdAndEventId(userId, eventPk) } + val userIdsToFetch = listOfNotNull(lockedEvent.createdBy, userId).distinct() + val fetchedUsersById = userRepository.findAllById(userIdsToFetch).associateBy { it.id!! } + val hostEmail = fetchedUsersById[lockedEvent.createdBy]?.email + val user = userId?.let { fetchedUsersById[it] } + val saved = try { if (existingRegistration != null) { @@ -112,8 +118,7 @@ RegistrationService( -> throw RegistrationConflictException(RegistrationErrorCode.REGISTRATION_ALREADY_EXISTS) } } else { - val hostEmail = userRepository.findById(lockedEvent.createdBy).orElse(null)?.email - val registrationEmail = userId?.let { userRepository.findById(it).orElse(null)?.email } ?: guestEmail + val registrationEmail = user?.email ?: guestEmail if (hostEmail != null && hostEmail == registrationEmail) { throw RegistrationValidationException(RegistrationErrorCode.REGISTRATION_BLOCKED_HOST) @@ -143,19 +148,11 @@ RegistrationService( throw RegistrationConflictException(RegistrationErrorCode.REGISTRATION_ALREADY_EXISTS) } - val waitlistedNumber: Int? = - if (saved.status == RegistrationStatus.WAITLISTED) { - registrationRepository - .countByEventIdAndStatus(eventPk, RegistrationStatus.WAITLISTED) - .toInt() - } else { - null - } - - val user = - userId?.let { id -> - userRepository.findById(id).orElse(null) - } + val waitlistedCount = + registrationRepository + .countByEventIdAndStatus(eventPk, RegistrationStatus.WAITLISTED) + .toInt() + val waitlistedNumber: Int? = if (saved.status == RegistrationStatus.WAITLISTED) waitlistedCount else null val recipientEmail = user?.email ?: guestEmail @@ -164,10 +161,6 @@ RegistrationService( registrationRepository .countByEventIdAndStatus(eventPk, RegistrationStatus.CONFIRMED) .toInt() - val waitlistedCount = - registrationRepository - .countByEventIdAndStatus(eventPk, RegistrationStatus.WAITLISTED) - .toInt() val totalCount = confirmedCount + waitlistedCount val emailData = @@ -260,8 +253,6 @@ RegistrationService( } } - eventLockRepository.lockById(registration.eventId) - if (!isRegistrationEnabled(event)) { throw RegistrationValidationException(RegistrationErrorCode.NOT_WITHIN_REGISTRATION_WINDOW) } @@ -585,13 +576,14 @@ RegistrationService( val status = registration.status val waitlistPosition = if (status == RegistrationStatus.WAITLISTED) { - val waitlistedRegs = - registrationRepository.findByEventIdAndStatusOrderByCreatedAtAsc( - registration.eventId, - RegistrationStatus.WAITLISTED, - ) - val idx = waitlistedRegs.indexOfFirst { it.id == registration.id } - if (idx >= 0) idx + 1 else 0 + registrationRepository + .findWaitlistPositionsByRegistrationPublicIds( + eventId = registration.eventId, + status = RegistrationStatus.WAITLISTED, + registrationPublicIds = listOf(registration.registrationPublicId), + ).firstOrNull() + ?.waitlistNumber + ?.toInt() ?: 0 } else { 0 } @@ -755,31 +747,34 @@ RegistrationService( val available = capacity - confirmed if (available <= 0) return - val waitlistedRegs = + val totalWaitlisted = + registrationRepository.countByEventIdAndStatus(eventId, RegistrationStatus.WAITLISTED).toInt() + + val promoted = registrationRepository.findByEventIdAndStatusOrderByCreatedAtAsc( eventId, RegistrationStatus.WAITLISTED, + Pageable.ofSize(available), ) val waitlistNumbers = - waitlistedRegs.withIndex().associate { indexed -> - indexed.value.registrationPublicId to (indexed.index + 1) + promoted.withIndex().associate { (index, reg) -> + reg.registrationPublicId to (index + 1) } - val promoted = waitlistedRegs.take(available) promoted.forEach { it.status = RegistrationStatus.CONFIRMED } registrationRepository.saveAll(promoted) val confirmedAfter = confirmed + promoted.size - val remainingWaitlisted = waitlistedRegs.size - promoted.size + val remainingWaitlisted = totalWaitlisted - promoted.size val totalCount = confirmedAfter + remainingWaitlisted + val promotedUserIds = promoted.mapNotNull { it.userId }.distinct() + val promotedUsersById = userRepository.findAllById(promotedUserIds).associateBy { it.id!! } + val emailDataList = promoted.mapNotNull { registration -> - val user: User? = - registration.userId?.let { uid -> - userRepository.findById(uid).orElse(null) - } + val user: User? = registration.userId?.let { promotedUsersById[it] } val recipientEmail = user?.email ?: registration.guestEmail val recipientName = user?.name ?: registration.guestName ?: "참여자"