Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
dd0d60e
feat: 응모자 생성, 응모자 현황 조회 기능 생성
Seooooo24 Jan 30, 2026
85792f4
chore: 유저 접근을 위한 SecurityConfig 수정
Seooooo24 Jan 30, 2026
b20c6d8
chore: pairGameApplier 테이블 생성 flyway 스크립트 작성
Seooooo24 Jan 30, 2026
2811f64
feat: 짝 맞추기 게임 메타데이터 조회 API 구현
Seooooo24 Jan 30, 2026
19fe204
refactor: 학생회비 납부내역 이미지 cdn url 저장하도록 수정
Seooooo24 Jan 30, 2026
4cbf1f8
fix: 플라이웨이 스크립트 오타 수정
Seooooo24 Jan 30, 2026
5006dc1
refactor: studentNumber unique 제약조건 추가
Seooooo24 Jan 30, 2026
817e76e
refactor: 코드래빗 리뷰 반영
Seooooo24 Jan 30, 2026
f2f3c46
refactor: 학번 중복 체크 로직 추가
Seooooo24 Jan 30, 2026
797aaed
refactor: 리뷰 코멘트 반영
Seooooo24 Jan 31, 2026
62fb8b3
refactor: toCommand 메소드 파라미터 수정
Seooooo24 Jan 31, 2026
05f4df8
test: FacadeUserPairGameServiceTest 통합 테스트 작성
Seooooo24 Feb 1, 2026
ef8b07a
test: FacadeUserPairGameServiceTest 통합 테스트 수정
Seooooo24 Feb 1, 2026
bca624f
chore: 도커 최신 버전 이슈 해결을 위한 properties 파일 추가
Seooooo24 Feb 1, 2026
6f77f82
refactor: 게임 메타데이터 조회 API 응답에 clubName 추가
Seooooo24 Feb 1, 2026
51e21ba
refactor: ClubService 메서드명 수정
Seooooo24 Feb 1, 2026
c6945aa
refactor: 클라이언트 요청사항 반영
Seooooo24 Feb 3, 2026
3478507
test: 테스트 실패 방지를 위해 느슨하게 수정
Seooooo24 Feb 3, 2026
1f9673b
refactor: 리뷰 내용 반영
Seooooo24 Feb 4, 2026
cc3b983
refactor: 리뷰 내용 반영
Seooooo24 Feb 4, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,13 @@ public SecurityFilterChain filterChain(HttpSecurity http, JwtAuthService authSer
API_PREFIX + "/questions/**",
API_PREFIX + "/feeds/**",
API_PREFIX + "/forms/**",
API_PREFIX + "/file/upload-url/form-application"
API_PREFIX + "/file/upload-url/form-application",
API_PREFIX + "/pair-game/**"
)
.permitAll()
.requestMatchers(POST,
API_PREFIX + "/forms/{formId}/applications"
API_PREFIX + "/forms/{formId}/applications",
API_PREFIX + "/pair-game/appliers"
)
.permitAll()
.requestMatchers(API_PREFIX + "/internal/**")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package ddingdong.ddingdongBE.common.exception;

import static org.springframework.http.HttpStatus.BAD_REQUEST;

