-
Notifications
You must be signed in to change notification settings - Fork 2
feat: 도어용 게스트 티켓 도어 확인 및 유효성 검증 api - #197 #198
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f0a4ec5
b4dacfd
3d7e86f
4bf2279
918826f
fed0747
c7d6fa7
56bc3c9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -23,7 +23,7 @@ | |||||
| public final class QrCodeUtil { | ||||||
| private static final int QR_SIZE = 256; | ||||||
| private static final String PNG = "PNG"; | ||||||
| private static final String URL_PATH = "/entry/"; | ||||||
| private static final String URL_PATH = "/entry/guest"; | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find . -name "QrCodeUtil.java" -type f -exec wc -l {} \;Repository: PERMIT-SEOUL/permit-server Length of output: 151 🏁 Script executed: find . -name "QrCodeUtil.java" -type f -exec cat -n {} \;Repository: PERMIT-SEOUL/permit-server Length of output: 2509 🏁 Script executed: # More specific search for /entry/ paths in controllers
rg -n --type=java '"/entry' -B 5 -A 5Repository: PERMIT-SEOUL/permit-server Length of output: 1349 🏁 Script executed: # Search for any @GetMapping or @PostMapping with path variables
rg -n --type=java '@(Get|Post|Put|Patch|Delete)Mapping' -B 2 -A 5 | grep -E '(Mapping|entry|guest|ticketCode|@)' | head -100Repository: PERMIT-SEOUL/permit-server Length of output: 15668 🏁 Script executed: # Search for any route or endpoint that contains "/entry"
rg -n --type=java 'RequestMapping|RestController|Controller' ./src/main/java/com/permitseoul/permitserver/domain/guest/ -B 1 -A 5Repository: PERMIT-SEOUL/permit-server Length of output: 2622 🏁 Script executed: # Search for any TODO or FIXME comments related to entry or guest
rg -n 'TODO|FIXME' --type=java | grep -i 'entry\|guest' | head -20Repository: PERMIT-SEOUL/permit-server Length of output: 52 🏁 Script executed: # Check git log for recent changes to QrCodeUtil
git log --oneline -20 -- src/main/java/com/permitseoul/permitserver/domain/admin/util/QrCodeUtil.java 2>/dev/null || echo "Git history not available"Repository: PERMIT-SEOUL/permit-server Length of output: 116 URL 경로에 구분자가 누락되어 QR 코드가 잘못된 URL을 생성합니다. Line 31에서
이로 인해 QR 코드를 스캔했을 때 라우팅 실패나 404 에러가 발생합니다. 경로 구분자를 추가하세요: - private static final String URL_PATH = "/entry/guest";
+ private static final String URL_PATH = "/entry/guest/";📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
|
|
||||||
|
|
||||||
| public static byte[] generatePng(final String baseUrl, final String ticketCode) { | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,6 @@ | ||
| package com.permitseoul.permitserver.domain.eventimage; | ||
|
|
||
| public abstract class EventImageBaseException extends RuntimeException { | ||
| import com.permitseoul.permitserver.global.exception.PermitGlobalException; | ||
|
|
||
| public abstract class EventImageBaseException extends PermitGlobalException { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package com.permitseoul.permitserver.domain.guest; | ||
|
|
||
| import com.permitseoul.permitserver.global.exception.PermitGlobalException; | ||
|
|
||
| public abstract class GuestBaseException extends PermitGlobalException { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package com.permitseoul.permitserver.domain.guest.api; | ||
|
|
||
| import com.permitseoul.permitserver.domain.guest.api.exception.GuestApiException; | ||
| import com.permitseoul.permitserver.global.response.ApiResponseUtil; | ||
| import com.permitseoul.permitserver.global.response.BaseResponse; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.ExceptionHandler; | ||
| import org.springframework.web.bind.annotation.RestControllerAdvice; | ||
|
|
||
| @RestControllerAdvice(basePackages = "com.permitseoul.permitserver.domain.guest") | ||
| public class GuestExceptionHandler { | ||
|
|
||
| @ExceptionHandler(GuestApiException.class) | ||
| public ResponseEntity<BaseResponse<?>> handleGuestApiException(final GuestApiException e) { | ||
| return ApiResponseUtil.failure(e.getErrorCode()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| package com.permitseoul.permitserver.domain.guest.api.controller; | ||
|
|
||
|
|
||
| import com.permitseoul.permitserver.domain.guest.api.dto.req.GuestTicketConfirmRequest; | ||
| import com.permitseoul.permitserver.domain.guest.api.service.GuestService; | ||
| import com.permitseoul.permitserver.global.response.ApiResponseUtil; | ||
| import com.permitseoul.permitserver.global.response.BaseResponse; | ||
| import com.permitseoul.permitserver.global.response.code.SuccessCode; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.web.bind.annotation.*; | ||
|
|
||
| @RestController | ||
| @RequestMapping("/api/guests") | ||
| @RequiredArgsConstructor | ||
| public class GuestController { | ||
| private final GuestService guestService; | ||
|
|
||
| //도어용 게스트 티켓 유효성 검증 api | ||
| @GetMapping("/tickets/door/validation/{ticketCode}") | ||
| public ResponseEntity<BaseResponse<?>> validateGuestTicket( | ||
| @PathVariable final String ticketCode | ||
| ) { | ||
| return ApiResponseUtil.success(SuccessCode.OK, guestService.validateGuestTicket(ticketCode)); | ||
| } | ||
|
|
||
| //도어용 게스트 티켓 스텝 확인 api | ||
| @PostMapping("/tickets/door/staff/confirm") | ||
| public ResponseEntity<BaseResponse<?>> confirmGuestTicketByStaffAtDoor( | ||
| @RequestBody @Valid GuestTicketConfirmRequest guestTicketConfirmRequest | ||
| ) { | ||
| guestService.confirmGuestTicketByStaff(guestTicketConfirmRequest.ticketCode(), guestTicketConfirmRequest.checkCode()); | ||
| return ApiResponseUtil.success(SuccessCode.OK); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package com.permitseoul.permitserver.domain.guest.api.dto.req; | ||
|
|
||
| import jakarta.validation.constraints.NotBlank; | ||
|
|
||
| public record GuestTicketConfirmRequest( | ||
| @NotBlank(message = "ticketCode가 비어있습니다.") | ||
| String ticketCode, | ||
|
|
||
| @NotBlank(message = "checkCode가 비어있습니다.") | ||
| String checkCode | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,15 @@ | ||||||||||
| package com.permitseoul.permitserver.domain.guest.api.dto.res; | ||||||||||
|
|
||||||||||
| import com.fasterxml.jackson.annotation.JsonFormat; | ||||||||||
| import com.permitseoul.permitserver.domain.ticket.api.dto.res.DoorValidateUserTicket; | ||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 사용하지 않는 import 제거 필요
import com.fasterxml.jackson.annotation.JsonFormat;
-import com.permitseoul.permitserver.domain.ticket.api.dto.res.DoorValidateUserTicket;
import java.time.LocalDateTime;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| import java.time.LocalDateTime; | ||||||||||
|
|
||||||||||
| public record GuestTicketValidateResponse( | ||||||||||
| String eventName, | ||||||||||
| String ticketName | ||||||||||
| ) { | ||||||||||
| public static GuestTicketValidateResponse of(final String eventName) { | ||||||||||
| return new GuestTicketValidateResponse(eventName, "Guest Ticket"); | ||||||||||
| } | ||||||||||
| } | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| package com.permitseoul.permitserver.domain.guest.api.exception; | ||
|
|
||
| import com.permitseoul.permitserver.domain.guest.GuestBaseException; | ||
| import com.permitseoul.permitserver.global.response.code.ErrorCode; | ||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| @Getter | ||
| @RequiredArgsConstructor | ||
| public abstract class GuestApiException extends GuestBaseException { | ||
| private final ErrorCode errorCode; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package com.permitseoul.permitserver.domain.guest.api.exception; | ||
|
|
||
| import com.permitseoul.permitserver.global.response.code.ErrorCode; | ||
|
|
||
| public class GuestNotFoundException extends GuestApiException { | ||
| public GuestNotFoundException(ErrorCode errorCode) { | ||
| super(errorCode); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package com.permitseoul.permitserver.domain.guest.api.exception; | ||
|
|
||
| import com.permitseoul.permitserver.global.response.code.ErrorCode; | ||
|
|
||
| public class GuestTicketIllegalException extends GuestApiException{ | ||
| public GuestTicketIllegalException(ErrorCode errorCode) { | ||
| super(errorCode); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| package com.permitseoul.permitserver.domain.guest.api.service; | ||
|
|
||
| import com.permitseoul.permitserver.domain.admin.guestticket.core.domain.GuestTicketStatus; | ||
| import com.permitseoul.permitserver.domain.admin.guestticket.core.domain.entity.GuestTicketEntity; | ||
| import com.permitseoul.permitserver.domain.admin.guestticket.core.exception.GuestTicketNotFoundException; | ||
| import com.permitseoul.permitserver.domain.event.core.component.EventRetriever; | ||
| import com.permitseoul.permitserver.domain.event.core.domain.Event; | ||
| import com.permitseoul.permitserver.domain.event.core.exception.EventNotfoundException; | ||
| import com.permitseoul.permitserver.domain.guest.api.dto.res.GuestTicketValidateResponse; | ||
| import com.permitseoul.permitserver.domain.guest.api.exception.GuestNotFoundException; | ||
| import com.permitseoul.permitserver.domain.guest.api.exception.GuestTicketIllegalException; | ||
| import com.permitseoul.permitserver.domain.guest.core.component.GuestRetriever; | ||
| import com.permitseoul.permitserver.domain.guest.core.component.GuestUpdater; | ||
| import com.permitseoul.permitserver.domain.guest.core.domain.GuestTicket; | ||
| import com.permitseoul.permitserver.domain.ticket.api.exception.NotFoundTicketException; | ||
| import com.permitseoul.permitserver.domain.ticket.core.exception.TicketNotFoundException; | ||
| import com.permitseoul.permitserver.domain.tickettype.core.exception.TicketTypeNotfoundException; | ||
| import com.permitseoul.permitserver.global.response.code.ErrorCode; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import java.util.Objects; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| public class GuestService { | ||
| private final GuestRetriever guestRetriever; | ||
| private final EventRetriever eventRetriever; | ||
| private final GuestUpdater guestUpdater; | ||
|
|
||
| public GuestTicketValidateResponse validateGuestTicket(final String ticketCode) { | ||
| try { | ||
| final GuestTicket guestTicket = guestRetriever.findGuestTicketByTicketCode(ticketCode); | ||
| validateGuestTicketStatus(guestTicket.getStatus()); | ||
|
|
||
| final Event event = eventRetriever.findEventById(guestTicket.getEventId()); | ||
| return GuestTicketValidateResponse.of(event.getName()); | ||
| } catch (GuestTicketNotFoundException e) { | ||
| throw new GuestNotFoundException(ErrorCode.NOT_FOUND_GUEST_TICKET); | ||
| } catch (EventNotfoundException e) { | ||
| throw new GuestNotFoundException(ErrorCode.NOT_FOUND_EVENT); | ||
| } | ||
| } | ||
|
|
||
| @Transactional | ||
| public void confirmGuestTicketByStaff(final String ticketCode, final String checkCodeFromStaff) { | ||
| try { | ||
| final GuestTicketEntity guestTicketEntity = guestRetriever.findGuestTicketEntityByTicketCode(ticketCode); | ||
| validateGuestTicketStatus(guestTicketEntity.getStatus()); | ||
|
|
||
| final Event event = eventRetriever.findEventById(guestTicketEntity.getEventId()); | ||
| validateGuestTicketByCheckCode(event.getTicketCheckCode(), checkCodeFromStaff); | ||
|
|
||
| guestUpdater.updateGuestTicketStatus(guestTicketEntity, GuestTicketStatus.USED); | ||
| } catch (GuestTicketNotFoundException e) { | ||
| throw new GuestNotFoundException(ErrorCode.NOT_FOUND_GUEST_TICKET); | ||
| } catch (EventNotfoundException e) { | ||
| throw new GuestNotFoundException(ErrorCode.NOT_FOUND_EVENT); | ||
| } | ||
| } | ||
|
|
||
| private void validateGuestTicketStatus(final GuestTicketStatus status) { | ||
| if(status == GuestTicketStatus.USED) { | ||
| throw new GuestTicketIllegalException(ErrorCode.CONFLICT_ALREADY_USED_TICKET); | ||
| } | ||
| } | ||
|
|
||
| private void validateGuestTicketByCheckCode(final String checkCode, final String checkCodeFromStaff) { | ||
| if(!Objects.equals(checkCode, checkCodeFromStaff)) { | ||
| throw new GuestTicketIllegalException(ErrorCode.BAD_REQUEST_TICKET_CHECK_CODE_ERROR); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package com.permitseoul.permitserver.domain.guest.core.component; | ||
|
|
||
| import com.permitseoul.permitserver.domain.admin.guestticket.core.domain.entity.GuestTicketEntity; | ||
| import com.permitseoul.permitserver.domain.admin.guestticket.core.exception.GuestTicketNotFoundException; | ||
| import com.permitseoul.permitserver.domain.admin.guestticket.core.repository.GuestTicketRepository; | ||
| import com.permitseoul.permitserver.domain.guest.core.domain.GuestTicket; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Component | ||
| @RequiredArgsConstructor | ||
| public class GuestRetriever { | ||
| private final GuestTicketRepository guestTicketRepository; | ||
|
|
||
| public GuestTicket findGuestTicketByTicketCode(final String ticketCode) { | ||
| return GuestTicket.fromEntity(guestTicketRepository.findByGuestTicketCode(ticketCode).orElseThrow(GuestTicketNotFoundException::new)); | ||
| } | ||
|
|
||
| public GuestTicketEntity findGuestTicketEntityByTicketCode(final String ticketCode) { | ||
| return guestTicketRepository.findByGuestTicketCode(ticketCode).orElseThrow(GuestTicketNotFoundException::new); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package com.permitseoul.permitserver.domain.guest.core.component; | ||
|
|
||
| import com.permitseoul.permitserver.domain.admin.guestticket.core.domain.GuestTicketStatus; | ||
| import com.permitseoul.permitserver.domain.admin.guestticket.core.domain.entity.GuestTicketEntity; | ||
| import org.springframework.stereotype.Component; | ||
|
|
||
| @Component | ||
| public class GuestUpdater { | ||
|
|
||
| public void updateGuestTicketStatus(final GuestTicketEntity guestTicketEntity, final GuestTicketStatus guestTicketStatus) { | ||
| guestTicketEntity.updateStatus(guestTicketStatus); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package com.permitseoul.permitserver.domain.guest.core.domain; | ||
|
|
||
| import com.permitseoul.permitserver.domain.admin.guestticket.core.domain.GuestTicketStatus; | ||
| import com.permitseoul.permitserver.domain.admin.guestticket.core.domain.entity.GuestTicketEntity; | ||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
||
| @Getter | ||
| @RequiredArgsConstructor | ||
| public class GuestTicket { | ||
| private final Long guestTicketId; | ||
| private final long eventId; | ||
| private final long guestId; | ||
| private final String guestTicketCode; | ||
| private final GuestTicketStatus status; | ||
| private final LocalDateTime usedTime; | ||
|
|
||
| public static GuestTicket fromEntity(final GuestTicketEntity guestTicketEntity) { | ||
| return new GuestTicket( | ||
| guestTicketEntity.getGuestTicketId(), | ||
| guestTicketEntity.getEventId(), | ||
| guestTicketEntity.getGuestId(), | ||
| guestTicketEntity.getGuestTicketCode(), | ||
| guestTicketEntity.getStatus(), | ||
| guestTicketEntity.getUsedTime() | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -39,7 +39,8 @@ public class SecurityConfig { | |||||
| "/api/tickets/info/*", | ||||||
| "/api/tickets/door/staff/confirm", | ||||||
| "/api/tickets/door/validation/*", | ||||||
| "/api/notion/**" | ||||||
| "/api/notion/**", | ||||||
| "api/guests/**" | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 치명적: 경로 패턴에 선행 슬래시(/)가 누락되었습니다. Line 43의 다음 diff를 적용하여 수정하세요: - "api/guests/**"
+ "/api/guests/**"📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||
| }; | ||||||
|
|
||||||
| private static final String[] adminURIList = { | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: PERMIT-SEOUL/permit-server
Length of output: 4130
🏁 Script executed:
Repository: PERMIT-SEOUL/permit-server
Length of output: 179
🏁 Script executed:
Repository: PERMIT-SEOUL/permit-server
Length of output: 1467
🏁 Script executed:
Repository: PERMIT-SEOUL/permit-server
Length of output: 1598
🏁 Script executed:
Repository: PERMIT-SEOUL/permit-server
Length of output: 117
API 응답 구조 변경 사항을 검토하세요.
TimetableInfoResponse에eventId필드가 새로 추가되었으며, 이로 인해 기존 API 응답 구조가 변경되었습니다. 이 변경사항은 API 응답 스키마를 변경하므로, 클라이언트(프론트엔드, 외부 시스템 등)의 JSON 직렬화/역직렬화 로직에 영향을 미칠 수 있습니다. 버전 관리나 하위 호환성 유지 방안을 검토하세요.