public class FileException extends CustomException {

private static final String UPLOADED_FILE_NOT_FOUND_MESSAGE = "업로드 할 파일이 없습니다.";
private static final String FILE_READING_ERROR_MESSAGE = "파일을 읽을 수 없습니다.";

public FileException(String message, int errorCode) { super(message, errorCode); }

public static final class UploadedFileNotFoundException extends FileException {

public UploadedFileNotFoundException() {
super(UPLOADED_FILE_NOT_FOUND_MESSAGE, BAD_REQUEST.value());
}
}

public static final class FileReadingException extends FileException {

public FileReadingException() {
super(FILE_READING_ERROR_MESSAGE, BAD_REQUEST.value());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package ddingdong.ddingdongBE.common.exception;

import static org.springframework.http.HttpStatus.BAD_REQUEST;

public class PairGameApplierException extends CustomException {

private static final String DUPLICATED_PAIR_GAME_APPLIER_MESSAGE = "이미 존재하는 응모자입니다.";
public PairGameApplierException(String message, int errorCode) {
super(message, errorCode);
}

public static final class DuplicatedPairGameApplierException extends PairGameApplierException {
public DuplicatedPairGameApplierException() {
super(DUPLICATED_PAIR_GAME_APPLIER_MESSAGE, BAD_REQUEST.value());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ public interface ClubService {

Club getByUserId(Long userId);

List<Club> findAll();
List<Club> getAll();

List<Club> getAllByIds(List<Long> clubIds);

void update(Club club, Club updatedClub);

void delete(Long clubId);

Club getByUserIdWithFetch(Long userId);

List<UserClubListInfo> findAllClubListInfo();
List<UserClubListInfo> getAllClubListInfo();
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public Long create(CreateClubCommand command) {

@Override
public List<AdminClubListQuery> findAll() {
return clubService.findAll().stream()
return clubService.getAll().stream()
.map(club -> {
UploadedFileUrlAndNameQuery clubProfileImageQuery = fileMetaDataService.getCoupledAllByDomainTypeAndEntityId(
DomainType.CLUB_PROFILE, club.getId())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class FacadeUserClubServiceImpl implements FacadeUserClubService {

@Override
public List<UserClubListQuery> findAllWithRecruitTimeCheckPoint(LocalDate now) {
List<UserClubListInfo> userClubListInfos = clubService.findAllClubListInfo();
List<UserClubListInfo> userClubListInfos = clubService.getAllClubListInfo();
return userClubListInfos.stream()
.map(info -> UserClubListQuery.of(info, checkRecruit(now, info.getStart(), info.getEnd()).getText()))
.toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,20 @@ public Club getByUserIdWithFetch(Long userId) {
}

@Override
public List<UserClubListInfo> findAllClubListInfo() {
public List<UserClubListInfo> getAllClubListInfo() {
return clubRepository.findAllClubListInfo(LocalDate.now());
}

@Override
public List<Club> findAll() {
public List<Club> getAll() {
return clubRepository.findAll();
}

@Override
public List<Club> getAllByIds(List<Long> clubIds) {
return clubRepository.findAllById(clubIds);
}
Comment on lines +59 to +62
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

rg -n -A 5 'getAllByIds' --type java

Repository: COW-dev/ddingdong-be

Length of output: 4933


🏁 Script executed:

cat -n src/main/java/ddingdong/ddingdongBE/domain/pairgame/service/FacadeUserPairGameService.java | sed -n '50,70p'

Repository: COW-dev/ddingdong-be

Length of output: 1255


🏁 Script executed:

# Check if FileMetaData has foreign key constraint to Club
rg -A 10 'class FileMetaData' --type java

Repository: COW-dev/ddingdong-be

Length of output: 6617


🏁 Script executed:

cat -n src/main/java/ddingdong/ddingdongBE/domain/filemetadata/entity/FileMetaData.java

Repository: COW-dev/ddingdong-be

Length of output: 3216


🏁 Script executed:

# Check the second call site more thoroughly
cat -n src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FacadeCentralFormApplicationServiceImpl.java | sed -n '100,120p'

Repository: COW-dev/ddingdong-be

Length of output: 762


🏁 Script executed:

# Check if there's any null checking or validation for clubName after it's retrieved
cat -n src/main/java/ddingdong/ddingdongBE/domain/pairgame/service/FacadeUserPairGameService.java | sed -n '59,67p'

Repository: COW-dev/ddingdong-be

Length of output: 591


🏁 Script executed:

# Check if there are any tests that show the expected behavior
rg -A 10 'getAllByIds' --type java -B 5 src/test

Repository: COW-dev/ddingdong-be

Length of output: 46


🏁 Script executed:

# Check if Club entity has any cascade delete or soft delete
rg -A 20 'class Club' --type java src/main/java/ddingdong/ddingdongBE/domain/club

Repository: COW-dev/ddingdong-be

Length of output: 1953


getAllByIds 메서드의 반환 데이터 검증 필요

findAllById는 존재하지 않는 ID에 대해 예외를 발생시키지 않고 찾은 엔티티만 반환합니다. FileMetaData는 Club과의 외래 키 제약이 없어 entityId가 삭제된 Club을 참조할 수 있으며, 이 경우 clubNameMap.get(file.getEntityId())가 null을 반환합니다.

특히 FacadeUserPairGameService (line 60)에서 null 값이 response에 포함되므로, 반환된 데이터 크기 검증 또는 null 체크 로직이 필요합니다.

🤖 Prompt for AI Agents
In
`@src/main/java/ddingdong/ddingdongBE/domain/club/service/GeneralClubService.java`
around lines 59 - 62, getAllByIds currently returns whatever
clubRepository.findAllById(ids) finds and can miss IDs (so later
clubNameMap.get(file.getEntityId()) yields null); modify getAllByIds to validate
that all requested IDs were returned (compare returned list size or build a Set
of returned IDs against input IDs) and either throw a clear exception (e.g.,
EntityNotFoundException) when any ID is missing or return a map of id->Club and
let callers handle missing entries; update callers such as
FacadeUserPairGameService to handle missing Clubs (avoid placing null values
into responses) by filtering out or substituting a safe value and ensure any
usage of clubNameMap.get(file.getEntityId()) checks for null before adding to
response.


@Override
@Transactional
public void update(Club club, Club updatedClub) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ List<FileMetaData> findAllByDomainTypeAndEntityIdWithFileStatus(
@Param("fileStatus") FileStatus fileStatus
);

@Query("""
select fmd from FileMetaData fmd
where fmd.domainType = :domainType
and fmd.fileStatus = :fileStatus
""")
List<FileMetaData> findAllByDomainTypeWithFileStatus(
@Param("domainType") DomainType domainType,
@Param("fileStatus") FileStatus fileStatus
);

@Query("select fmd from FileMetaData fmd where fmd.entityId = :entityId and fmd.fileStatus = :fileStatus")
List<FileMetaData> findAllByEntityIdWithFileStatus(
@Param("entityId") Long entityId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public interface FileMetaDataService {

List<FileMetaData> getCoupledAllByDomainTypeAndEntityId(DomainType domainType, Long entityId);

List<FileMetaData> getCoupledAllByDomainType(DomainType domainType);

List<FileMetaData> getCoupledAllByEntityId(Long entityId);

List<FileMetaData> getCoupledAllByDomainTypeAndEntityIdOrderedAsc(DomainType domainType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ public List<FileMetaData> getCoupledAllByDomainTypeAndEntityId(DomainType domain
return fileMetaDataRepository.findAllByDomainTypeAndEntityIdWithFileStatus(domainType, entityId, COUPLED);
}

@Override
public List<FileMetaData> getCoupledAllByDomainType(DomainType domainType) {
return fileMetaDataRepository.findAllByDomainTypeWithFileStatus(domainType, COUPLED);
}

@Override
public List<FileMetaData> getCoupledAllByEntityId(Long entityId) {
return fileMetaDataRepository.findAllByEntityIdWithFileStatus(entityId, COUPLED);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package ddingdong.ddingdongBE.domain.pairgame.api;

import ddingdong.ddingdongBE.domain.pairgame.controller.dto.request.CreatePairGameApplierRequest;
import ddingdong.ddingdongBE.domain.pairgame.controller.dto.response.PairGameApplierAmountResponse;
import ddingdong.ddingdongBE.domain.pairgame.controller.dto.response.PairGameMetaDataResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

@Tag(name = "PairGame - User", description = "User PairGame API")
@RequestMapping("/server")
public interface UserPairGameApi {

@Operation(summary = "응모자 생성 API")
@ApiResponse(responseCode = "201", description = "응모자 생성 성공")
@ResponseStatus(HttpStatus.CREATED)
@PostMapping("/pair-game/appliers")
void createPairGameApplier(
@Valid @RequestPart("request") CreatePairGameApplierRequest request,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p1) RequestPart 필요한가요? 내부에 MultipartFile이 존재하다면, 사용한다는데 분리되어있는 것 같아서요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSON 데이터와 MultipartFile를 함께 받으려면 RequestPart로 각각 받아야 한다고 알고 있어서 해당 방법으로 구현했습니다! 찾아보니 @ModelAttribute로 한 번에 받는 방법도 있는데, 혹시 해당 방법으로 바꿀 필요가 있을까요?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requestdto 내부에 Multipartfile이 존재하지 않고, 따로 파라미터로 받고 있어서 질문하였습니다.
dto내부에는 json데이터만 담고 있는 것 같아서요!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RequestPart로 MultipartFile을 받으려면 dto 내부에 위치하면 안 되는 것으로 알고 있어 해당 방식으로 구현하였습니다..!! 혹시 dto 내부에 위치시키는 다른 방식이 있을까요? 해당 방식은 프론트에서 form-data 형식으로 각각 key 지정해서 보내주면 되는 것 같습니다!

@RequestPart("file") MultipartFile file
);

@Operation(summary = "응모자 현황 조회 API")
@ApiResponse(responseCode = "200", description = "응모자 현황 조회 성공")
@ResponseStatus(HttpStatus.OK)
@GetMapping("/pair-game/appliers/amount")
PairGameApplierAmountResponse getPairGameApplierAmount();

@Operation(summary = "게임 메타데이터 조회 API")
@ApiResponse(responseCode = "200", description = "게임 메타데이터 조회 성공")
@ResponseStatus(HttpStatus.OK)
@GetMapping("/pair-game/metadata")
PairGameMetaDataResponse getPairGameMetaData();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package ddingdong.ddingdongBE.domain.pairgame.controller;

import ddingdong.ddingdongBE.domain.pairgame.api.UserPairGameApi;
import ddingdong.ddingdongBE.domain.pairgame.controller.dto.request.CreatePairGameApplierRequest;
import ddingdong.ddingdongBE.domain.pairgame.controller.dto.response.PairGameApplierAmountResponse;
import ddingdong.ddingdongBE.domain.pairgame.controller.dto.response.PairGameMetaDataResponse;
import ddingdong.ddingdongBE.domain.pairgame.service.FacadeUserPairGameService;
import ddingdong.ddingdongBE.domain.pairgame.service.dto.query.PairGameApplierAmountQuery;
import ddingdong.ddingdongBE.domain.pairgame.service.dto.query.PairGameMetaDataQuery;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequiredArgsConstructor
public class UserPairGameController implements UserPairGameApi {

private final FacadeUserPairGameService facadeUserPairGameService;

@Override
public void createPairGameApplier(CreatePairGameApplierRequest createPairGameApplierRequest, MultipartFile studentFeeImageFile) {
facadeUserPairGameService.createPairGameApplier(createPairGameApplierRequest.toCommand(studentFeeImageFile));
}

@Override
public PairGameApplierAmountResponse getPairGameApplierAmount() {
PairGameApplierAmountQuery query = facadeUserPairGameService.getPairGameApplierAmount();
return PairGameApplierAmountResponse.from(query);
}

@Override
public PairGameMetaDataResponse getPairGameMetaData() {
PairGameMetaDataQuery query = facadeUserPairGameService.getPairGameMetaData();
return PairGameMetaDataResponse.from(query);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package ddingdong.ddingdongBE.domain.pairgame.controller.dto.request;

import ddingdong.ddingdongBE.domain.pairgame.service.dto.command.CreatePairGameApplierCommand;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import org.springframework.web.multipart.MultipartFile;

public record CreatePairGameApplierRequest (

@NotNull(message = "응모자 이름은 필수 입력 사항입니다.")
@Schema(description = "응모자 이름", example = "김띵동")
String name,

@NotNull(message = "응모자 학과는 필수 입력 사항입니다.")
@Schema(description = "응모자 학과", example = "융합소프트웨어학부")
String department,

@NotNull(message = "응모자 학번은 필수 입력 사항입니다.")
@Schema(description = "응모자 학번", example = "60000000")
String studentNumber,

@NotNull(message = "응모자 전화번호는 필수 입력 사항입니다.")
@Schema(description = "응모자 전화번호", example = "010-0000-0000")
String phoneNumber
) {
public CreatePairGameApplierCommand toCommand(MultipartFile studentFeeImageFile) {
return CreatePairGameApplierCommand.builder()
.name(name)
.department(department)
.studentNumber(studentNumber)
.phoneNumber(phoneNumber)
.studentFeeImageFile(studentFeeImageFile)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package ddingdong.ddingdongBE.domain.pairgame.controller.dto.response;

import ddingdong.ddingdongBE.domain.pairgame.service.dto.query.PairGameApplierAmountQuery;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;

@Builder
public record PairGameApplierAmountResponse(
@Schema(description = "총 응모자 수", example = "200")
int amount
) {
public static PairGameApplierAmountResponse from(PairGameApplierAmountQuery query) {
return PairGameApplierAmountResponse.builder()
.amount(query.amount())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package ddingdong.ddingdongBE.domain.pairgame.controller.dto.response;

import ddingdong.ddingdongBE.domain.pairgame.service.dto.query.PairGameMetaDataQuery.PairGameClubAndImageQuery;
import ddingdong.ddingdongBE.domain.pairgame.service.dto.query.PairGameMetaDataQuery;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;

import java.util.List;

@Builder
public record PairGameMetaDataResponse(
@ArraySchema(schema = @Schema(implementation = PairGameMetaDataResponse.PairGameClubAndImageResponse.class))
List<PairGameClubAndImageResponse> metaData
) {
@Builder
public record PairGameClubAndImageResponse(
@Schema(description = "동아리 이름", example = "COW")
String clubName,
@Schema(description = "동아리 분과", example = "사회연구")
String category,
@Schema(description = "동아리 로고 이미지 CDN URL", example = "https://cdn.com")
String imageUrl
) {
public static PairGameClubAndImageResponse from(PairGameClubAndImageQuery query) {
return PairGameClubAndImageResponse.builder()
.clubName(query.clubName())
.category(query.category())
.imageUrl(query.imageUrl())
.build();
}
}
public static PairGameMetaDataResponse from(PairGameMetaDataQuery query) {
List<PairGameClubAndImageResponse> responses = query.metaData().stream()
.map(PairGameClubAndImageResponse::from)
.toList();
Comment on lines +25 to +36
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

from(...) 매핑 시 null 방어(또는 계약 명시)가 필요합니다.
query 또는 query.metaData()가 null이면 NPE가 발생합니다. 서비스 계층에서 항상 non-null을 보장하지 않는다면 방어 로직(또는 명시적 검증) 추가를 고려해주세요.

🛠️ 수정 제안
 import lombok.Builder;

 import java.util.List;
+import java.util.Objects;

     ) {
         public static PairGameClubAndImageResponse from(PairGameClubAndImageQuery query) {
+            Objects.requireNonNull(query, "query must not be null");
             return PairGameClubAndImageResponse.builder()
                     .clubName(query.clubName())
                     .category(query.category())
                     .imageUrl(query.imageUrl())
                     .build();
         }
     }
     public static PairGameMetaDataResponse from(PairGameMetaDataQuery query) {
-        List<PairGameClubAndImageResponse> responses = query.metaData().stream()
-                .map(PairGameClubAndImageResponse::from)
-                .toList();
+        Objects.requireNonNull(query, "query must not be null");
+        List<PairGameClubAndImageResponse> responses =
+                query.metaData() == null ? List.of()
+                        : query.metaData().stream()
+                                .map(PairGameClubAndImageResponse::from)
+                                .toList();

         return PairGameMetaDataResponse.builder()
                 .metaData(responses)
                 .build();
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static PairGameClubAndImageResponse from(PairGameClubAndImageQuery query) {
return PairGameClubAndImageResponse.builder()
.clubName(query.clubName())
.category(query.category())
.imageUrl(query.imageUrl())
.build();
}
}
public static PairGameMetaDataResponse from(PairGameMetaDataQuery query) {
List<PairGameClubAndImageResponse> responses = query.metaData().stream()
.map(PairGameClubAndImageResponse::from)
.toList();
import lombok.Builder;
import java.util.List;
import java.util.Objects;
public static PairGameClubAndImageResponse from(PairGameClubAndImageQuery query) {
Objects.requireNonNull(query, "query must not be null");
return PairGameClubAndImageResponse.builder()
.clubName(query.clubName())
.category(query.category())
.imageUrl(query.imageUrl())
.build();
}
}
public static PairGameMetaDataResponse from(PairGameMetaDataQuery query) {
Objects.requireNonNull(query, "query must not be null");
List<PairGameClubAndImageResponse> responses =
query.metaData() == null ? List.of()
: query.metaData().stream()
.map(PairGameClubAndImageResponse::from)
.toList();
return PairGameMetaDataResponse.builder()
.metaData(responses)
.build();
}
🤖 Prompt for AI Agents
In
`@src/main/java/ddingdong/ddingdongBE/domain/pairgame/controller/dto/response/PairGameMetaDataResponse.java`
around lines 25 - 36, PairGameMetaDataResponse.from(PairGameMetaDataQuery)
currently assumes query and query.metaData() are non-null and can NPE; add
defensive checks: validate query at method start (e.g.,
Objects.requireNonNull(query, "query must not be null") or throw
IllegalArgumentException) and replace direct query.metaData() use with a
null-safe alternative (e.g., List.of() when metaData is null or
Optional.ofNullable(query.metaData()).orElseGet(List::of)) before streaming;
also add a null check inside
PairGameClubAndImageResponse.from(PairGameClubAndImageQuery) to validate its
query parameter similarly so mapping .map(PairGameClubAndImageResponse::from) is
safe.


return PairGameMetaDataResponse.builder()
.metaData(responses)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package ddingdong.ddingdongBE.domain.pairgame.entity;

import ddingdong.ddingdongBE.common.BaseEntity;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.SQLDelete;
import org.hibernate.annotations.SQLRestriction;

import java.time.LocalDateTime;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@SQLDelete(sql = "update pair_game_applier set deleted_at = CURRENT_TIMESTAMP where id=?")
@SQLRestriction("deleted_at IS NULL")
public class PairGameApplier extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, length = 50)
private String name;

@Column(nullable = false, length = 50)
private String department;

@Column(unique = true, nullable = false, length = 50)
private String studentNumber;

@Column(nullable = false, length = 50)
private String phoneNumber;

@Column(nullable = false)
private String studentFeeImageUrl;

@Column(columnDefinition = "TIMESTAMP")
private LocalDateTime deletedAt;

@Builder
private PairGameApplier(String name, String department, String studentNumber, String phoneNumber, String studentFeeImageUrl) {
this.name = name;
this.department = department;
this.studentNumber = studentNumber;
this.phoneNumber = phoneNumber;
this.studentFeeImageUrl = studentFeeImageUrl;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package ddingdong.ddingdongBE.domain.pairgame.repository;

import ddingdong.ddingdongBE.domain.pairgame.entity.PairGameApplier;
import org.springframework.data.jpa.repository.JpaRepository;


public interface PairGameRepository extends JpaRepository<PairGameApplier, Long> {
boolean existsByStudentNumber(String studentNumber);
}
Loading