From dc55fbb9700e8d3fca61aa9241357087634e27cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 26 May 2026 22:52:45 +0900 Subject: [PATCH 01/13] =?UTF-8?q?feat:=20=EA=B8=B0=EC=A1=B4=20=EB=8F=99?= =?UTF-8?q?=EC=95=84=EB=A6=AC=20=EC=A0=95=EB=B3=B4=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존 동아리 정보를 바로 변경하지 않고 운영자가 확인할 수 있도록 수정 요청을 별도 테이블에 저장한다 - 동아리 등록 요청과 같은 공개 요청 저장 및 커밋 후 Slack 알림 패턴을 사용해 운영 알림 흐름을 맞춘다 - 존재하지 않는 동아리 ID로 요청이 저장되지 않도록 대상 동아리를 먼저 조회한다 - API, 서비스, Slack 위임, 메시지 포맷 테스트를 함께 추가해 요청 저장과 알림 흐름을 검증한다 --- .../ClubRegistrationRequestApi.java | 7 ++ .../ClubRegistrationRequestController.java | 22 ++++- .../dto/ClubInformationUpdateRequestDto.java | 46 +++++++++ .../ClubInformationUpdateRequestedEvent.java | 30 ++++++ .../model/ClubInformationUpdateRequest.java | 95 +++++++++++++++++++ ...lubInformationUpdateRequestRepository.java | 9 ++ .../ClubRegistrationRequestService.java | 25 +++++ .../slack/enums/SlackMessageTemplate.java | 31 +++++- .../ClubRegistrationRequestSlackListener.java | 25 +++++ .../service/SlackNotificationService.java | 26 +++++ ...create_club_information_update_request.sql | 16 ++++ .../club/ClubRegistrationRequestApiTest.java | 60 ++++++++++++ .../ClubRegistrationRequestServiceTest.java | 61 ++++++++++++ ...bRegistrationRequestSlackListenerTest.java | 62 ++++++++++++ .../service/SlackNotificationServiceTest.java | 51 +++++++++- 15 files changed, 557 insertions(+), 9 deletions(-) create mode 100644 src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequestDto.java create mode 100644 src/main/java/gg/agit/konect/domain/club/event/ClubInformationUpdateRequestedEvent.java create mode 100644 src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java create mode 100644 src/main/java/gg/agit/konect/domain/club/repository/ClubInformationUpdateRequestRepository.java create mode 100644 src/main/resources/db/migration/V81__create_club_information_update_request.sql diff --git a/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestApi.java b/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestApi.java index 2aab33338..02d940ea8 100644 --- a/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestApi.java +++ b/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestApi.java @@ -1,7 +1,9 @@ package gg.agit.konect.domain.club.controller; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequestDto; import gg.agit.konect.domain.club.dto.ClubRegistrationRequestDto; import io.swagger.v3.oas.annotations.parameters.RequestBody; @@ -10,4 +12,9 @@ public interface ClubRegistrationRequestApi { ResponseEntity registerClub( @RequestBody ClubRegistrationRequestDto request ); + + ResponseEntity requestClubInformationUpdate( + @PathVariable(name = "clubId") Integer clubId, + @RequestBody ClubInformationUpdateRequestDto request + ); } diff --git a/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestController.java b/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestController.java index fdc00b45c..91bf98b10 100644 --- a/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestController.java +++ b/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestController.java @@ -2,11 +2,13 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequestDto; import gg.agit.konect.domain.club.dto.ClubRegistrationRequestDto; import gg.agit.konect.domain.club.service.ClubRegistrationRequestService; import gg.agit.konect.global.auth.annotation.PublicApi; @@ -24,7 +26,10 @@ public class ClubRegistrationRequestController implements ClubRegistrationReques private final ClubRegistrationRequestService clubRegistrationRequestService; @Override - @Operation(summary = "동아리 등록 요청", description = "비로그인 사용자가 새 동아리 등록을 요청합니다.") + @Operation( + summary = "동아리 등록 요청", + description = "비로그인 사용자가 새 동아리 등록을 요청합니다." + ) @PostMapping("/registration-requests") @PublicApi public ResponseEntity registerClub( @@ -33,4 +38,19 @@ public ResponseEntity registerClub( clubRegistrationRequestService.register(request); return ResponseEntity.status(HttpStatus.CREATED).build(); } + + @Override + @Operation( + summary = "동아리 정보 수정 요청", + description = "비로그인 사용자가 기존 동아리 정보 수정을 요청합니다." + ) + @PostMapping("/{clubId}/information-update-requests") + @PublicApi + public ResponseEntity requestClubInformationUpdate( + @PathVariable(name = "clubId") Integer clubId, + @Valid @RequestBody ClubInformationUpdateRequestDto request + ) { + clubRegistrationRequestService.requestInformationUpdate(clubId, request); + return ResponseEntity.status(HttpStatus.CREATED).build(); + } } diff --git a/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequestDto.java b/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequestDto.java new file mode 100644 index 000000000..60cc26d0c --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequestDto.java @@ -0,0 +1,46 @@ +package gg.agit.konect.domain.club.dto; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + +import gg.agit.konect.domain.club.enums.ClubCategory; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; + +@Schema(name = "ClubInformationUpdateRequest", description = "동아리 정보 수정 요청") +public record ClubInformationUpdateRequestDto( + + @Schema(description = "동아리 명", example = "BCSD Lab", requiredMode = REQUIRED) + @NotBlank(message = "동아리 명은 필수입니다.") + @Size(max = 50, message = "동아리 명은 최대 50자입니다.") + String clubName, + + @Schema(description = "동아리 분과", example = "ACADEMIC", requiredMode = REQUIRED) + @NotNull(message = "동아리 분과는 필수입니다.") + ClubCategory clubCategory, + + @Schema(description = "한 줄 소개", example = "코딩 동아리입니다.", requiredMode = REQUIRED) + @NotBlank(message = "한 줄 소개는 필수입니다.") + @Size(max = 25, message = "한 줄 소개는 최대 25자입니다.") + String shortDescription, + + @Schema( + description = "동아리 로고 이미지 URL", + example = "https://example.com/logo.png", + requiredMode = REQUIRED + ) + @NotBlank(message = "동아리 로고 이미지 URL은 필수입니다.") + @Size(max = 255, message = "동아리 로고 이미지 URL은 최대 255자입니다.") + String imageUrl, + + @Schema(description = "동아리 방 위치", example = "학생회관 101호", requiredMode = REQUIRED) + @NotBlank(message = "동아리 위치는 필수입니다.") + @Size(max = 255, message = "동아리 위치는 최대 255자입니다.") + String location, + + @Schema(description = "동아리 소개", example = "상세한 동아리 소개 내용...", requiredMode = REQUIRED) + @NotBlank(message = "동아리 소개는 필수입니다.") + String fullIntroduction +) { +} diff --git a/src/main/java/gg/agit/konect/domain/club/event/ClubInformationUpdateRequestedEvent.java b/src/main/java/gg/agit/konect/domain/club/event/ClubInformationUpdateRequestedEvent.java new file mode 100644 index 000000000..8a221a0b6 --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/club/event/ClubInformationUpdateRequestedEvent.java @@ -0,0 +1,30 @@ +package gg.agit.konect.domain.club.event; + +import gg.agit.konect.domain.club.model.ClubInformationUpdateRequest; + +public record ClubInformationUpdateRequestedEvent( + Integer requestId, + Integer clubId, + String currentClubName, + String requestedClubName, + String category, + String description, + String imageUrl, + String location, + String fullIntroduction +) { + + public static ClubInformationUpdateRequestedEvent from(ClubInformationUpdateRequest request) { + return new ClubInformationUpdateRequestedEvent( + request.getId(), + request.getClub().getId(), + request.getClub().getName(), + request.getClubName(), + request.getClubCategory().getDescription(), + request.getShortDescription(), + request.getImageUrl(), + request.getLocation(), + request.getFullIntroduction() + ); + } +} diff --git a/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java b/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java new file mode 100644 index 000000000..e6b361a08 --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java @@ -0,0 +1,95 @@ +package gg.agit.konect.domain.club.model; + +import static jakarta.persistence.EnumType.STRING; +import static jakarta.persistence.FetchType.LAZY; +import static jakarta.persistence.GenerationType.IDENTITY; +import static lombok.AccessLevel.PROTECTED; + +import gg.agit.konect.domain.club.enums.ClubCategory; +import gg.agit.konect.global.model.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@Table(name = "club_information_update_request") +@NoArgsConstructor(access = PROTECTED) +public class ClubInformationUpdateRequest extends BaseEntity { + + @Id + @GeneratedValue(strategy = IDENTITY) + @Column(name = "id", nullable = false, updatable = false, unique = true) + private Integer id; + + @NotNull + @ManyToOne(fetch = LAZY) + @JoinColumn(name = "club_id", nullable = false) + private Club club; + + @NotNull + @Column(name = "club_name", length = 50, nullable = false) + private String clubName; + + @NotNull + @Enumerated(value = STRING) + @Column(name = "club_category", nullable = false) + private ClubCategory clubCategory; + + @NotNull + @Column(name = "short_description", length = 25, nullable = false) + private String shortDescription; + + @NotNull + @Column(name = "image_url", length = 255, nullable = false) + private String imageUrl; + + @NotNull + @Column(name = "location", length = 255, nullable = false) + private String location; + + @NotNull + @Column(name = "full_introduction", columnDefinition = "TEXT", nullable = false) + private String fullIntroduction; + + @NotNull + @Enumerated(value = STRING) + @Column(name = "status", length = 20, nullable = false) + private UpdateRequestStatus status; + + @Builder + private ClubInformationUpdateRequest( + Integer id, + Club club, + String clubName, + ClubCategory clubCategory, + String shortDescription, + String imageUrl, + String location, + String fullIntroduction, + UpdateRequestStatus status + ) { + this.id = id; + this.club = club; + this.clubName = clubName; + this.clubCategory = clubCategory; + this.shortDescription = shortDescription; + this.imageUrl = imageUrl; + this.location = location; + this.fullIntroduction = fullIntroduction; + this.status = status != null ? status : UpdateRequestStatus.PENDING; + } + + public enum UpdateRequestStatus { + PENDING, APPROVED, REJECTED + } +} diff --git a/src/main/java/gg/agit/konect/domain/club/repository/ClubInformationUpdateRequestRepository.java b/src/main/java/gg/agit/konect/domain/club/repository/ClubInformationUpdateRequestRepository.java new file mode 100644 index 000000000..55ca66907 --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/club/repository/ClubInformationUpdateRequestRepository.java @@ -0,0 +1,9 @@ +package gg.agit.konect.domain.club.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import gg.agit.konect.domain.club.model.ClubInformationUpdateRequest; + +public interface ClubInformationUpdateRequestRepository + extends JpaRepository { +} diff --git a/src/main/java/gg/agit/konect/domain/club/service/ClubRegistrationRequestService.java b/src/main/java/gg/agit/konect/domain/club/service/ClubRegistrationRequestService.java index aa812da2b..a30cae133 100644 --- a/src/main/java/gg/agit/konect/domain/club/service/ClubRegistrationRequestService.java +++ b/src/main/java/gg/agit/konect/domain/club/service/ClubRegistrationRequestService.java @@ -4,10 +4,16 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequestDto; import gg.agit.konect.domain.club.dto.ClubRegistrationRequestDto; +import gg.agit.konect.domain.club.event.ClubInformationUpdateRequestedEvent; import gg.agit.konect.domain.club.event.ClubRegistrationRequestedEvent; +import gg.agit.konect.domain.club.model.Club; +import gg.agit.konect.domain.club.model.ClubInformationUpdateRequest; import gg.agit.konect.domain.club.model.ClubRegistrationRequest; +import gg.agit.konect.domain.club.repository.ClubInformationUpdateRequestRepository; import gg.agit.konect.domain.club.repository.ClubRegistrationRequestRepository; +import gg.agit.konect.domain.club.repository.ClubRepository; import lombok.RequiredArgsConstructor; @Service @@ -16,6 +22,8 @@ public class ClubRegistrationRequestService { private final ClubRegistrationRequestRepository clubRegistrationRequestRepository; + private final ClubInformationUpdateRequestRepository clubInformationUpdateRequestRepository; + private final ClubRepository clubRepository; private final ApplicationEventPublisher applicationEventPublisher; public void register(ClubRegistrationRequestDto request) { @@ -38,4 +46,21 @@ public void register(ClubRegistrationRequestDto request) { ClubRegistrationRequest saved = clubRegistrationRequestRepository.save(entity); applicationEventPublisher.publishEvent(ClubRegistrationRequestedEvent.from(saved)); } + + public void requestInformationUpdate(Integer clubId, ClubInformationUpdateRequestDto request) { + Club club = clubRepository.getById(clubId); + ClubInformationUpdateRequest entity = ClubInformationUpdateRequest.builder() + .club(club) + .clubName(request.clubName()) + .clubCategory(request.clubCategory()) + .shortDescription(request.shortDescription()) + .imageUrl(request.imageUrl()) + .location(request.location()) + .fullIntroduction(request.fullIntroduction()) + .status(ClubInformationUpdateRequest.UpdateRequestStatus.PENDING) + .build(); + + ClubInformationUpdateRequest saved = clubInformationUpdateRequestRepository.save(entity); + applicationEventPublisher.publishEvent(ClubInformationUpdateRequestedEvent.from(saved)); + } } diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java b/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java index f57f65109..621473833 100644 --- a/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java +++ b/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java @@ -41,23 +41,46 @@ public enum SlackMessageTemplate { CLUB_REGISTRATION_REQUEST( """ :sparkles: *새 동아리 등록 요청이 도착했어요* - + :school: *대학교* : *`%s`* %s *동아리* : *`%s`* :label: *분과* : *`%s`* :dart: *주제* : *`%s`* :art: *요청 이모지* : *`%s`* - + :memo: *한 줄 소개* ```%s``` - + :page_facing_up: *상세 소개* ```%s``` - + :paperclip: *첨부 이미지* ```%s``` """ ), + CLUB_INFORMATION_UPDATE_REQUEST( + """ + :pencil2: *동아리 정보 수정 요청이 도착했어요* + + :receipt: *요청 ID* : *`%s`* + :id: *동아리 ID* : *`%s`* + :bookmark: *현재 동아리명* : *`%s`* + :bookmark_tabs: *요청 동아리명* : *`%s`* + :label: *요청 분과* : *`%s`* + + :memo: *요청 한 줄 소개* + ```%s``` + + :frame_with_picture: *요청 로고 이미지* + ```%s``` + + :round_pushpin: *요청 위치* + ```%s``` + + :page_facing_up: *요청 상세 소개* + ```%s``` + """ + ), ; private final String template; diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/listener/ClubRegistrationRequestSlackListener.java b/src/main/java/gg/agit/konect/infrastructure/slack/listener/ClubRegistrationRequestSlackListener.java index 6a15dfdb0..4c18c5dfa 100644 --- a/src/main/java/gg/agit/konect/infrastructure/slack/listener/ClubRegistrationRequestSlackListener.java +++ b/src/main/java/gg/agit/konect/infrastructure/slack/listener/ClubRegistrationRequestSlackListener.java @@ -6,6 +6,7 @@ import org.springframework.stereotype.Component; import org.springframework.transaction.event.TransactionalEventListener; +import gg.agit.konect.domain.club.event.ClubInformationUpdateRequestedEvent; import gg.agit.konect.domain.club.event.ClubRegistrationRequestedEvent; import gg.agit.konect.infrastructure.slack.service.SlackNotificationService; import lombok.RequiredArgsConstructor; @@ -37,4 +38,28 @@ public void handleClubRegistrationRequested(ClubRegistrationRequestedEvent event log.warn("Failed to send club registration request Slack notification. requestId={}", event.requestId(), e); } } + + @Async("slackTaskExecutor") + @TransactionalEventListener(phase = AFTER_COMMIT) + public void handleClubInformationUpdateRequested(ClubInformationUpdateRequestedEvent event) { + try { + slackNotificationService.notifyClubInformationUpdateRequest( + event.requestId(), + event.clubId(), + event.currentClubName(), + event.requestedClubName(), + event.category(), + event.description(), + event.imageUrl(), + event.location(), + event.fullIntroduction() + ); + } catch (RuntimeException e) { + log.warn( + "Failed to send club information update request Slack notification. requestId={}", + event.requestId(), + e + ); + } + } } diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java b/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java index c2342ec15..8d57cd800 100644 --- a/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java +++ b/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java @@ -1,6 +1,7 @@ package gg.agit.konect.infrastructure.slack.service; import static gg.agit.konect.infrastructure.slack.enums.SlackMessageTemplate.ADMIN_CHAT_RECEIVED; +import static gg.agit.konect.infrastructure.slack.enums.SlackMessageTemplate.CLUB_INFORMATION_UPDATE_REQUEST; import static gg.agit.konect.infrastructure.slack.enums.SlackMessageTemplate.CLUB_REGISTRATION_REQUEST; import static gg.agit.konect.infrastructure.slack.enums.SlackMessageTemplate.INQUIRY; import static gg.agit.konect.infrastructure.slack.enums.SlackMessageTemplate.SHEET_SYNC_FAILED; @@ -79,6 +80,31 @@ public void notifyClubRegistrationRequest( slackClient.sendMessage(message, slackProperties.webhooks().event()); } + public void notifyClubInformationUpdateRequest( + Integer requestId, + Integer clubId, + String currentClubName, + String requestedClubName, + String category, + String description, + String imageUrl, + String location, + String fullIntroduction + ) { + String message = CLUB_INFORMATION_UPDATE_REQUEST.format( + requestId, + clubId, + currentClubName, + requestedClubName, + category, + description, + imageUrl, + location, + fullIntroduction + ); + slackClient.sendMessage(message, slackProperties.webhooks().event()); + } + private String formatImageUrls(List imageUrls) { if (imageUrls.isEmpty()) { return "없음"; diff --git a/src/main/resources/db/migration/V81__create_club_information_update_request.sql b/src/main/resources/db/migration/V81__create_club_information_update_request.sql new file mode 100644 index 000000000..baee9033b --- /dev/null +++ b/src/main/resources/db/migration/V81__create_club_information_update_request.sql @@ -0,0 +1,16 @@ +CREATE TABLE IF NOT EXISTS club_information_update_request +( + id INT AUTO_INCREMENT PRIMARY KEY, + club_id INT NOT NULL COMMENT '수정 요청 대상 동아리 ID', + club_name VARCHAR(50) NOT NULL COMMENT '요청 동아리 명', + club_category VARCHAR(255) NOT NULL COMMENT '요청 동아리 분과', + short_description VARCHAR(25) NOT NULL COMMENT '요청 한 줄 소개', + image_url VARCHAR(255) NOT NULL COMMENT '요청 동아리 로고 이미지 URL', + location VARCHAR(255) NOT NULL COMMENT '요청 동아리 위치', + full_introduction TEXT NOT NULL COMMENT '요청 동아리 소개', + status VARCHAR(20) DEFAULT 'PENDING' NOT NULL COMMENT '요청 상태 (PENDING, APPROVED, REJECTED)', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL, + + FOREIGN KEY (club_id) REFERENCES club (id) +) COMMENT '동아리 정보 수정 요청'; diff --git a/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java b/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java index 808bfd630..d646f9f18 100644 --- a/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java +++ b/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java @@ -7,9 +7,14 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequestDto; import gg.agit.konect.domain.club.dto.ClubRegistrationRequestDto; import gg.agit.konect.domain.club.enums.ClubCategory; +import gg.agit.konect.domain.club.model.Club; +import gg.agit.konect.domain.university.model.University; import gg.agit.konect.support.IntegrationTestSupport; +import gg.agit.konect.support.fixture.ClubFixture; +import gg.agit.konect.support.fixture.UniversityFixture; class ClubRegistrationRequestApiTest extends IntegrationTestSupport { @@ -120,4 +125,59 @@ void registerClubWithLongIntroduction() throws Exception { performPost("/clubs/registration-requests", request) .andExpect(status().isBadRequest()); } + + @Test + @DisplayName("비로그인 사용자도 기존 동아리 정보 수정 요청을 보낼 수 있다") + void requestClubInformationUpdateWithoutLogin() throws Exception { + // given + University university = persist(UniversityFixture.create()); + Club club = persist(ClubFixture.create(university)); + ClubInformationUpdateRequestDto request = createInformationUpdateRequest(); + + // when & then + performPost("/clubs/" + club.getId() + "/information-update-requests", request) + .andExpect(status().isCreated()); + } + + @Test + @DisplayName("존재하지 않는 동아리에 대한 정보 수정 요청은 404를 반환한다") + void requestClubInformationUpdateWithUnknownClub() throws Exception { + // given + ClubInformationUpdateRequestDto request = createInformationUpdateRequest(); + + // when & then + performPost("/clubs/999999/information-update-requests", request) + .andExpect(status().isNotFound()); + } + + @Test + @DisplayName("동아리 정보 수정 요청 필수값이 없으면 400을 반환한다") + void requestClubInformationUpdateWithMissingFields() throws Exception { + // given + University university = persist(UniversityFixture.create()); + Club club = persist(ClubFixture.create(university)); + ClubInformationUpdateRequestDto request = new ClubInformationUpdateRequestDto( + "", + ClubCategory.ACADEMIC, + "수정 소개", + "https://example.com/logo.png", + "학생회관 102호", + "수정 상세 소개입니다." + ); + + // when & then + performPost("/clubs/" + club.getId() + "/information-update-requests", request) + .andExpect(status().isBadRequest()); + } + + private ClubInformationUpdateRequestDto createInformationUpdateRequest() { + return new ClubInformationUpdateRequestDto( + "BCSD Lab", + ClubCategory.ACADEMIC, + "수정 소개", + "https://example.com/logo.png", + "학생회관 102호", + "수정 상세 소개입니다." + ); + } } diff --git a/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java b/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java index 8d441fac4..fe343fef8 100644 --- a/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java +++ b/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java @@ -14,19 +14,33 @@ import org.mockito.Mock; import org.springframework.context.ApplicationEventPublisher; +import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequestDto; import gg.agit.konect.domain.club.dto.ClubRegistrationRequestDto; import gg.agit.konect.domain.club.enums.ClubCategory; +import gg.agit.konect.domain.club.event.ClubInformationUpdateRequestedEvent; import gg.agit.konect.domain.club.event.ClubRegistrationRequestedEvent; +import gg.agit.konect.domain.club.model.Club; +import gg.agit.konect.domain.club.model.ClubInformationUpdateRequest; import gg.agit.konect.domain.club.model.ClubRegistrationRequest; +import gg.agit.konect.domain.club.repository.ClubInformationUpdateRequestRepository; import gg.agit.konect.domain.club.repository.ClubRegistrationRequestRepository; +import gg.agit.konect.domain.club.repository.ClubRepository; import gg.agit.konect.domain.club.service.ClubRegistrationRequestService; import gg.agit.konect.support.ServiceTestSupport; +import gg.agit.konect.support.fixture.ClubFixture; +import gg.agit.konect.support.fixture.UniversityFixture; class ClubRegistrationRequestServiceTest extends ServiceTestSupport { @Mock private ClubRegistrationRequestRepository clubRegistrationRequestRepository; + @Mock + private ClubInformationUpdateRequestRepository clubInformationUpdateRequestRepository; + + @Mock + private ClubRepository clubRepository; + @Mock private ApplicationEventPublisher applicationEventPublisher; @@ -81,4 +95,51 @@ void registerPublishesClubRegistrationRequestedEvent() { assertThat(event.imageUrls()).containsExactlyElementsOf(request.imageUrls()); assertThat(event.imageCount()).isEqualTo(1); } + + @Test + @DisplayName("동아리 정보 수정 요청 저장 후 Slack 알림 이벤트를 발행한다") + void requestInformationUpdatePublishesClubInformationUpdateRequestedEvent() { + // given + Club club = ClubFixture.createWithId(UniversityFixture.createWithId(1), 1, "현재 동아리명"); + ClubInformationUpdateRequestDto request = new ClubInformationUpdateRequestDto( + "요청 동아리명", + ClubCategory.ACADEMIC, + "수정 소개", + "https://example.com/logo.png", + "학생회관 102호", + "수정 상세 소개입니다." + ); + ClubInformationUpdateRequest saved = ClubInformationUpdateRequest.builder() + .id(10) + .club(club) + .clubName(request.clubName()) + .clubCategory(request.clubCategory()) + .shortDescription(request.shortDescription()) + .imageUrl(request.imageUrl()) + .location(request.location()) + .fullIntroduction(request.fullIntroduction()) + .build(); + given(clubRepository.getById(club.getId())).willReturn(club); + given(clubInformationUpdateRequestRepository.save(any(ClubInformationUpdateRequest.class))).willReturn(saved); + + // when + clubRegistrationRequestService.requestInformationUpdate(club.getId(), request); + + // then + ArgumentCaptor eventCaptor = ArgumentCaptor.forClass( + ClubInformationUpdateRequestedEvent.class + ); + verify(applicationEventPublisher).publishEvent(eventCaptor.capture()); + + ClubInformationUpdateRequestedEvent event = eventCaptor.getValue(); + assertThat(event.requestId()).isEqualTo(saved.getId()); + assertThat(event.clubId()).isEqualTo(club.getId()); + assertThat(event.currentClubName()).isEqualTo(club.getName()); + assertThat(event.requestedClubName()).isEqualTo(request.clubName()); + assertThat(event.category()).isEqualTo(request.clubCategory().getDescription()); + assertThat(event.description()).isEqualTo(request.shortDescription()); + assertThat(event.imageUrl()).isEqualTo(request.imageUrl()); + assertThat(event.location()).isEqualTo(request.location()); + assertThat(event.fullIntroduction()).isEqualTo(request.fullIntroduction()); + } } diff --git a/src/test/java/gg/agit/konect/unit/infrastructure/slack/listener/ClubRegistrationRequestSlackListenerTest.java b/src/test/java/gg/agit/konect/unit/infrastructure/slack/listener/ClubRegistrationRequestSlackListenerTest.java index 8335e18d6..a04a3217f 100644 --- a/src/test/java/gg/agit/konect/unit/infrastructure/slack/listener/ClubRegistrationRequestSlackListenerTest.java +++ b/src/test/java/gg/agit/konect/unit/infrastructure/slack/listener/ClubRegistrationRequestSlackListenerTest.java @@ -11,6 +11,7 @@ import org.mockito.InjectMocks; import org.mockito.Mock; +import gg.agit.konect.domain.club.event.ClubInformationUpdateRequestedEvent; import gg.agit.konect.domain.club.event.ClubRegistrationRequestedEvent; import gg.agit.konect.infrastructure.slack.listener.ClubRegistrationRequestSlackListener; import gg.agit.konect.infrastructure.slack.service.SlackNotificationService; @@ -71,6 +72,53 @@ void handleClubRegistrationRequestedSwallowsExceptions() { .doesNotThrowAnyException(); } + @Test + @DisplayName("동아리 정보 수정 요청 이벤트를 Slack 알림 서비스에 위임한다") + void handleClubInformationUpdateRequestedDelegatesToSlackService() { + // given + ClubInformationUpdateRequestedEvent event = createInformationUpdateEvent(); + + // when + clubRegistrationRequestSlackListener.handleClubInformationUpdateRequested(event); + + // then + verify(slackNotificationService).notifyClubInformationUpdateRequest( + event.requestId(), + event.clubId(), + event.currentClubName(), + event.requestedClubName(), + event.category(), + event.description(), + event.imageUrl(), + event.location(), + event.fullIntroduction() + ); + } + + @Test + @DisplayName("동아리 정보 수정 요청 Slack 알림 실패를 삼킨다") + void handleClubInformationUpdateRequestedSwallowsExceptions() { + // given + ClubInformationUpdateRequestedEvent event = createInformationUpdateEvent(); + doThrow(new RuntimeException("slack error")) + .when(slackNotificationService) + .notifyClubInformationUpdateRequest( + event.requestId(), + event.clubId(), + event.currentClubName(), + event.requestedClubName(), + event.category(), + event.description(), + event.imageUrl(), + event.location(), + event.fullIntroduction() + ); + + // when & then + assertThatCode(() -> clubRegistrationRequestSlackListener.handleClubInformationUpdateRequested(event)) + .doesNotThrowAnyException(); + } + private ClubRegistrationRequestedEvent createEvent() { return new ClubRegistrationRequestedEvent( 1, @@ -84,4 +132,18 @@ private ClubRegistrationRequestedEvent createEvent() { List.of("https://example.com/image1.jpg") ); } + + private ClubInformationUpdateRequestedEvent createInformationUpdateEvent() { + return new ClubInformationUpdateRequestedEvent( + 1, + 2, + "현재 동아리명", + "요청 동아리명", + "학술", + "수정 소개", + "https://example.com/logo.png", + "학생회관 102호", + "수정 상세 소개 내용입니다." + ); + } } diff --git a/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java b/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java index d0d5efcb3..c8e3d004c 100644 --- a/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java +++ b/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java @@ -59,19 +59,19 @@ void notifyClubRegistrationRequestFormatsSlackMessageWithMarkdownAndEmoji() { assertThat(messageCaptor.getValue()).isEqualTo( """ :sparkles: *새 동아리 등록 요청이 도착했어요* - + :school: *대학교* : *`한국기술교육대학교`* 💻 *동아리* : *`BCSD Lab`* :label: *분과* : *`학술`* :dart: *주제* : *`코딩`* :art: *요청 이모지* : *`💻`* - + :memo: *한 줄 소개* ```코딩 동아리입니다.``` - + :page_facing_up: *상세 소개* ```상세한 동아리 소개 내용입니다.``` - + :paperclip: *첨부 이미지* ```https://example.com/image1.jpg https://example.com/image2.jpg``` @@ -79,4 +79,47 @@ void notifyClubRegistrationRequestFormatsSlackMessageWithMarkdownAndEmoji() { ); } + @Test + @DisplayName("동아리 정보 수정 요청 Slack 메시지를 마크다운과 이모지로 구성한다") + void notifyClubInformationUpdateRequestFormatsSlackMessageWithMarkdownAndEmoji() { + // when + slackNotificationService.notifyClubInformationUpdateRequest( + 1, + 2, + "현재 동아리명", + "요청 동아리명", + "학술", + "수정 소개", + "https://example.com/logo.png", + "학생회관 102호", + "수정 상세 소개 내용입니다." + ); + + // then + ArgumentCaptor messageCaptor = ArgumentCaptor.forClass(String.class); + verify(slackClient).sendMessage(messageCaptor.capture(), eq(EVENT_WEBHOOK_URL)); + assertThat(messageCaptor.getValue()).isEqualTo( + """ + :pencil2: *동아리 정보 수정 요청이 도착했어요* + + :receipt: *요청 ID* : *`1`* + :id: *동아리 ID* : *`2`* + :bookmark: *현재 동아리명* : *`현재 동아리명`* + :bookmark_tabs: *요청 동아리명* : *`요청 동아리명`* + :label: *요청 분과* : *`학술`* + + :memo: *요청 한 줄 소개* + ```수정 소개``` + + :frame_with_picture: *요청 로고 이미지* + ```https://example.com/logo.png``` + + :round_pushpin: *요청 위치* + ```학생회관 102호``` + + :page_facing_up: *요청 상세 소개* + ```수정 상세 소개 내용입니다.``` + """ + ); + } } From d44f4c7562a5425cf36f3a3f1bff6ebfd033aae6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 26 May 2026 22:52:56 +0900 Subject: [PATCH 02/13] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=20=ED=8F=AC?= =?UTF-8?q?=EB=A7=B7=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../slack/enums/SlackMessageTemplate.java | 18 +++++++++--------- .../service/SlackNotificationServiceTest.java | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java b/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java index 621473833..6397c9053 100644 --- a/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java +++ b/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java @@ -41,19 +41,19 @@ public enum SlackMessageTemplate { CLUB_REGISTRATION_REQUEST( """ :sparkles: *새 동아리 등록 요청이 도착했어요* - + :school: *대학교* : *`%s`* %s *동아리* : *`%s`* :label: *분과* : *`%s`* :dart: *주제* : *`%s`* :art: *요청 이모지* : *`%s`* - + :memo: *한 줄 소개* ```%s``` - + :page_facing_up: *상세 소개* ```%s``` - + :paperclip: *첨부 이미지* ```%s``` """ @@ -61,22 +61,22 @@ public enum SlackMessageTemplate { CLUB_INFORMATION_UPDATE_REQUEST( """ :pencil2: *동아리 정보 수정 요청이 도착했어요* - + :receipt: *요청 ID* : *`%s`* :id: *동아리 ID* : *`%s`* :bookmark: *현재 동아리명* : *`%s`* :bookmark_tabs: *요청 동아리명* : *`%s`* :label: *요청 분과* : *`%s`* - + :memo: *요청 한 줄 소개* ```%s``` - + :frame_with_picture: *요청 로고 이미지* ```%s``` - + :round_pushpin: *요청 위치* ```%s``` - + :page_facing_up: *요청 상세 소개* ```%s``` """ diff --git a/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java b/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java index c8e3d004c..e54400a8e 100644 --- a/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java +++ b/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java @@ -59,19 +59,19 @@ void notifyClubRegistrationRequestFormatsSlackMessageWithMarkdownAndEmoji() { assertThat(messageCaptor.getValue()).isEqualTo( """ :sparkles: *새 동아리 등록 요청이 도착했어요* - + :school: *대학교* : *`한국기술교육대학교`* 💻 *동아리* : *`BCSD Lab`* :label: *분과* : *`학술`* :dart: *주제* : *`코딩`* :art: *요청 이모지* : *`💻`* - + :memo: *한 줄 소개* ```코딩 동아리입니다.``` - + :page_facing_up: *상세 소개* ```상세한 동아리 소개 내용입니다.``` - + :paperclip: *첨부 이미지* ```https://example.com/image1.jpg https://example.com/image2.jpg``` @@ -101,22 +101,22 @@ void notifyClubInformationUpdateRequestFormatsSlackMessageWithMarkdownAndEmoji() assertThat(messageCaptor.getValue()).isEqualTo( """ :pencil2: *동아리 정보 수정 요청이 도착했어요* - + :receipt: *요청 ID* : *`1`* :id: *동아리 ID* : *`2`* :bookmark: *현재 동아리명* : *`현재 동아리명`* :bookmark_tabs: *요청 동아리명* : *`요청 동아리명`* :label: *요청 분과* : *`학술`* - + :memo: *요청 한 줄 소개* ```수정 소개``` - + :frame_with_picture: *요청 로고 이미지* ```https://example.com/logo.png``` - + :round_pushpin: *요청 위치* ```학생회관 102호``` - + :page_facing_up: *요청 상세 소개* ```수정 상세 소개 내용입니다.``` """ From 15a12e288de23731202eb97398fa66cf2f1a1920 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 26 May 2026 22:58:56 +0900 Subject: [PATCH 03/13] =?UTF-8?q?fix:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=9A=94=EC=B2=AD=20=EC=95=8C=EB=A6=BC?= =?UTF-8?q?=EC=97=90=20=EB=B3=80=EA=B2=BD=20=EC=A0=84=ED=9B=84=20=EA=B0=92?= =?UTF-8?q?=EC=9D=84=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 운영자가 수정 요청을 검토할 때 기존 값과 요청 값을 바로 비교할 수 있도록 Slack 이벤트 payload를 확장한다 - 동아리명뿐 아니라 분과, 한 줄 소개, 이미지, 위치, 상세 소개를 모두 현재 값에서 요청 값으로 표시한다 - 요청 저장 구조는 유지해 실제 동아리 정보가 즉시 변경되지 않는 기존 승인 흐름을 보존한다 - 이벤트 매핑과 Slack 메시지 포맷 테스트를 함께 수정해 비교 표시가 깨지지 않도록 한다 --- .../ClubInformationUpdateRequestedEvent.java | 20 ++++++++--- .../slack/enums/SlackMessageTemplate.java | 21 +++++++---- .../ClubRegistrationRequestSlackListener.java | 15 +++++--- .../service/SlackNotificationService.java | 30 ++++++++++------ .../ClubRegistrationRequestServiceTest.java | 15 +++++--- ...bRegistrationRequestSlackListenerTest.java | 35 +++++++++++++------ .../service/SlackNotificationServiceTest.java | 26 ++++++++++---- 7 files changed, 113 insertions(+), 49 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/club/event/ClubInformationUpdateRequestedEvent.java b/src/main/java/gg/agit/konect/domain/club/event/ClubInformationUpdateRequestedEvent.java index 8a221a0b6..df95b92f8 100644 --- a/src/main/java/gg/agit/konect/domain/club/event/ClubInformationUpdateRequestedEvent.java +++ b/src/main/java/gg/agit/konect/domain/club/event/ClubInformationUpdateRequestedEvent.java @@ -7,11 +7,16 @@ public record ClubInformationUpdateRequestedEvent( Integer clubId, String currentClubName, String requestedClubName, - String category, - String description, - String imageUrl, - String location, - String fullIntroduction + String currentCategory, + String requestedCategory, + String currentDescription, + String requestedDescription, + String currentImageUrl, + String requestedImageUrl, + String currentLocation, + String requestedLocation, + String currentFullIntroduction, + String requestedFullIntroduction ) { public static ClubInformationUpdateRequestedEvent from(ClubInformationUpdateRequest request) { @@ -20,10 +25,15 @@ public static ClubInformationUpdateRequestedEvent from(ClubInformationUpdateRequ request.getClub().getId(), request.getClub().getName(), request.getClubName(), + request.getClub().getClubCategory().getDescription(), request.getClubCategory().getDescription(), + request.getClub().getDescription(), request.getShortDescription(), + request.getClub().getImageUrl(), request.getImageUrl(), + request.getClub().getLocation(), request.getLocation(), + request.getClub().getIntroduce(), request.getFullIntroduction() ); } diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java b/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java index 6397c9053..7e4af90b5 100644 --- a/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java +++ b/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java @@ -64,20 +64,27 @@ public enum SlackMessageTemplate { :receipt: *요청 ID* : *`%s`* :id: *동아리 ID* : *`%s`* - :bookmark: *현재 동아리명* : *`%s`* - :bookmark_tabs: *요청 동아리명* : *`%s`* - :label: *요청 분과* : *`%s`* + :bookmark: *동아리명* : *`%s`* → *`%s`* + :label: *분과* : *`%s`* → *`%s`* - :memo: *요청 한 줄 소개* + :memo: *한 줄 소개* + ```%s``` + → ```%s``` - :frame_with_picture: *요청 로고 이미지* + :frame_with_picture: *로고 이미지* + ```%s``` + → ```%s``` - :round_pushpin: *요청 위치* + :round_pushpin: *위치* + ```%s``` + → ```%s``` - :page_facing_up: *요청 상세 소개* + :page_facing_up: *상세 소개* + ```%s``` + → ```%s``` """ ), diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/listener/ClubRegistrationRequestSlackListener.java b/src/main/java/gg/agit/konect/infrastructure/slack/listener/ClubRegistrationRequestSlackListener.java index 4c18c5dfa..9a7972f59 100644 --- a/src/main/java/gg/agit/konect/infrastructure/slack/listener/ClubRegistrationRequestSlackListener.java +++ b/src/main/java/gg/agit/konect/infrastructure/slack/listener/ClubRegistrationRequestSlackListener.java @@ -48,11 +48,16 @@ public void handleClubInformationUpdateRequested(ClubInformationUpdateRequestedE event.clubId(), event.currentClubName(), event.requestedClubName(), - event.category(), - event.description(), - event.imageUrl(), - event.location(), - event.fullIntroduction() + event.currentCategory(), + event.requestedCategory(), + event.currentDescription(), + event.requestedDescription(), + event.currentImageUrl(), + event.requestedImageUrl(), + event.currentLocation(), + event.requestedLocation(), + event.currentFullIntroduction(), + event.requestedFullIntroduction() ); } catch (RuntimeException e) { log.warn( diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java b/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java index 8d57cd800..a74dbe544 100644 --- a/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java +++ b/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java @@ -85,22 +85,32 @@ public void notifyClubInformationUpdateRequest( Integer clubId, String currentClubName, String requestedClubName, - String category, - String description, - String imageUrl, - String location, - String fullIntroduction + String currentCategory, + String requestedCategory, + String currentDescription, + String requestedDescription, + String currentImageUrl, + String requestedImageUrl, + String currentLocation, + String requestedLocation, + String currentFullIntroduction, + String requestedFullIntroduction ) { String message = CLUB_INFORMATION_UPDATE_REQUEST.format( requestId, clubId, currentClubName, requestedClubName, - category, - description, - imageUrl, - location, - fullIntroduction + currentCategory, + requestedCategory, + currentDescription, + requestedDescription, + currentImageUrl, + requestedImageUrl, + currentLocation, + requestedLocation, + currentFullIntroduction, + requestedFullIntroduction ); slackClient.sendMessage(message, slackProperties.webhooks().event()); } diff --git a/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java b/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java index fe343fef8..33a87a990 100644 --- a/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java +++ b/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java @@ -136,10 +136,15 @@ void requestInformationUpdatePublishesClubInformationUpdateRequestedEvent() { assertThat(event.clubId()).isEqualTo(club.getId()); assertThat(event.currentClubName()).isEqualTo(club.getName()); assertThat(event.requestedClubName()).isEqualTo(request.clubName()); - assertThat(event.category()).isEqualTo(request.clubCategory().getDescription()); - assertThat(event.description()).isEqualTo(request.shortDescription()); - assertThat(event.imageUrl()).isEqualTo(request.imageUrl()); - assertThat(event.location()).isEqualTo(request.location()); - assertThat(event.fullIntroduction()).isEqualTo(request.fullIntroduction()); + assertThat(event.currentCategory()).isEqualTo(club.getClubCategory().getDescription()); + assertThat(event.requestedCategory()).isEqualTo(request.clubCategory().getDescription()); + assertThat(event.currentDescription()).isEqualTo(club.getDescription()); + assertThat(event.requestedDescription()).isEqualTo(request.shortDescription()); + assertThat(event.currentImageUrl()).isEqualTo(club.getImageUrl()); + assertThat(event.requestedImageUrl()).isEqualTo(request.imageUrl()); + assertThat(event.currentLocation()).isEqualTo(club.getLocation()); + assertThat(event.requestedLocation()).isEqualTo(request.location()); + assertThat(event.currentFullIntroduction()).isEqualTo(club.getIntroduce()); + assertThat(event.requestedFullIntroduction()).isEqualTo(request.fullIntroduction()); } } diff --git a/src/test/java/gg/agit/konect/unit/infrastructure/slack/listener/ClubRegistrationRequestSlackListenerTest.java b/src/test/java/gg/agit/konect/unit/infrastructure/slack/listener/ClubRegistrationRequestSlackListenerTest.java index a04a3217f..f373ce7f3 100644 --- a/src/test/java/gg/agit/konect/unit/infrastructure/slack/listener/ClubRegistrationRequestSlackListenerTest.java +++ b/src/test/java/gg/agit/konect/unit/infrastructure/slack/listener/ClubRegistrationRequestSlackListenerTest.java @@ -87,11 +87,16 @@ void handleClubInformationUpdateRequestedDelegatesToSlackService() { event.clubId(), event.currentClubName(), event.requestedClubName(), - event.category(), - event.description(), - event.imageUrl(), - event.location(), - event.fullIntroduction() + event.currentCategory(), + event.requestedCategory(), + event.currentDescription(), + event.requestedDescription(), + event.currentImageUrl(), + event.requestedImageUrl(), + event.currentLocation(), + event.requestedLocation(), + event.currentFullIntroduction(), + event.requestedFullIntroduction() ); } @@ -107,11 +112,16 @@ void handleClubInformationUpdateRequestedSwallowsExceptions() { event.clubId(), event.currentClubName(), event.requestedClubName(), - event.category(), - event.description(), - event.imageUrl(), - event.location(), - event.fullIntroduction() + event.currentCategory(), + event.requestedCategory(), + event.currentDescription(), + event.requestedDescription(), + event.currentImageUrl(), + event.requestedImageUrl(), + event.currentLocation(), + event.requestedLocation(), + event.currentFullIntroduction(), + event.requestedFullIntroduction() ); // when & then @@ -139,10 +149,15 @@ private ClubInformationUpdateRequestedEvent createInformationUpdateEvent() { 2, "현재 동아리명", "요청 동아리명", + "문화", "학술", + "현재 소개", "수정 소개", + "https://example.com/current-logo.png", "https://example.com/logo.png", + "학생회관 101호", "학생회관 102호", + "현재 상세 소개 내용입니다.", "수정 상세 소개 내용입니다." ); } diff --git a/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java b/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java index e54400a8e..2913aec06 100644 --- a/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java +++ b/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java @@ -88,10 +88,15 @@ void notifyClubInformationUpdateRequestFormatsSlackMessageWithMarkdownAndEmoji() 2, "현재 동아리명", "요청 동아리명", + "문화", "학술", + "현재 소개", "수정 소개", + "https://example.com/current-logo.png", "https://example.com/logo.png", + "학생회관 101호", "학생회관 102호", + "현재 상세 소개 내용입니다.", "수정 상세 소개 내용입니다." ); @@ -104,20 +109,27 @@ void notifyClubInformationUpdateRequestFormatsSlackMessageWithMarkdownAndEmoji() :receipt: *요청 ID* : *`1`* :id: *동아리 ID* : *`2`* - :bookmark: *현재 동아리명* : *`현재 동아리명`* - :bookmark_tabs: *요청 동아리명* : *`요청 동아리명`* - :label: *요청 분과* : *`학술`* + :bookmark: *동아리명* : *`현재 동아리명`* → *`요청 동아리명`* + :label: *분과* : *`문화`* → *`학술`* - :memo: *요청 한 줄 소개* + :memo: *한 줄 소개* + ```현재 소개``` + → ```수정 소개``` - :frame_with_picture: *요청 로고 이미지* + :frame_with_picture: *로고 이미지* + ```https://example.com/current-logo.png``` + → ```https://example.com/logo.png``` - :round_pushpin: *요청 위치* + :round_pushpin: *위치* + ```학생회관 101호``` + → ```학생회관 102호``` - :page_facing_up: *요청 상세 소개* + :page_facing_up: *상세 소개* + ```현재 상세 소개 내용입니다.``` + → ```수정 상세 소개 내용입니다.``` """ ); From e7d8ec2a8b0a0c0dc14ddb6449d32a1957fa18bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 26 May 2026 23:00:36 +0900 Subject: [PATCH 04/13] =?UTF-8?q?fix:=20=EC=88=98=EC=A0=95=20=EC=9A=94?= =?UTF-8?q?=EC=B2=AD=20builder=20=EC=B6=9C=EB=A0=A5=EC=97=90=EC=84=9C=20cl?= =?UTF-8?q?ub=20=EC=B0=B8=EC=A1=B0=20=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Lombok builder 출력에 연관 엔티티의 기본 Object 문자열이 포함되지 않도록 club 필드를 제외한다 - 엔티티 매핑과 요청 저장 흐름은 그대로 유지해 리뷰 지적 범위만 최소 수정한다 - 디버그 문자열에서 의미 없는 객체 참조가 노출되는 상태를 방지한다 --- .../konect/domain/club/model/ClubInformationUpdateRequest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java b/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java index e6b361a08..d3045fe2a 100644 --- a/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java +++ b/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java @@ -19,6 +19,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.ToString; @Getter @Entity @@ -32,6 +33,7 @@ public class ClubInformationUpdateRequest extends BaseEntity { private Integer id; @NotNull + @ToString.Exclude @ManyToOne(fetch = LAZY) @JoinColumn(name = "club_id", nullable = false) private Club club; From 0e6047f1e073fa28928adc9a2f327f0870784c3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 26 May 2026 23:09:31 +0900 Subject: [PATCH 05/13] =?UTF-8?q?fix:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=9A=94=EC=B2=AD=20=EB=B9=84=EA=B5=90=20?= =?UTF-8?q?=EA=B8=B0=EC=A4=80=EC=9D=84=20=EC=9B=B9=20=EB=8F=99=EC=95=84?= =?UTF-8?q?=EB=A6=AC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 수정 요청에서 운영자가 확인하는 현재값을 앱용 club이 아니라 웹 노출용 web_club 기준으로 조회한다 - 요청 저장 FK도 web_club을 바라보게 맞춰 비교 대상과 저장 대상의 의미가 어긋나지 않도록 한다 - 기존 요청 저장과 Slack 알림 흐름은 유지하고 비교 기준만 웹 동아리 데이터로 좁힌다 - 통합 테스트와 서비스 테스트를 web_club fixture 기반으로 바꿔 API path의 clubId 해석을 고정한다 --- .../model/ClubInformationUpdateRequest.java | 7 ++++--- .../ClubRegistrationRequestService.java | 11 ++++++---- ...create_club_information_update_request.sql | 4 ++-- .../club/ClubRegistrationRequestApiTest.java | 16 +++++++-------- .../ClubRegistrationRequestServiceTest.java | 20 ++++++++++++------- 5 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java b/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java index d3045fe2a..307ce238b 100644 --- a/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java +++ b/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java @@ -6,6 +6,7 @@ import static lombok.AccessLevel.PROTECTED; import gg.agit.konect.domain.club.enums.ClubCategory; +import gg.agit.konect.domain.website.model.WebClub; import gg.agit.konect.global.model.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -35,8 +36,8 @@ public class ClubInformationUpdateRequest extends BaseEntity { @NotNull @ToString.Exclude @ManyToOne(fetch = LAZY) - @JoinColumn(name = "club_id", nullable = false) - private Club club; + @JoinColumn(name = "web_club_id", nullable = false) + private WebClub club; @NotNull @Column(name = "club_name", length = 50, nullable = false) @@ -71,7 +72,7 @@ public class ClubInformationUpdateRequest extends BaseEntity { @Builder private ClubInformationUpdateRequest( Integer id, - Club club, + WebClub club, String clubName, ClubCategory clubCategory, String shortDescription, diff --git a/src/main/java/gg/agit/konect/domain/club/service/ClubRegistrationRequestService.java b/src/main/java/gg/agit/konect/domain/club/service/ClubRegistrationRequestService.java index a30cae133..51823396d 100644 --- a/src/main/java/gg/agit/konect/domain/club/service/ClubRegistrationRequestService.java +++ b/src/main/java/gg/agit/konect/domain/club/service/ClubRegistrationRequestService.java @@ -8,12 +8,14 @@ import gg.agit.konect.domain.club.dto.ClubRegistrationRequestDto; import gg.agit.konect.domain.club.event.ClubInformationUpdateRequestedEvent; import gg.agit.konect.domain.club.event.ClubRegistrationRequestedEvent; -import gg.agit.konect.domain.club.model.Club; import gg.agit.konect.domain.club.model.ClubInformationUpdateRequest; import gg.agit.konect.domain.club.model.ClubRegistrationRequest; import gg.agit.konect.domain.club.repository.ClubInformationUpdateRequestRepository; import gg.agit.konect.domain.club.repository.ClubRegistrationRequestRepository; -import gg.agit.konect.domain.club.repository.ClubRepository; +import gg.agit.konect.domain.website.model.WebClub; +import gg.agit.konect.domain.website.repository.WebsiteQueryRepository; +import gg.agit.konect.global.code.ApiResponseCode; +import gg.agit.konect.global.exception.CustomException; import lombok.RequiredArgsConstructor; @Service @@ -23,7 +25,7 @@ public class ClubRegistrationRequestService { private final ClubRegistrationRequestRepository clubRegistrationRequestRepository; private final ClubInformationUpdateRequestRepository clubInformationUpdateRequestRepository; - private final ClubRepository clubRepository; + private final WebsiteQueryRepository websiteQueryRepository; private final ApplicationEventPublisher applicationEventPublisher; public void register(ClubRegistrationRequestDto request) { @@ -48,7 +50,8 @@ public void register(ClubRegistrationRequestDto request) { } public void requestInformationUpdate(Integer clubId, ClubInformationUpdateRequestDto request) { - Club club = clubRepository.getById(clubId); + WebClub club = websiteQueryRepository.findClub(clubId) + .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_CLUB)); ClubInformationUpdateRequest entity = ClubInformationUpdateRequest.builder() .club(club) .clubName(request.clubName()) diff --git a/src/main/resources/db/migration/V81__create_club_information_update_request.sql b/src/main/resources/db/migration/V81__create_club_information_update_request.sql index baee9033b..3e3ba74c3 100644 --- a/src/main/resources/db/migration/V81__create_club_information_update_request.sql +++ b/src/main/resources/db/migration/V81__create_club_information_update_request.sql @@ -1,7 +1,7 @@ CREATE TABLE IF NOT EXISTS club_information_update_request ( id INT AUTO_INCREMENT PRIMARY KEY, - club_id INT NOT NULL COMMENT '수정 요청 대상 동아리 ID', + web_club_id INT NOT NULL COMMENT '수정 요청 대상 웹 동아리 ID', club_name VARCHAR(50) NOT NULL COMMENT '요청 동아리 명', club_category VARCHAR(255) NOT NULL COMMENT '요청 동아리 분과', short_description VARCHAR(25) NOT NULL COMMENT '요청 한 줄 소개', @@ -12,5 +12,5 @@ CREATE TABLE IF NOT EXISTS club_information_update_request created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL, - FOREIGN KEY (club_id) REFERENCES club (id) + FOREIGN KEY (web_club_id) REFERENCES web_club (id) ) COMMENT '동아리 정보 수정 요청'; diff --git a/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java b/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java index d646f9f18..6adbbee49 100644 --- a/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java +++ b/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java @@ -10,11 +10,11 @@ import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequestDto; import gg.agit.konect.domain.club.dto.ClubRegistrationRequestDto; import gg.agit.konect.domain.club.enums.ClubCategory; -import gg.agit.konect.domain.club.model.Club; -import gg.agit.konect.domain.university.model.University; +import gg.agit.konect.domain.website.model.WebClub; +import gg.agit.konect.domain.website.model.WebUniversity; import gg.agit.konect.support.IntegrationTestSupport; -import gg.agit.konect.support.fixture.ClubFixture; -import gg.agit.konect.support.fixture.UniversityFixture; +import gg.agit.konect.support.fixture.WebClubFixture; +import gg.agit.konect.support.fixture.WebUniversityFixture; class ClubRegistrationRequestApiTest extends IntegrationTestSupport { @@ -130,8 +130,8 @@ void registerClubWithLongIntroduction() throws Exception { @DisplayName("비로그인 사용자도 기존 동아리 정보 수정 요청을 보낼 수 있다") void requestClubInformationUpdateWithoutLogin() throws Exception { // given - University university = persist(UniversityFixture.create()); - Club club = persist(ClubFixture.create(university)); + WebUniversity university = persist(WebUniversityFixture.create()); + WebClub club = persist(WebClubFixture.create(university)); ClubInformationUpdateRequestDto request = createInformationUpdateRequest(); // when & then @@ -154,8 +154,8 @@ void requestClubInformationUpdateWithUnknownClub() throws Exception { @DisplayName("동아리 정보 수정 요청 필수값이 없으면 400을 반환한다") void requestClubInformationUpdateWithMissingFields() throws Exception { // given - University university = persist(UniversityFixture.create()); - Club club = persist(ClubFixture.create(university)); + WebUniversity university = persist(WebUniversityFixture.create()); + WebClub club = persist(WebClubFixture.create(university)); ClubInformationUpdateRequestDto request = new ClubInformationUpdateRequestDto( "", ClubCategory.ACADEMIC, diff --git a/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java b/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java index 33a87a990..4722e136f 100644 --- a/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java +++ b/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java @@ -6,6 +6,7 @@ import static org.mockito.Mockito.verify; import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -19,16 +20,16 @@ import gg.agit.konect.domain.club.enums.ClubCategory; import gg.agit.konect.domain.club.event.ClubInformationUpdateRequestedEvent; import gg.agit.konect.domain.club.event.ClubRegistrationRequestedEvent; -import gg.agit.konect.domain.club.model.Club; import gg.agit.konect.domain.club.model.ClubInformationUpdateRequest; import gg.agit.konect.domain.club.model.ClubRegistrationRequest; import gg.agit.konect.domain.club.repository.ClubInformationUpdateRequestRepository; import gg.agit.konect.domain.club.repository.ClubRegistrationRequestRepository; -import gg.agit.konect.domain.club.repository.ClubRepository; import gg.agit.konect.domain.club.service.ClubRegistrationRequestService; +import gg.agit.konect.domain.website.model.WebClub; +import gg.agit.konect.domain.website.repository.WebsiteQueryRepository; import gg.agit.konect.support.ServiceTestSupport; -import gg.agit.konect.support.fixture.ClubFixture; -import gg.agit.konect.support.fixture.UniversityFixture; +import gg.agit.konect.support.fixture.WebClubFixture; +import gg.agit.konect.support.fixture.WebUniversityFixture; class ClubRegistrationRequestServiceTest extends ServiceTestSupport { @@ -39,7 +40,7 @@ class ClubRegistrationRequestServiceTest extends ServiceTestSupport { private ClubInformationUpdateRequestRepository clubInformationUpdateRequestRepository; @Mock - private ClubRepository clubRepository; + private WebsiteQueryRepository websiteQueryRepository; @Mock private ApplicationEventPublisher applicationEventPublisher; @@ -100,7 +101,12 @@ void registerPublishesClubRegistrationRequestedEvent() { @DisplayName("동아리 정보 수정 요청 저장 후 Slack 알림 이벤트를 발행한다") void requestInformationUpdatePublishesClubInformationUpdateRequestedEvent() { // given - Club club = ClubFixture.createWithId(UniversityFixture.createWithId(1), 1, "현재 동아리명"); + WebClub club = WebClubFixture.createWithId( + 1, + WebUniversityFixture.createWithId(1), + "현재 동아리명", + ClubCategory.HOBBY + ); ClubInformationUpdateRequestDto request = new ClubInformationUpdateRequestDto( "요청 동아리명", ClubCategory.ACADEMIC, @@ -119,7 +125,7 @@ void requestInformationUpdatePublishesClubInformationUpdateRequestedEvent() { .location(request.location()) .fullIntroduction(request.fullIntroduction()) .build(); - given(clubRepository.getById(club.getId())).willReturn(club); + given(websiteQueryRepository.findClub(club.getId())).willReturn(Optional.of(club)); given(clubInformationUpdateRequestRepository.save(any(ClubInformationUpdateRequest.class))).willReturn(saved); // when From 90f4e1e7b6f2f4a9dee353a7a7b4b3873eba0de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 26 May 2026 23:15:02 +0900 Subject: [PATCH 06/13] =?UTF-8?q?fix:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=9A=94=EC=B2=AD=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=EC=9D=84=20=EB=93=B1=EB=A1=9D=20=EC=9A=94=EC=B2=AD=EA=B3=BC=20?= =?UTF-8?q?=EB=A7=9E=EC=B6=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 수정 요청도 등록 요청과 같은 필드셋을 받도록 대학명, 주제, 이모지, 첨부 이미지 목록을 포함한다 - 요청 이미지 목록은 등록 요청과 같이 별도 이미지 테이블에 순서와 함께 저장한다 - 웹 동아리 현재값과 요청값 비교 알림은 등록 요청 필드 기준으로 재정렬한다 - 기존 위치와 로고 단일 URL 입력을 제거해 등록 요청과 수정 요청 계약이 달라지는 혼선을 줄인다 --- .../dto/ClubInformationUpdateRequestDto.java | 48 +++++++++---- .../ClubInformationUpdateRequestedEvent.java | 30 +++++--- .../model/ClubInformationUpdateRequest.java | 47 ++++++++++--- .../ClubInformationUpdateRequestImage.java | 68 +++++++++++++++++++ .../ClubRegistrationRequestService.java | 9 ++- .../slack/enums/SlackMessageTemplate.java | 31 ++++----- .../ClubRegistrationRequestSlackListener.java | 13 ++-- .../service/SlackNotificationService.java | 26 ++++--- ...create_club_information_update_request.sql | 19 +++++- .../club/ClubRegistrationRequestApiTest.java | 16 +++-- .../ClubRegistrationRequestServiceTest.java | 25 ++++--- ...bRegistrationRequestSlackListenerTest.java | 39 +++++++---- .../service/SlackNotificationServiceTest.java | 46 +++++++------ 13 files changed, 294 insertions(+), 123 deletions(-) create mode 100644 src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequestImage.java diff --git a/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequestDto.java b/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequestDto.java index 60cc26d0c..32b98ae38 100644 --- a/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequestDto.java +++ b/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequestDto.java @@ -2,6 +2,8 @@ import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; +import java.util.List; + import gg.agit.konect.domain.club.enums.ClubCategory; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; @@ -11,6 +13,10 @@ @Schema(name = "ClubInformationUpdateRequest", description = "동아리 정보 수정 요청") public record ClubInformationUpdateRequestDto( + @Schema(description = "대학교 명", example = "한국기술교육대학교", requiredMode = REQUIRED) + @NotBlank(message = "대학교 명은 필수입니다.") + String universityName, + @Schema(description = "동아리 명", example = "BCSD Lab", requiredMode = REQUIRED) @NotBlank(message = "동아리 명은 필수입니다.") @Size(max = 50, message = "동아리 명은 최대 50자입니다.") @@ -20,27 +26,39 @@ public record ClubInformationUpdateRequestDto( @NotNull(message = "동아리 분과는 필수입니다.") ClubCategory clubCategory, - @Schema(description = "한 줄 소개", example = "코딩 동아리입니다.", requiredMode = REQUIRED) + @Schema(description = "동아리 주제", example = "코딩", requiredMode = REQUIRED) + @NotBlank(message = "동아리 주제는 필수입니다.") + @Size(max = 20, message = "동아리 주제는 최대 20자입니다.") + String clubTopic, + + @Schema(description = "동아리 이모지", example = "💻", requiredMode = REQUIRED) + @NotBlank(message = "동아리 이모지는 필수입니다.") + @Size(max = 10, message = "동아리 이모지는 최대 10자입니다.") + String clubEmoji, + + @Schema( + description = "한 줄 소개 (최대 30자)", + example = "코딩 동아리입니다.", + requiredMode = REQUIRED + ) @NotBlank(message = "한 줄 소개는 필수입니다.") - @Size(max = 25, message = "한 줄 소개는 최대 25자입니다.") + @Size(max = 30, message = "한 줄 소개는 최대 30자입니다.") String shortDescription, @Schema( - description = "동아리 로고 이미지 URL", - example = "https://example.com/logo.png", + description = "동아리 소개 (최대 2000자)", + example = "상세한 동아리 소개 내용...", requiredMode = REQUIRED ) - @NotBlank(message = "동아리 로고 이미지 URL은 필수입니다.") - @Size(max = 255, message = "동아리 로고 이미지 URL은 최대 255자입니다.") - String imageUrl, - - @Schema(description = "동아리 방 위치", example = "학생회관 101호", requiredMode = REQUIRED) - @NotBlank(message = "동아리 위치는 필수입니다.") - @Size(max = 255, message = "동아리 위치는 최대 255자입니다.") - String location, - - @Schema(description = "동아리 소개", example = "상세한 동아리 소개 내용...", requiredMode = REQUIRED) @NotBlank(message = "동아리 소개는 필수입니다.") - String fullIntroduction + @Size(max = 2000, message = "동아리 소개는 최대 2000자입니다.") + String fullIntroduction, + + @Schema( + description = "사진 및 영상 URL 목록 (최대 5개)", + example = "[\"https://example.com/image1.jpg\"]" + ) + @Size(max = 5, message = "사진 및 영상은 최대 5개까지 업로드 가능합니다.") + List imageUrls ) { } diff --git a/src/main/java/gg/agit/konect/domain/club/event/ClubInformationUpdateRequestedEvent.java b/src/main/java/gg/agit/konect/domain/club/event/ClubInformationUpdateRequestedEvent.java index df95b92f8..b59f596e4 100644 --- a/src/main/java/gg/agit/konect/domain/club/event/ClubInformationUpdateRequestedEvent.java +++ b/src/main/java/gg/agit/konect/domain/club/event/ClubInformationUpdateRequestedEvent.java @@ -1,40 +1,50 @@ package gg.agit.konect.domain.club.event; +import java.util.List; + import gg.agit.konect.domain.club.model.ClubInformationUpdateRequest; public record ClubInformationUpdateRequestedEvent( Integer requestId, Integer clubId, + String currentUniversityName, + String requestedUniversityName, String currentClubName, String requestedClubName, String currentCategory, String requestedCategory, + String currentTopic, + String requestedTopic, + String requestedEmoji, String currentDescription, String requestedDescription, - String currentImageUrl, - String requestedImageUrl, - String currentLocation, - String requestedLocation, String currentFullIntroduction, - String requestedFullIntroduction + String requestedFullIntroduction, + String currentImageUrl, + List requestedImageUrls ) { public static ClubInformationUpdateRequestedEvent from(ClubInformationUpdateRequest request) { return new ClubInformationUpdateRequestedEvent( request.getId(), request.getClub().getId(), + request.getClub().getUniversity().getKoreanName(), + request.getUniversityName(), request.getClub().getName(), request.getClubName(), request.getClub().getClubCategory().getDescription(), request.getClubCategory().getDescription(), + request.getClub().getTopic(), + request.getClubTopic(), + request.getClubEmoji(), request.getClub().getDescription(), request.getShortDescription(), - request.getClub().getImageUrl(), - request.getImageUrl(), - request.getClub().getLocation(), - request.getLocation(), request.getClub().getIntroduce(), - request.getFullIntroduction() + request.getFullIntroduction(), + request.getClub().getImageUrl(), + request.getImages().stream() + .map(image -> image.getImageUrl()) + .toList() ); } } diff --git a/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java b/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java index 307ce238b..ba02e2d2c 100644 --- a/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java +++ b/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java @@ -1,10 +1,14 @@ package gg.agit.konect.domain.club.model; +import static jakarta.persistence.CascadeType.ALL; import static jakarta.persistence.EnumType.STRING; import static jakarta.persistence.FetchType.LAZY; import static jakarta.persistence.GenerationType.IDENTITY; import static lombok.AccessLevel.PROTECTED; +import java.util.ArrayList; +import java.util.List; + import gg.agit.konect.domain.club.enums.ClubCategory; import gg.agit.konect.domain.website.model.WebClub; import gg.agit.konect.global.model.BaseEntity; @@ -15,6 +19,8 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OrderBy; import jakarta.persistence.Table; import jakarta.validation.constraints.NotNull; import lombok.Builder; @@ -39,6 +45,10 @@ public class ClubInformationUpdateRequest extends BaseEntity { @JoinColumn(name = "web_club_id", nullable = false) private WebClub club; + @NotNull + @Column(name = "university_name", nullable = false) + private String universityName; + @NotNull @Column(name = "club_name", length = 50, nullable = false) private String clubName; @@ -49,21 +59,25 @@ public class ClubInformationUpdateRequest extends BaseEntity { private ClubCategory clubCategory; @NotNull - @Column(name = "short_description", length = 25, nullable = false) - private String shortDescription; + @Column(name = "club_topic", length = 20, nullable = false) + private String clubTopic; @NotNull - @Column(name = "image_url", length = 255, nullable = false) - private String imageUrl; + @Column(name = "club_emoji", length = 10, nullable = false) + private String clubEmoji; @NotNull - @Column(name = "location", length = 255, nullable = false) - private String location; + @Column(name = "short_description", length = 30, nullable = false) + private String shortDescription; @NotNull @Column(name = "full_introduction", columnDefinition = "TEXT", nullable = false) private String fullIntroduction; + @OneToMany(mappedBy = "request", cascade = ALL, orphanRemoval = true) + @OrderBy("displayOrder ASC") + private List images = new ArrayList<>(); + @NotNull @Enumerated(value = STRING) @Column(name = "status", length = 20, nullable = false) @@ -73,25 +87,38 @@ public class ClubInformationUpdateRequest extends BaseEntity { private ClubInformationUpdateRequest( Integer id, WebClub club, + String universityName, String clubName, ClubCategory clubCategory, + String clubTopic, + String clubEmoji, String shortDescription, - String imageUrl, - String location, String fullIntroduction, UpdateRequestStatus status ) { this.id = id; this.club = club; + this.universityName = universityName; this.clubName = clubName; this.clubCategory = clubCategory; + this.clubTopic = clubTopic; + this.clubEmoji = clubEmoji; this.shortDescription = shortDescription; - this.imageUrl = imageUrl; - this.location = location; this.fullIntroduction = fullIntroduction; this.status = status != null ? status : UpdateRequestStatus.PENDING; } + public void addImages(List imageUrls) { + for (int i = 0; i < imageUrls.size(); i++) { + ClubInformationUpdateRequestImage image = ClubInformationUpdateRequestImage.builder() + .request(this) + .imageUrl(imageUrls.get(i)) + .displayOrder(i) + .build(); + this.images.add(image); + } + } + public enum UpdateRequestStatus { PENDING, APPROVED, REJECTED } diff --git a/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequestImage.java b/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequestImage.java new file mode 100644 index 000000000..84549f1dc --- /dev/null +++ b/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequestImage.java @@ -0,0 +1,68 @@ +package gg.agit.konect.domain.club.model; + +import static jakarta.persistence.FetchType.LAZY; +import static jakarta.persistence.GenerationType.IDENTITY; +import static lombok.AccessLevel.PROTECTED; + +import gg.agit.konect.global.model.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import jakarta.validation.constraints.NotNull; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@Table(name = "club_information_update_request_image") +@NoArgsConstructor(access = PROTECTED) +public class ClubInformationUpdateRequestImage extends BaseEntity { + + @Id + @GeneratedValue(strategy = IDENTITY) + @Column(name = "id", nullable = false, updatable = false, unique = true) + private Integer id; + + @NotNull + @ManyToOne(fetch = LAZY) + @JoinColumn(name = "request_id", nullable = false) + private ClubInformationUpdateRequest request; + + @NotNull + @Column(name = "image_url", length = 500, nullable = false) + private String imageUrl; + + @NotNull + @Column(name = "display_order", nullable = false) + private Integer displayOrder; + + @Builder + private ClubInformationUpdateRequestImage( + Integer id, + ClubInformationUpdateRequest request, + String imageUrl, + Integer displayOrder + ) { + this.id = id; + this.request = request; + this.imageUrl = imageUrl; + this.displayOrder = displayOrder; + } + + public static class ClubInformationUpdateRequestImageBuilder { + + @Override + public String toString() { + return "ClubInformationUpdateRequestImage.ClubInformationUpdateRequestImageBuilder(" + + "id=" + id + + ", imageUrl=" + imageUrl + + ", displayOrder=" + displayOrder + + ")"; + } + } +} diff --git a/src/main/java/gg/agit/konect/domain/club/service/ClubRegistrationRequestService.java b/src/main/java/gg/agit/konect/domain/club/service/ClubRegistrationRequestService.java index 51823396d..2b2a25203 100644 --- a/src/main/java/gg/agit/konect/domain/club/service/ClubRegistrationRequestService.java +++ b/src/main/java/gg/agit/konect/domain/club/service/ClubRegistrationRequestService.java @@ -54,15 +54,20 @@ public void requestInformationUpdate(Integer clubId, ClubInformationUpdateReques .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_CLUB)); ClubInformationUpdateRequest entity = ClubInformationUpdateRequest.builder() .club(club) + .universityName(request.universityName()) .clubName(request.clubName()) .clubCategory(request.clubCategory()) + .clubTopic(request.clubTopic()) + .clubEmoji(request.clubEmoji()) .shortDescription(request.shortDescription()) - .imageUrl(request.imageUrl()) - .location(request.location()) .fullIntroduction(request.fullIntroduction()) .status(ClubInformationUpdateRequest.UpdateRequestStatus.PENDING) .build(); + if (request.imageUrls() != null && !request.imageUrls().isEmpty()) { + entity.addImages(request.imageUrls()); + } + ClubInformationUpdateRequest saved = clubInformationUpdateRequestRepository.save(entity); applicationEventPublisher.publishEvent(ClubInformationUpdateRequestedEvent.from(saved)); } diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java b/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java index 7e4af90b5..6001f2e71 100644 --- a/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java +++ b/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java @@ -41,19 +41,19 @@ public enum SlackMessageTemplate { CLUB_REGISTRATION_REQUEST( """ :sparkles: *새 동아리 등록 요청이 도착했어요* - + :school: *대학교* : *`%s`* %s *동아리* : *`%s`* :label: *분과* : *`%s`* :dart: *주제* : *`%s`* :art: *요청 이모지* : *`%s`* - + :memo: *한 줄 소개* ```%s``` - + :page_facing_up: *상세 소개* ```%s``` - + :paperclip: *첨부 이미지* ```%s``` """ @@ -61,30 +61,29 @@ public enum SlackMessageTemplate { CLUB_INFORMATION_UPDATE_REQUEST( """ :pencil2: *동아리 정보 수정 요청이 도착했어요* - + :receipt: *요청 ID* : *`%s`* :id: *동아리 ID* : *`%s`* + :school: *대학교* : *`%s`* → *`%s`* :bookmark: *동아리명* : *`%s`* → *`%s`* :label: *분과* : *`%s`* → *`%s`* - + :dart: *주제* : *`%s`* → *`%s`* + :art: *요청 이모지* : *`%s`* + :memo: *한 줄 소개* ```%s``` → ```%s``` - - :frame_with_picture: *로고 이미지* - ```%s``` - → - ```%s``` - - :round_pushpin: *위치* + + :page_facing_up: *상세 소개* ```%s``` → ```%s``` - - :page_facing_up: *상세 소개* + + :frame_with_picture: *현재 대표 이미지* ```%s``` - → + + :paperclip: *요청 첨부 이미지* ```%s``` """ ), diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/listener/ClubRegistrationRequestSlackListener.java b/src/main/java/gg/agit/konect/infrastructure/slack/listener/ClubRegistrationRequestSlackListener.java index 9a7972f59..5f853b9c7 100644 --- a/src/main/java/gg/agit/konect/infrastructure/slack/listener/ClubRegistrationRequestSlackListener.java +++ b/src/main/java/gg/agit/konect/infrastructure/slack/listener/ClubRegistrationRequestSlackListener.java @@ -46,18 +46,21 @@ public void handleClubInformationUpdateRequested(ClubInformationUpdateRequestedE slackNotificationService.notifyClubInformationUpdateRequest( event.requestId(), event.clubId(), + event.currentUniversityName(), + event.requestedUniversityName(), event.currentClubName(), event.requestedClubName(), event.currentCategory(), event.requestedCategory(), + event.currentTopic(), + event.requestedTopic(), + event.requestedEmoji(), event.currentDescription(), event.requestedDescription(), - event.currentImageUrl(), - event.requestedImageUrl(), - event.currentLocation(), - event.requestedLocation(), event.currentFullIntroduction(), - event.requestedFullIntroduction() + event.requestedFullIntroduction(), + event.currentImageUrl(), + event.requestedImageUrls() ); } catch (RuntimeException e) { log.warn( diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java b/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java index a74dbe544..6a0cab23d 100644 --- a/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java +++ b/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java @@ -83,34 +83,40 @@ public void notifyClubRegistrationRequest( public void notifyClubInformationUpdateRequest( Integer requestId, Integer clubId, + String currentUniversityName, + String requestedUniversityName, String currentClubName, String requestedClubName, String currentCategory, String requestedCategory, + String currentTopic, + String requestedTopic, + String requestedEmoji, String currentDescription, String requestedDescription, - String currentImageUrl, - String requestedImageUrl, - String currentLocation, - String requestedLocation, String currentFullIntroduction, - String requestedFullIntroduction + String requestedFullIntroduction, + String currentImageUrl, + List requestedImageUrls ) { String message = CLUB_INFORMATION_UPDATE_REQUEST.format( requestId, clubId, + currentUniversityName, + requestedUniversityName, currentClubName, requestedClubName, currentCategory, requestedCategory, + currentTopic, + requestedTopic, + requestedEmoji, currentDescription, requestedDescription, - currentImageUrl, - requestedImageUrl, - currentLocation, - requestedLocation, currentFullIntroduction, - requestedFullIntroduction + requestedFullIntroduction, + currentImageUrl, + formatImageUrls(requestedImageUrls) ); slackClient.sendMessage(message, slackProperties.webhooks().event()); } diff --git a/src/main/resources/db/migration/V81__create_club_information_update_request.sql b/src/main/resources/db/migration/V81__create_club_information_update_request.sql index 3e3ba74c3..513de8ae7 100644 --- a/src/main/resources/db/migration/V81__create_club_information_update_request.sql +++ b/src/main/resources/db/migration/V81__create_club_information_update_request.sql @@ -2,11 +2,12 @@ CREATE TABLE IF NOT EXISTS club_information_update_request ( id INT AUTO_INCREMENT PRIMARY KEY, web_club_id INT NOT NULL COMMENT '수정 요청 대상 웹 동아리 ID', + university_name VARCHAR(255) NOT NULL COMMENT '요청 대학교 명', club_name VARCHAR(50) NOT NULL COMMENT '요청 동아리 명', club_category VARCHAR(255) NOT NULL COMMENT '요청 동아리 분과', - short_description VARCHAR(25) NOT NULL COMMENT '요청 한 줄 소개', - image_url VARCHAR(255) NOT NULL COMMENT '요청 동아리 로고 이미지 URL', - location VARCHAR(255) NOT NULL COMMENT '요청 동아리 위치', + club_topic VARCHAR(20) NOT NULL COMMENT '요청 동아리 주제', + club_emoji VARCHAR(10) NOT NULL COMMENT '요청 동아리 이모지', + short_description VARCHAR(30) NOT NULL COMMENT '요청 한 줄 소개', full_introduction TEXT NOT NULL COMMENT '요청 동아리 소개', status VARCHAR(20) DEFAULT 'PENDING' NOT NULL COMMENT '요청 상태 (PENDING, APPROVED, REJECTED)', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, @@ -14,3 +15,15 @@ CREATE TABLE IF NOT EXISTS club_information_update_request FOREIGN KEY (web_club_id) REFERENCES web_club (id) ) COMMENT '동아리 정보 수정 요청'; + +CREATE TABLE IF NOT EXISTS club_information_update_request_image +( + id INT AUTO_INCREMENT PRIMARY KEY, + request_id INT NOT NULL COMMENT '동아리 정보 수정 요청 ID', + image_url VARCHAR(500) NOT NULL COMMENT '이미지 URL', + display_order INT DEFAULT 0 NOT NULL COMMENT '표시 순서', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL, + + FOREIGN KEY (request_id) REFERENCES club_information_update_request (id) ON DELETE CASCADE +) COMMENT '동아리 정보 수정 요청 이미지'; diff --git a/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java b/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java index 6adbbee49..a0f227141 100644 --- a/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java +++ b/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java @@ -158,11 +158,13 @@ void requestClubInformationUpdateWithMissingFields() throws Exception { WebClub club = persist(WebClubFixture.create(university)); ClubInformationUpdateRequestDto request = new ClubInformationUpdateRequestDto( "", + "BCSD Lab", ClubCategory.ACADEMIC, + "코딩", + "💻", "수정 소개", - "https://example.com/logo.png", - "학생회관 102호", - "수정 상세 소개입니다." + "수정 상세 소개입니다.", + List.of() ); // when & then @@ -172,12 +174,14 @@ void requestClubInformationUpdateWithMissingFields() throws Exception { private ClubInformationUpdateRequestDto createInformationUpdateRequest() { return new ClubInformationUpdateRequestDto( + "한국기술교육대학교", "BCSD Lab", ClubCategory.ACADEMIC, + "코딩", + "💻", "수정 소개", - "https://example.com/logo.png", - "학생회관 102호", - "수정 상세 소개입니다." + "수정 상세 소개입니다.", + List.of("https://example.com/image1.jpg") ); } } diff --git a/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java b/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java index 4722e136f..1c33a978a 100644 --- a/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java +++ b/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java @@ -108,23 +108,27 @@ void requestInformationUpdatePublishesClubInformationUpdateRequestedEvent() { ClubCategory.HOBBY ); ClubInformationUpdateRequestDto request = new ClubInformationUpdateRequestDto( + "한국기술교육대학교", "요청 동아리명", ClubCategory.ACADEMIC, + "AI", + "🤖", "수정 소개", - "https://example.com/logo.png", - "학생회관 102호", - "수정 상세 소개입니다." + "수정 상세 소개입니다.", + List.of("https://example.com/image1.jpg") ); ClubInformationUpdateRequest saved = ClubInformationUpdateRequest.builder() .id(10) .club(club) + .universityName(request.universityName()) .clubName(request.clubName()) .clubCategory(request.clubCategory()) + .clubTopic(request.clubTopic()) + .clubEmoji(request.clubEmoji()) .shortDescription(request.shortDescription()) - .imageUrl(request.imageUrl()) - .location(request.location()) .fullIntroduction(request.fullIntroduction()) .build(); + saved.addImages(request.imageUrls()); given(websiteQueryRepository.findClub(club.getId())).willReturn(Optional.of(club)); given(clubInformationUpdateRequestRepository.save(any(ClubInformationUpdateRequest.class))).willReturn(saved); @@ -140,17 +144,20 @@ void requestInformationUpdatePublishesClubInformationUpdateRequestedEvent() { ClubInformationUpdateRequestedEvent event = eventCaptor.getValue(); assertThat(event.requestId()).isEqualTo(saved.getId()); assertThat(event.clubId()).isEqualTo(club.getId()); + assertThat(event.currentUniversityName()).isEqualTo(club.getUniversity().getKoreanName()); + assertThat(event.requestedUniversityName()).isEqualTo(request.universityName()); assertThat(event.currentClubName()).isEqualTo(club.getName()); assertThat(event.requestedClubName()).isEqualTo(request.clubName()); assertThat(event.currentCategory()).isEqualTo(club.getClubCategory().getDescription()); assertThat(event.requestedCategory()).isEqualTo(request.clubCategory().getDescription()); + assertThat(event.currentTopic()).isEqualTo(club.getTopic()); + assertThat(event.requestedTopic()).isEqualTo(request.clubTopic()); + assertThat(event.requestedEmoji()).isEqualTo(request.clubEmoji()); assertThat(event.currentDescription()).isEqualTo(club.getDescription()); assertThat(event.requestedDescription()).isEqualTo(request.shortDescription()); - assertThat(event.currentImageUrl()).isEqualTo(club.getImageUrl()); - assertThat(event.requestedImageUrl()).isEqualTo(request.imageUrl()); - assertThat(event.currentLocation()).isEqualTo(club.getLocation()); - assertThat(event.requestedLocation()).isEqualTo(request.location()); assertThat(event.currentFullIntroduction()).isEqualTo(club.getIntroduce()); assertThat(event.requestedFullIntroduction()).isEqualTo(request.fullIntroduction()); + assertThat(event.currentImageUrl()).isEqualTo(club.getImageUrl()); + assertThat(event.requestedImageUrls()).containsExactlyElementsOf(request.imageUrls()); } } diff --git a/src/test/java/gg/agit/konect/unit/infrastructure/slack/listener/ClubRegistrationRequestSlackListenerTest.java b/src/test/java/gg/agit/konect/unit/infrastructure/slack/listener/ClubRegistrationRequestSlackListenerTest.java index f373ce7f3..1ed28914f 100644 --- a/src/test/java/gg/agit/konect/unit/infrastructure/slack/listener/ClubRegistrationRequestSlackListenerTest.java +++ b/src/test/java/gg/agit/konect/unit/infrastructure/slack/listener/ClubRegistrationRequestSlackListenerTest.java @@ -85,18 +85,21 @@ void handleClubInformationUpdateRequestedDelegatesToSlackService() { verify(slackNotificationService).notifyClubInformationUpdateRequest( event.requestId(), event.clubId(), + event.currentUniversityName(), + event.requestedUniversityName(), event.currentClubName(), event.requestedClubName(), event.currentCategory(), event.requestedCategory(), + event.currentTopic(), + event.requestedTopic(), + event.requestedEmoji(), event.currentDescription(), event.requestedDescription(), - event.currentImageUrl(), - event.requestedImageUrl(), - event.currentLocation(), - event.requestedLocation(), event.currentFullIntroduction(), - event.requestedFullIntroduction() + event.requestedFullIntroduction(), + event.currentImageUrl(), + event.requestedImageUrls() ); } @@ -110,18 +113,21 @@ void handleClubInformationUpdateRequestedSwallowsExceptions() { .notifyClubInformationUpdateRequest( event.requestId(), event.clubId(), + event.currentUniversityName(), + event.requestedUniversityName(), event.currentClubName(), event.requestedClubName(), event.currentCategory(), event.requestedCategory(), + event.currentTopic(), + event.requestedTopic(), + event.requestedEmoji(), event.currentDescription(), event.requestedDescription(), - event.currentImageUrl(), - event.requestedImageUrl(), - event.currentLocation(), - event.requestedLocation(), event.currentFullIntroduction(), - event.requestedFullIntroduction() + event.requestedFullIntroduction(), + event.currentImageUrl(), + event.requestedImageUrls() ); // when & then @@ -147,18 +153,21 @@ private ClubInformationUpdateRequestedEvent createInformationUpdateEvent() { return new ClubInformationUpdateRequestedEvent( 1, 2, + "한국기술교육대학교", + "한국기술교육대학교", "현재 동아리명", "요청 동아리명", "문화", "학술", + "코딩", + "AI", + "🤖", "현재 소개", "수정 소개", - "https://example.com/current-logo.png", - "https://example.com/logo.png", - "학생회관 101호", - "학생회관 102호", "현재 상세 소개 내용입니다.", - "수정 상세 소개 내용입니다." + "수정 상세 소개 내용입니다.", + "https://example.com/current-logo.png", + List.of("https://example.com/image1.jpg") ); } } diff --git a/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java b/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java index 2913aec06..df0d4a8d9 100644 --- a/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java +++ b/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java @@ -59,19 +59,19 @@ void notifyClubRegistrationRequestFormatsSlackMessageWithMarkdownAndEmoji() { assertThat(messageCaptor.getValue()).isEqualTo( """ :sparkles: *새 동아리 등록 요청이 도착했어요* - + :school: *대학교* : *`한국기술교육대학교`* 💻 *동아리* : *`BCSD Lab`* :label: *분과* : *`학술`* :dart: *주제* : *`코딩`* :art: *요청 이모지* : *`💻`* - + :memo: *한 줄 소개* ```코딩 동아리입니다.``` - + :page_facing_up: *상세 소개* ```상세한 동아리 소개 내용입니다.``` - + :paperclip: *첨부 이미지* ```https://example.com/image1.jpg https://example.com/image2.jpg``` @@ -86,18 +86,21 @@ void notifyClubInformationUpdateRequestFormatsSlackMessageWithMarkdownAndEmoji() slackNotificationService.notifyClubInformationUpdateRequest( 1, 2, + "한국기술교육대학교", + "한국기술교육대학교", "현재 동아리명", "요청 동아리명", "문화", "학술", + "코딩", + "AI", + "🤖", "현재 소개", "수정 소개", - "https://example.com/current-logo.png", - "https://example.com/logo.png", - "학생회관 101호", - "학생회관 102호", "현재 상세 소개 내용입니다.", - "수정 상세 소개 내용입니다." + "수정 상세 소개 내용입니다.", + "https://example.com/current-logo.png", + List.of("https://example.com/image1.jpg") ); // then @@ -106,31 +109,30 @@ void notifyClubInformationUpdateRequestFormatsSlackMessageWithMarkdownAndEmoji() assertThat(messageCaptor.getValue()).isEqualTo( """ :pencil2: *동아리 정보 수정 요청이 도착했어요* - + :receipt: *요청 ID* : *`1`* :id: *동아리 ID* : *`2`* + :school: *대학교* : *`한국기술교육대학교`* → *`한국기술교육대학교`* :bookmark: *동아리명* : *`현재 동아리명`* → *`요청 동아리명`* :label: *분과* : *`문화`* → *`학술`* - + :dart: *주제* : *`코딩`* → *`AI`* + :art: *요청 이모지* : *`🤖`* + :memo: *한 줄 소개* ```현재 소개``` → ```수정 소개``` - - :frame_with_picture: *로고 이미지* - ```https://example.com/current-logo.png``` - → - ```https://example.com/logo.png``` - - :round_pushpin: *위치* - ```학생회관 101호``` - → - ```학생회관 102호``` - + :page_facing_up: *상세 소개* ```현재 상세 소개 내용입니다.``` → ```수정 상세 소개 내용입니다.``` + + :frame_with_picture: *현재 대표 이미지* + ```https://example.com/current-logo.png``` + + :paperclip: *요청 첨부 이미지* + ```https://example.com/image1.jpg``` """ ); } From 5bf82d0436c475fd8be3c0899804a2ffa9343f05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 26 May 2026 23:15:13 +0900 Subject: [PATCH 07/13] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=20=ED=8F=AC?= =?UTF-8?q?=EB=A7=B7=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../slack/enums/SlackMessageTemplate.java | 18 +++++++++--------- .../service/SlackNotificationServiceTest.java | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java b/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java index 6001f2e71..5cfafeb08 100644 --- a/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java +++ b/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java @@ -41,19 +41,19 @@ public enum SlackMessageTemplate { CLUB_REGISTRATION_REQUEST( """ :sparkles: *새 동아리 등록 요청이 도착했어요* - + :school: *대학교* : *`%s`* %s *동아리* : *`%s`* :label: *분과* : *`%s`* :dart: *주제* : *`%s`* :art: *요청 이모지* : *`%s`* - + :memo: *한 줄 소개* ```%s``` - + :page_facing_up: *상세 소개* ```%s``` - + :paperclip: *첨부 이미지* ```%s``` """ @@ -61,7 +61,7 @@ public enum SlackMessageTemplate { CLUB_INFORMATION_UPDATE_REQUEST( """ :pencil2: *동아리 정보 수정 요청이 도착했어요* - + :receipt: *요청 ID* : *`%s`* :id: *동아리 ID* : *`%s`* :school: *대학교* : *`%s`* → *`%s`* @@ -69,20 +69,20 @@ public enum SlackMessageTemplate { :label: *분과* : *`%s`* → *`%s`* :dart: *주제* : *`%s`* → *`%s`* :art: *요청 이모지* : *`%s`* - + :memo: *한 줄 소개* ```%s``` → ```%s``` - + :page_facing_up: *상세 소개* ```%s``` → ```%s``` - + :frame_with_picture: *현재 대표 이미지* ```%s``` - + :paperclip: *요청 첨부 이미지* ```%s``` """ diff --git a/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java b/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java index df0d4a8d9..9ee7982ae 100644 --- a/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java +++ b/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java @@ -59,19 +59,19 @@ void notifyClubRegistrationRequestFormatsSlackMessageWithMarkdownAndEmoji() { assertThat(messageCaptor.getValue()).isEqualTo( """ :sparkles: *새 동아리 등록 요청이 도착했어요* - + :school: *대학교* : *`한국기술교육대학교`* 💻 *동아리* : *`BCSD Lab`* :label: *분과* : *`학술`* :dart: *주제* : *`코딩`* :art: *요청 이모지* : *`💻`* - + :memo: *한 줄 소개* ```코딩 동아리입니다.``` - + :page_facing_up: *상세 소개* ```상세한 동아리 소개 내용입니다.``` - + :paperclip: *첨부 이미지* ```https://example.com/image1.jpg https://example.com/image2.jpg``` @@ -109,7 +109,7 @@ void notifyClubInformationUpdateRequestFormatsSlackMessageWithMarkdownAndEmoji() assertThat(messageCaptor.getValue()).isEqualTo( """ :pencil2: *동아리 정보 수정 요청이 도착했어요* - + :receipt: *요청 ID* : *`1`* :id: *동아리 ID* : *`2`* :school: *대학교* : *`한국기술교육대학교`* → *`한국기술교육대학교`* @@ -117,20 +117,20 @@ void notifyClubInformationUpdateRequestFormatsSlackMessageWithMarkdownAndEmoji() :label: *분과* : *`문화`* → *`학술`* :dart: *주제* : *`코딩`* → *`AI`* :art: *요청 이모지* : *`🤖`* - + :memo: *한 줄 소개* ```현재 소개``` → ```수정 소개``` - + :page_facing_up: *상세 소개* ```현재 상세 소개 내용입니다.``` → ```수정 상세 소개 내용입니다.``` - + :frame_with_picture: *현재 대표 이미지* ```https://example.com/current-logo.png``` - + :paperclip: *요청 첨부 이미지* ```https://example.com/image1.jpg``` """ From 78097100c7f37508a8c7a5c546b0842d0ba19fd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 26 May 2026 23:24:45 +0900 Subject: [PATCH 08/13] =?UTF-8?q?fix:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=9A=94=EC=B2=AD=20=EC=95=8C=EB=A6=BC=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EA=B0=92=EB=A7=8C=20=EB=B9=84=EA=B5=90=20?= =?UTF-8?q?=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 현재값과 요청값이 같은 항목은 단일 값으로 표시해 불필요한 비교 화살표를 줄인다 - 값이 달라진 항목만 초기값에서 변경값으로 이어지는 형태로 보여 운영자가 실제 변경 내용을 빠르게 볼 수 있게 한다 - 인라인 값과 긴 본문 값의 표시 방식을 분리해 Slack 메시지 가독성을 유지한다 --- .../slack/enums/SlackMessageTemplate.java | 23 ++++----- .../service/SlackNotificationService.java | 48 +++++++++++++------ .../service/SlackNotificationServiceTest.java | 7 ++- 3 files changed, 45 insertions(+), 33 deletions(-) diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java b/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java index 5cfafeb08..1a3db1024 100644 --- a/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java +++ b/src/main/java/gg/agit/konect/infrastructure/slack/enums/SlackMessageTemplate.java @@ -64,27 +64,20 @@ public enum SlackMessageTemplate { :receipt: *요청 ID* : *`%s`* :id: *동아리 ID* : *`%s`* - :school: *대학교* : *`%s`* → *`%s`* - :bookmark: *동아리명* : *`%s`* → *`%s`* - :label: *분과* : *`%s`* → *`%s`* - :dart: *주제* : *`%s`* → *`%s`* + :school: *대학교* : %s + :bookmark: *동아리명* : %s + :label: *분과* : %s + :dart: *주제* : %s :art: *요청 이모지* : *`%s`* :memo: *한 줄 소개* - ```%s``` - → - ```%s``` + %s :page_facing_up: *상세 소개* - ```%s``` - → - ```%s``` + %s - :frame_with_picture: *현재 대표 이미지* - ```%s``` - - :paperclip: *요청 첨부 이미지* - ```%s``` + :paperclip: *이미지* + %s """ ), ; diff --git a/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java b/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java index 6a0cab23d..1c435bf54 100644 --- a/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java +++ b/src/main/java/gg/agit/konect/infrastructure/slack/service/SlackNotificationService.java @@ -9,6 +9,7 @@ import static gg.agit.konect.infrastructure.slack.enums.SlackMessageTemplate.USER_WITHDRAWAL; import java.util.List; +import java.util.Objects; import org.springframework.stereotype.Service; @@ -102,25 +103,44 @@ public void notifyClubInformationUpdateRequest( String message = CLUB_INFORMATION_UPDATE_REQUEST.format( requestId, clubId, - currentUniversityName, - requestedUniversityName, - currentClubName, - requestedClubName, - currentCategory, - requestedCategory, - currentTopic, - requestedTopic, + formatInlineChange(currentUniversityName, requestedUniversityName), + formatInlineChange(currentClubName, requestedClubName), + formatInlineChange(currentCategory, requestedCategory), + formatInlineChange(currentTopic, requestedTopic), requestedEmoji, - currentDescription, - requestedDescription, - currentFullIntroduction, - requestedFullIntroduction, - currentImageUrl, - formatImageUrls(requestedImageUrls) + formatBlockChange(currentDescription, requestedDescription), + formatBlockChange(currentFullIntroduction, requestedFullIntroduction), + formatBlockChange(currentImageUrl, formatImageUrls(requestedImageUrls)) ); slackClient.sendMessage(message, slackProperties.webhooks().event()); } + private String formatInlineChange(String currentValue, String requestedValue) { + if (Objects.equals(currentValue, requestedValue)) { + return wrapInline(currentValue); + } + return wrapInline(currentValue) + " → " + wrapInline(requestedValue); + } + + private String wrapInline(String value) { + return "*`" + value + "`*"; + } + + private String formatBlockChange(String currentValue, String requestedValue) { + if (Objects.equals(currentValue, requestedValue)) { + return wrapBlock(currentValue); + } + return wrapBlock(currentValue) + + System.lineSeparator() + + "→" + + System.lineSeparator() + + wrapBlock(requestedValue); + } + + private String wrapBlock(String value) { + return "```" + value + "```"; + } + private String formatImageUrls(List imageUrls) { if (imageUrls.isEmpty()) { return "없음"; diff --git a/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java b/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java index 9ee7982ae..26727677c 100644 --- a/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java +++ b/src/test/java/gg/agit/konect/unit/infrastructure/slack/service/SlackNotificationServiceTest.java @@ -112,7 +112,7 @@ void notifyClubInformationUpdateRequestFormatsSlackMessageWithMarkdownAndEmoji() :receipt: *요청 ID* : *`1`* :id: *동아리 ID* : *`2`* - :school: *대학교* : *`한국기술교육대학교`* → *`한국기술교육대학교`* + :school: *대학교* : *`한국기술교육대학교`* :bookmark: *동아리명* : *`현재 동아리명`* → *`요청 동아리명`* :label: *분과* : *`문화`* → *`학술`* :dart: *주제* : *`코딩`* → *`AI`* @@ -128,10 +128,9 @@ void notifyClubInformationUpdateRequestFormatsSlackMessageWithMarkdownAndEmoji() → ```수정 상세 소개 내용입니다.``` - :frame_with_picture: *현재 대표 이미지* + :paperclip: *이미지* ```https://example.com/current-logo.png``` - - :paperclip: *요청 첨부 이미지* + → ```https://example.com/image1.jpg``` """ ); From a6080df988f57b89025794a27f4d48385b0551b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 26 May 2026 23:32:52 +0900 Subject: [PATCH 09/13] =?UTF-8?q?fix:=20=EB=8F=99=EC=95=84=EB=A6=AC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=9A=94=EC=B2=AD=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EB=B3=B4=EA=B0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 수정 요청 이미지 URL 원소에도 빈 값과 길이 제한을 적용해 잘못된 URL이 저장되지 않도록 정리 - 요청별 이미지 표시 순서가 중복 저장되지 않도록 DB 유니크 제약을 추가 - 존재하지 않는 동아리 테스트가 임의 숫자에 의존하지 않도록 경계값을 사용 --- .../domain/club/dto/ClubInformationUpdateRequestDto.java | 5 ++++- .../V81__create_club_information_update_request.sql | 1 + .../domain/club/ClubRegistrationRequestApiTest.java | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequestDto.java b/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequestDto.java index 32b98ae38..ae375dac7 100644 --- a/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequestDto.java +++ b/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequestDto.java @@ -59,6 +59,9 @@ public record ClubInformationUpdateRequestDto( example = "[\"https://example.com/image1.jpg\"]" ) @Size(max = 5, message = "사진 및 영상은 최대 5개까지 업로드 가능합니다.") - List imageUrls + List< + @NotBlank(message = "이미지 URL은 필수입니다.") + @Size(max = 500, message = "이미지 URL은 최대 500자입니다.") + String> imageUrls ) { } diff --git a/src/main/resources/db/migration/V81__create_club_information_update_request.sql b/src/main/resources/db/migration/V81__create_club_information_update_request.sql index 513de8ae7..78775e425 100644 --- a/src/main/resources/db/migration/V81__create_club_information_update_request.sql +++ b/src/main/resources/db/migration/V81__create_club_information_update_request.sql @@ -25,5 +25,6 @@ CREATE TABLE IF NOT EXISTS club_information_update_request_image created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL, + CONSTRAINT uq_club_information_update_request_image_order UNIQUE (request_id, display_order), FOREIGN KEY (request_id) REFERENCES club_information_update_request (id) ON DELETE CASCADE ) COMMENT '동아리 정보 수정 요청 이미지'; diff --git a/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java b/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java index a0f227141..6110b3d4b 100644 --- a/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java +++ b/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java @@ -146,7 +146,7 @@ void requestClubInformationUpdateWithUnknownClub() throws Exception { ClubInformationUpdateRequestDto request = createInformationUpdateRequest(); // when & then - performPost("/clubs/999999/information-update-requests", request) + performPost("/clubs/" + Integer.MAX_VALUE + "/information-update-requests", request) .andExpect(status().isNotFound()); } From 59efb3f2e7f9835e58854ba3a75bd277b6af57a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 26 May 2026 23:33:03 +0900 Subject: [PATCH 10/13] =?UTF-8?q?chore:=20=EC=BD=94=EB=93=9C=20=ED=8F=AC?= =?UTF-8?q?=EB=A7=B7=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../konect/domain/club/dto/ClubInformationUpdateRequestDto.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequestDto.java b/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequestDto.java index ae375dac7..b6b33fecb 100644 --- a/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequestDto.java +++ b/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequestDto.java @@ -62,6 +62,6 @@ public record ClubInformationUpdateRequestDto( List< @NotBlank(message = "이미지 URL은 필수입니다.") @Size(max = 500, message = "이미지 URL은 최대 500자입니다.") - String> imageUrls + String> imageUrls ) { } From de35f1121692150c6ee0e658ce590f1fb8f20aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 26 May 2026 23:35:34 +0900 Subject: [PATCH 11/13] =?UTF-8?q?fix:=20PR=20=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=ED=94=BC=EB=93=9C=EB=B0=B1=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 수정 요청 엔티티의 문자열 출력에서 웹 동아리 참조가 노출되지 않도록 안전한 toString 생성을 명시 - 수정 요청 상태값이 허용된 enum 값으로만 저장되도록 DB 제약을 추가 --- .../konect/domain/club/model/ClubInformationUpdateRequest.java | 1 + .../db/migration/V81__create_club_information_update_request.sql | 1 + 2 files changed, 2 insertions(+) diff --git a/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java b/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java index ba02e2d2c..2f33401e9 100644 --- a/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java +++ b/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java @@ -29,6 +29,7 @@ import lombok.ToString; @Getter +@ToString @Entity @Table(name = "club_information_update_request") @NoArgsConstructor(access = PROTECTED) diff --git a/src/main/resources/db/migration/V81__create_club_information_update_request.sql b/src/main/resources/db/migration/V81__create_club_information_update_request.sql index 78775e425..a06d1936a 100644 --- a/src/main/resources/db/migration/V81__create_club_information_update_request.sql +++ b/src/main/resources/db/migration/V81__create_club_information_update_request.sql @@ -13,6 +13,7 @@ CREATE TABLE IF NOT EXISTS club_information_update_request created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP NOT NULL, + CONSTRAINT chk_club_information_update_request_status CHECK (status IN ('PENDING', 'APPROVED', 'REJECTED')), FOREIGN KEY (web_club_id) REFERENCES web_club (id) ) COMMENT '동아리 정보 수정 요청'; From 46fcbbbee24ad299fb3990cf734f408025edaf6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 26 May 2026 23:39:40 +0900 Subject: [PATCH 12/13] =?UTF-8?q?refactor:=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20DTO=20=EC=9D=B4=EB=A6=84=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - API 요청 타입에서 Dto 접미사를 제거해 기존 요청 DTO 네이밍과 맞춤 - 같은 이름의 저장 모델과 충돌하지 않도록 엔티티 타입을 명시적으로 분리 - 서비스와 테스트 참조를 새 타입명에 맞춰 정리해 FQCN 없이 읽히도록 유지 --- .../club/controller/ClubRegistrationRequestApi.java | 4 ++-- .../ClubRegistrationRequestController.java | 4 ++-- ...estDto.java => ClubInformationUpdateRequest.java} | 2 +- .../event/ClubInformationUpdateRequestedEvent.java | 4 ++-- ....java => ClubInformationUpdateRequestEntity.java} | 4 ++-- .../model/ClubInformationUpdateRequestImage.java | 4 ++-- .../ClubInformationUpdateRequestRepository.java | 4 ++-- .../club/service/ClubRegistrationRequestService.java | 12 ++++++------ .../domain/club/ClubRegistrationRequestApiTest.java | 12 ++++++------ .../service/ClubRegistrationRequestServiceTest.java | 11 ++++++----- 10 files changed, 31 insertions(+), 30 deletions(-) rename src/main/java/gg/agit/konect/domain/club/dto/{ClubInformationUpdateRequestDto.java => ClubInformationUpdateRequest.java} (98%) rename src/main/java/gg/agit/konect/domain/club/model/{ClubInformationUpdateRequest.java => ClubInformationUpdateRequestEntity.java} (97%) diff --git a/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestApi.java b/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestApi.java index 02d940ea8..47b53da13 100644 --- a/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestApi.java +++ b/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestApi.java @@ -3,7 +3,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; -import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequestDto; +import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequest; import gg.agit.konect.domain.club.dto.ClubRegistrationRequestDto; import io.swagger.v3.oas.annotations.parameters.RequestBody; @@ -15,6 +15,6 @@ ResponseEntity registerClub( ResponseEntity requestClubInformationUpdate( @PathVariable(name = "clubId") Integer clubId, - @RequestBody ClubInformationUpdateRequestDto request + @RequestBody ClubInformationUpdateRequest request ); } diff --git a/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestController.java b/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestController.java index 91bf98b10..6e46abf8e 100644 --- a/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestController.java +++ b/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestController.java @@ -8,7 +8,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequestDto; +import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequest; import gg.agit.konect.domain.club.dto.ClubRegistrationRequestDto; import gg.agit.konect.domain.club.service.ClubRegistrationRequestService; import gg.agit.konect.global.auth.annotation.PublicApi; @@ -48,7 +48,7 @@ public ResponseEntity registerClub( @PublicApi public ResponseEntity requestClubInformationUpdate( @PathVariable(name = "clubId") Integer clubId, - @Valid @RequestBody ClubInformationUpdateRequestDto request + @Valid @RequestBody ClubInformationUpdateRequest request ) { clubRegistrationRequestService.requestInformationUpdate(clubId, request); return ResponseEntity.status(HttpStatus.CREATED).build(); diff --git a/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequestDto.java b/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequest.java similarity index 98% rename from src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequestDto.java rename to src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequest.java index b6b33fecb..4a6a4cbe1 100644 --- a/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequestDto.java +++ b/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequest.java @@ -11,7 +11,7 @@ import jakarta.validation.constraints.Size; @Schema(name = "ClubInformationUpdateRequest", description = "동아리 정보 수정 요청") -public record ClubInformationUpdateRequestDto( +public record ClubInformationUpdateRequest( @Schema(description = "대학교 명", example = "한국기술교육대학교", requiredMode = REQUIRED) @NotBlank(message = "대학교 명은 필수입니다.") diff --git a/src/main/java/gg/agit/konect/domain/club/event/ClubInformationUpdateRequestedEvent.java b/src/main/java/gg/agit/konect/domain/club/event/ClubInformationUpdateRequestedEvent.java index b59f596e4..f4550eba0 100644 --- a/src/main/java/gg/agit/konect/domain/club/event/ClubInformationUpdateRequestedEvent.java +++ b/src/main/java/gg/agit/konect/domain/club/event/ClubInformationUpdateRequestedEvent.java @@ -2,7 +2,7 @@ import java.util.List; -import gg.agit.konect.domain.club.model.ClubInformationUpdateRequest; +import gg.agit.konect.domain.club.model.ClubInformationUpdateRequestEntity; public record ClubInformationUpdateRequestedEvent( Integer requestId, @@ -24,7 +24,7 @@ public record ClubInformationUpdateRequestedEvent( List requestedImageUrls ) { - public static ClubInformationUpdateRequestedEvent from(ClubInformationUpdateRequest request) { + public static ClubInformationUpdateRequestedEvent from(ClubInformationUpdateRequestEntity request) { return new ClubInformationUpdateRequestedEvent( request.getId(), request.getClub().getId(), diff --git a/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java b/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequestEntity.java similarity index 97% rename from src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java rename to src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequestEntity.java index 2f33401e9..8fa19240a 100644 --- a/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java +++ b/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequestEntity.java @@ -33,7 +33,7 @@ @Entity @Table(name = "club_information_update_request") @NoArgsConstructor(access = PROTECTED) -public class ClubInformationUpdateRequest extends BaseEntity { +public class ClubInformationUpdateRequestEntity extends BaseEntity { @Id @GeneratedValue(strategy = IDENTITY) @@ -85,7 +85,7 @@ public class ClubInformationUpdateRequest extends BaseEntity { private UpdateRequestStatus status; @Builder - private ClubInformationUpdateRequest( + private ClubInformationUpdateRequestEntity( Integer id, WebClub club, String universityName, diff --git a/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequestImage.java b/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequestImage.java index 84549f1dc..1861dbf63 100644 --- a/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequestImage.java +++ b/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequestImage.java @@ -31,7 +31,7 @@ public class ClubInformationUpdateRequestImage extends BaseEntity { @NotNull @ManyToOne(fetch = LAZY) @JoinColumn(name = "request_id", nullable = false) - private ClubInformationUpdateRequest request; + private ClubInformationUpdateRequestEntity request; @NotNull @Column(name = "image_url", length = 500, nullable = false) @@ -44,7 +44,7 @@ public class ClubInformationUpdateRequestImage extends BaseEntity { @Builder private ClubInformationUpdateRequestImage( Integer id, - ClubInformationUpdateRequest request, + ClubInformationUpdateRequestEntity request, String imageUrl, Integer displayOrder ) { diff --git a/src/main/java/gg/agit/konect/domain/club/repository/ClubInformationUpdateRequestRepository.java b/src/main/java/gg/agit/konect/domain/club/repository/ClubInformationUpdateRequestRepository.java index 55ca66907..f3d662d7a 100644 --- a/src/main/java/gg/agit/konect/domain/club/repository/ClubInformationUpdateRequestRepository.java +++ b/src/main/java/gg/agit/konect/domain/club/repository/ClubInformationUpdateRequestRepository.java @@ -2,8 +2,8 @@ import org.springframework.data.jpa.repository.JpaRepository; -import gg.agit.konect.domain.club.model.ClubInformationUpdateRequest; +import gg.agit.konect.domain.club.model.ClubInformationUpdateRequestEntity; public interface ClubInformationUpdateRequestRepository - extends JpaRepository { + extends JpaRepository { } diff --git a/src/main/java/gg/agit/konect/domain/club/service/ClubRegistrationRequestService.java b/src/main/java/gg/agit/konect/domain/club/service/ClubRegistrationRequestService.java index 2b2a25203..0150c5830 100644 --- a/src/main/java/gg/agit/konect/domain/club/service/ClubRegistrationRequestService.java +++ b/src/main/java/gg/agit/konect/domain/club/service/ClubRegistrationRequestService.java @@ -4,11 +4,11 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequestDto; +import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequest; import gg.agit.konect.domain.club.dto.ClubRegistrationRequestDto; import gg.agit.konect.domain.club.event.ClubInformationUpdateRequestedEvent; import gg.agit.konect.domain.club.event.ClubRegistrationRequestedEvent; -import gg.agit.konect.domain.club.model.ClubInformationUpdateRequest; +import gg.agit.konect.domain.club.model.ClubInformationUpdateRequestEntity; import gg.agit.konect.domain.club.model.ClubRegistrationRequest; import gg.agit.konect.domain.club.repository.ClubInformationUpdateRequestRepository; import gg.agit.konect.domain.club.repository.ClubRegistrationRequestRepository; @@ -49,10 +49,10 @@ public void register(ClubRegistrationRequestDto request) { applicationEventPublisher.publishEvent(ClubRegistrationRequestedEvent.from(saved)); } - public void requestInformationUpdate(Integer clubId, ClubInformationUpdateRequestDto request) { + public void requestInformationUpdate(Integer clubId, ClubInformationUpdateRequest request) { WebClub club = websiteQueryRepository.findClub(clubId) .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_CLUB)); - ClubInformationUpdateRequest entity = ClubInformationUpdateRequest.builder() + ClubInformationUpdateRequestEntity entity = ClubInformationUpdateRequestEntity.builder() .club(club) .universityName(request.universityName()) .clubName(request.clubName()) @@ -61,14 +61,14 @@ public void requestInformationUpdate(Integer clubId, ClubInformationUpdateReques .clubEmoji(request.clubEmoji()) .shortDescription(request.shortDescription()) .fullIntroduction(request.fullIntroduction()) - .status(ClubInformationUpdateRequest.UpdateRequestStatus.PENDING) + .status(ClubInformationUpdateRequestEntity.UpdateRequestStatus.PENDING) .build(); if (request.imageUrls() != null && !request.imageUrls().isEmpty()) { entity.addImages(request.imageUrls()); } - ClubInformationUpdateRequest saved = clubInformationUpdateRequestRepository.save(entity); + ClubInformationUpdateRequestEntity saved = clubInformationUpdateRequestRepository.save(entity); applicationEventPublisher.publishEvent(ClubInformationUpdateRequestedEvent.from(saved)); } } diff --git a/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java b/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java index 6110b3d4b..865e07758 100644 --- a/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java +++ b/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java @@ -7,7 +7,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequestDto; +import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequest; import gg.agit.konect.domain.club.dto.ClubRegistrationRequestDto; import gg.agit.konect.domain.club.enums.ClubCategory; import gg.agit.konect.domain.website.model.WebClub; @@ -132,7 +132,7 @@ void requestClubInformationUpdateWithoutLogin() throws Exception { // given WebUniversity university = persist(WebUniversityFixture.create()); WebClub club = persist(WebClubFixture.create(university)); - ClubInformationUpdateRequestDto request = createInformationUpdateRequest(); + ClubInformationUpdateRequest request = createInformationUpdateRequest(); // when & then performPost("/clubs/" + club.getId() + "/information-update-requests", request) @@ -143,7 +143,7 @@ void requestClubInformationUpdateWithoutLogin() throws Exception { @DisplayName("존재하지 않는 동아리에 대한 정보 수정 요청은 404를 반환한다") void requestClubInformationUpdateWithUnknownClub() throws Exception { // given - ClubInformationUpdateRequestDto request = createInformationUpdateRequest(); + ClubInformationUpdateRequest request = createInformationUpdateRequest(); // when & then performPost("/clubs/" + Integer.MAX_VALUE + "/information-update-requests", request) @@ -156,7 +156,7 @@ void requestClubInformationUpdateWithMissingFields() throws Exception { // given WebUniversity university = persist(WebUniversityFixture.create()); WebClub club = persist(WebClubFixture.create(university)); - ClubInformationUpdateRequestDto request = new ClubInformationUpdateRequestDto( + ClubInformationUpdateRequest request = new ClubInformationUpdateRequest( "", "BCSD Lab", ClubCategory.ACADEMIC, @@ -172,8 +172,8 @@ void requestClubInformationUpdateWithMissingFields() throws Exception { .andExpect(status().isBadRequest()); } - private ClubInformationUpdateRequestDto createInformationUpdateRequest() { - return new ClubInformationUpdateRequestDto( + private ClubInformationUpdateRequest createInformationUpdateRequest() { + return new ClubInformationUpdateRequest( "한국기술교육대학교", "BCSD Lab", ClubCategory.ACADEMIC, diff --git a/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java b/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java index 1c33a978a..f99391137 100644 --- a/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java +++ b/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java @@ -15,12 +15,12 @@ import org.mockito.Mock; import org.springframework.context.ApplicationEventPublisher; -import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequestDto; +import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequest; import gg.agit.konect.domain.club.dto.ClubRegistrationRequestDto; import gg.agit.konect.domain.club.enums.ClubCategory; import gg.agit.konect.domain.club.event.ClubInformationUpdateRequestedEvent; import gg.agit.konect.domain.club.event.ClubRegistrationRequestedEvent; -import gg.agit.konect.domain.club.model.ClubInformationUpdateRequest; +import gg.agit.konect.domain.club.model.ClubInformationUpdateRequestEntity; import gg.agit.konect.domain.club.model.ClubRegistrationRequest; import gg.agit.konect.domain.club.repository.ClubInformationUpdateRequestRepository; import gg.agit.konect.domain.club.repository.ClubRegistrationRequestRepository; @@ -107,7 +107,7 @@ void requestInformationUpdatePublishesClubInformationUpdateRequestedEvent() { "현재 동아리명", ClubCategory.HOBBY ); - ClubInformationUpdateRequestDto request = new ClubInformationUpdateRequestDto( + ClubInformationUpdateRequest request = new ClubInformationUpdateRequest( "한국기술교육대학교", "요청 동아리명", ClubCategory.ACADEMIC, @@ -117,7 +117,7 @@ void requestInformationUpdatePublishesClubInformationUpdateRequestedEvent() { "수정 상세 소개입니다.", List.of("https://example.com/image1.jpg") ); - ClubInformationUpdateRequest saved = ClubInformationUpdateRequest.builder() + ClubInformationUpdateRequestEntity saved = ClubInformationUpdateRequestEntity.builder() .id(10) .club(club) .universityName(request.universityName()) @@ -130,7 +130,8 @@ void requestInformationUpdatePublishesClubInformationUpdateRequestedEvent() { .build(); saved.addImages(request.imageUrls()); given(websiteQueryRepository.findClub(club.getId())).willReturn(Optional.of(club)); - given(clubInformationUpdateRequestRepository.save(any(ClubInformationUpdateRequest.class))).willReturn(saved); + given(clubInformationUpdateRequestRepository.save(any(ClubInformationUpdateRequestEntity.class))) + .willReturn(saved); // when clubRegistrationRequestService.requestInformationUpdate(club.getId(), request); From 504f6024cc41d810a8edf151a4b4acb6da3c83ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EB=8F=99=ED=9B=88?= <2dh2@naver.com> Date: Tue, 26 May 2026 23:42:21 +0900 Subject: [PATCH 13/13] =?UTF-8?q?Revert=20"refactor:=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=20=EC=9A=94=EC=B2=AD=20DTO=20=EC=9D=B4=EB=A6=84=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 46fcbbbee24ad299fb3990cf734f408025edaf6b. --- .../club/controller/ClubRegistrationRequestApi.java | 4 ++-- .../ClubRegistrationRequestController.java | 4 ++-- ...est.java => ClubInformationUpdateRequestDto.java} | 2 +- .../event/ClubInformationUpdateRequestedEvent.java | 4 ++-- ...Entity.java => ClubInformationUpdateRequest.java} | 4 ++-- .../model/ClubInformationUpdateRequestImage.java | 4 ++-- .../ClubInformationUpdateRequestRepository.java | 4 ++-- .../club/service/ClubRegistrationRequestService.java | 12 ++++++------ .../domain/club/ClubRegistrationRequestApiTest.java | 12 ++++++------ .../service/ClubRegistrationRequestServiceTest.java | 11 +++++------ 10 files changed, 30 insertions(+), 31 deletions(-) rename src/main/java/gg/agit/konect/domain/club/dto/{ClubInformationUpdateRequest.java => ClubInformationUpdateRequestDto.java} (98%) rename src/main/java/gg/agit/konect/domain/club/model/{ClubInformationUpdateRequestEntity.java => ClubInformationUpdateRequest.java} (97%) diff --git a/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestApi.java b/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestApi.java index 47b53da13..02d940ea8 100644 --- a/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestApi.java +++ b/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestApi.java @@ -3,7 +3,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PathVariable; -import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequest; +import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequestDto; import gg.agit.konect.domain.club.dto.ClubRegistrationRequestDto; import io.swagger.v3.oas.annotations.parameters.RequestBody; @@ -15,6 +15,6 @@ ResponseEntity registerClub( ResponseEntity requestClubInformationUpdate( @PathVariable(name = "clubId") Integer clubId, - @RequestBody ClubInformationUpdateRequest request + @RequestBody ClubInformationUpdateRequestDto request ); } diff --git a/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestController.java b/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestController.java index 6e46abf8e..91bf98b10 100644 --- a/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestController.java +++ b/src/main/java/gg/agit/konect/domain/club/controller/ClubRegistrationRequestController.java @@ -8,7 +8,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequest; +import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequestDto; import gg.agit.konect.domain.club.dto.ClubRegistrationRequestDto; import gg.agit.konect.domain.club.service.ClubRegistrationRequestService; import gg.agit.konect.global.auth.annotation.PublicApi; @@ -48,7 +48,7 @@ public ResponseEntity registerClub( @PublicApi public ResponseEntity requestClubInformationUpdate( @PathVariable(name = "clubId") Integer clubId, - @Valid @RequestBody ClubInformationUpdateRequest request + @Valid @RequestBody ClubInformationUpdateRequestDto request ) { clubRegistrationRequestService.requestInformationUpdate(clubId, request); return ResponseEntity.status(HttpStatus.CREATED).build(); diff --git a/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequest.java b/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequestDto.java similarity index 98% rename from src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequest.java rename to src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequestDto.java index 4a6a4cbe1..b6b33fecb 100644 --- a/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequest.java +++ b/src/main/java/gg/agit/konect/domain/club/dto/ClubInformationUpdateRequestDto.java @@ -11,7 +11,7 @@ import jakarta.validation.constraints.Size; @Schema(name = "ClubInformationUpdateRequest", description = "동아리 정보 수정 요청") -public record ClubInformationUpdateRequest( +public record ClubInformationUpdateRequestDto( @Schema(description = "대학교 명", example = "한국기술교육대학교", requiredMode = REQUIRED) @NotBlank(message = "대학교 명은 필수입니다.") diff --git a/src/main/java/gg/agit/konect/domain/club/event/ClubInformationUpdateRequestedEvent.java b/src/main/java/gg/agit/konect/domain/club/event/ClubInformationUpdateRequestedEvent.java index f4550eba0..b59f596e4 100644 --- a/src/main/java/gg/agit/konect/domain/club/event/ClubInformationUpdateRequestedEvent.java +++ b/src/main/java/gg/agit/konect/domain/club/event/ClubInformationUpdateRequestedEvent.java @@ -2,7 +2,7 @@ import java.util.List; -import gg.agit.konect.domain.club.model.ClubInformationUpdateRequestEntity; +import gg.agit.konect.domain.club.model.ClubInformationUpdateRequest; public record ClubInformationUpdateRequestedEvent( Integer requestId, @@ -24,7 +24,7 @@ public record ClubInformationUpdateRequestedEvent( List requestedImageUrls ) { - public static ClubInformationUpdateRequestedEvent from(ClubInformationUpdateRequestEntity request) { + public static ClubInformationUpdateRequestedEvent from(ClubInformationUpdateRequest request) { return new ClubInformationUpdateRequestedEvent( request.getId(), request.getClub().getId(), diff --git a/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequestEntity.java b/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java similarity index 97% rename from src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequestEntity.java rename to src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java index 8fa19240a..2f33401e9 100644 --- a/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequestEntity.java +++ b/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequest.java @@ -33,7 +33,7 @@ @Entity @Table(name = "club_information_update_request") @NoArgsConstructor(access = PROTECTED) -public class ClubInformationUpdateRequestEntity extends BaseEntity { +public class ClubInformationUpdateRequest extends BaseEntity { @Id @GeneratedValue(strategy = IDENTITY) @@ -85,7 +85,7 @@ public class ClubInformationUpdateRequestEntity extends BaseEntity { private UpdateRequestStatus status; @Builder - private ClubInformationUpdateRequestEntity( + private ClubInformationUpdateRequest( Integer id, WebClub club, String universityName, diff --git a/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequestImage.java b/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequestImage.java index 1861dbf63..84549f1dc 100644 --- a/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequestImage.java +++ b/src/main/java/gg/agit/konect/domain/club/model/ClubInformationUpdateRequestImage.java @@ -31,7 +31,7 @@ public class ClubInformationUpdateRequestImage extends BaseEntity { @NotNull @ManyToOne(fetch = LAZY) @JoinColumn(name = "request_id", nullable = false) - private ClubInformationUpdateRequestEntity request; + private ClubInformationUpdateRequest request; @NotNull @Column(name = "image_url", length = 500, nullable = false) @@ -44,7 +44,7 @@ public class ClubInformationUpdateRequestImage extends BaseEntity { @Builder private ClubInformationUpdateRequestImage( Integer id, - ClubInformationUpdateRequestEntity request, + ClubInformationUpdateRequest request, String imageUrl, Integer displayOrder ) { diff --git a/src/main/java/gg/agit/konect/domain/club/repository/ClubInformationUpdateRequestRepository.java b/src/main/java/gg/agit/konect/domain/club/repository/ClubInformationUpdateRequestRepository.java index f3d662d7a..55ca66907 100644 --- a/src/main/java/gg/agit/konect/domain/club/repository/ClubInformationUpdateRequestRepository.java +++ b/src/main/java/gg/agit/konect/domain/club/repository/ClubInformationUpdateRequestRepository.java @@ -2,8 +2,8 @@ import org.springframework.data.jpa.repository.JpaRepository; -import gg.agit.konect.domain.club.model.ClubInformationUpdateRequestEntity; +import gg.agit.konect.domain.club.model.ClubInformationUpdateRequest; public interface ClubInformationUpdateRequestRepository - extends JpaRepository { + extends JpaRepository { } diff --git a/src/main/java/gg/agit/konect/domain/club/service/ClubRegistrationRequestService.java b/src/main/java/gg/agit/konect/domain/club/service/ClubRegistrationRequestService.java index 0150c5830..2b2a25203 100644 --- a/src/main/java/gg/agit/konect/domain/club/service/ClubRegistrationRequestService.java +++ b/src/main/java/gg/agit/konect/domain/club/service/ClubRegistrationRequestService.java @@ -4,11 +4,11 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequest; +import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequestDto; import gg.agit.konect.domain.club.dto.ClubRegistrationRequestDto; import gg.agit.konect.domain.club.event.ClubInformationUpdateRequestedEvent; import gg.agit.konect.domain.club.event.ClubRegistrationRequestedEvent; -import gg.agit.konect.domain.club.model.ClubInformationUpdateRequestEntity; +import gg.agit.konect.domain.club.model.ClubInformationUpdateRequest; import gg.agit.konect.domain.club.model.ClubRegistrationRequest; import gg.agit.konect.domain.club.repository.ClubInformationUpdateRequestRepository; import gg.agit.konect.domain.club.repository.ClubRegistrationRequestRepository; @@ -49,10 +49,10 @@ public void register(ClubRegistrationRequestDto request) { applicationEventPublisher.publishEvent(ClubRegistrationRequestedEvent.from(saved)); } - public void requestInformationUpdate(Integer clubId, ClubInformationUpdateRequest request) { + public void requestInformationUpdate(Integer clubId, ClubInformationUpdateRequestDto request) { WebClub club = websiteQueryRepository.findClub(clubId) .orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_CLUB)); - ClubInformationUpdateRequestEntity entity = ClubInformationUpdateRequestEntity.builder() + ClubInformationUpdateRequest entity = ClubInformationUpdateRequest.builder() .club(club) .universityName(request.universityName()) .clubName(request.clubName()) @@ -61,14 +61,14 @@ public void requestInformationUpdate(Integer clubId, ClubInformationUpdateReques .clubEmoji(request.clubEmoji()) .shortDescription(request.shortDescription()) .fullIntroduction(request.fullIntroduction()) - .status(ClubInformationUpdateRequestEntity.UpdateRequestStatus.PENDING) + .status(ClubInformationUpdateRequest.UpdateRequestStatus.PENDING) .build(); if (request.imageUrls() != null && !request.imageUrls().isEmpty()) { entity.addImages(request.imageUrls()); } - ClubInformationUpdateRequestEntity saved = clubInformationUpdateRequestRepository.save(entity); + ClubInformationUpdateRequest saved = clubInformationUpdateRequestRepository.save(entity); applicationEventPublisher.publishEvent(ClubInformationUpdateRequestedEvent.from(saved)); } } diff --git a/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java b/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java index 865e07758..6110b3d4b 100644 --- a/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java +++ b/src/test/java/gg/agit/konect/integration/domain/club/ClubRegistrationRequestApiTest.java @@ -7,7 +7,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequest; +import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequestDto; import gg.agit.konect.domain.club.dto.ClubRegistrationRequestDto; import gg.agit.konect.domain.club.enums.ClubCategory; import gg.agit.konect.domain.website.model.WebClub; @@ -132,7 +132,7 @@ void requestClubInformationUpdateWithoutLogin() throws Exception { // given WebUniversity university = persist(WebUniversityFixture.create()); WebClub club = persist(WebClubFixture.create(university)); - ClubInformationUpdateRequest request = createInformationUpdateRequest(); + ClubInformationUpdateRequestDto request = createInformationUpdateRequest(); // when & then performPost("/clubs/" + club.getId() + "/information-update-requests", request) @@ -143,7 +143,7 @@ void requestClubInformationUpdateWithoutLogin() throws Exception { @DisplayName("존재하지 않는 동아리에 대한 정보 수정 요청은 404를 반환한다") void requestClubInformationUpdateWithUnknownClub() throws Exception { // given - ClubInformationUpdateRequest request = createInformationUpdateRequest(); + ClubInformationUpdateRequestDto request = createInformationUpdateRequest(); // when & then performPost("/clubs/" + Integer.MAX_VALUE + "/information-update-requests", request) @@ -156,7 +156,7 @@ void requestClubInformationUpdateWithMissingFields() throws Exception { // given WebUniversity university = persist(WebUniversityFixture.create()); WebClub club = persist(WebClubFixture.create(university)); - ClubInformationUpdateRequest request = new ClubInformationUpdateRequest( + ClubInformationUpdateRequestDto request = new ClubInformationUpdateRequestDto( "", "BCSD Lab", ClubCategory.ACADEMIC, @@ -172,8 +172,8 @@ void requestClubInformationUpdateWithMissingFields() throws Exception { .andExpect(status().isBadRequest()); } - private ClubInformationUpdateRequest createInformationUpdateRequest() { - return new ClubInformationUpdateRequest( + private ClubInformationUpdateRequestDto createInformationUpdateRequest() { + return new ClubInformationUpdateRequestDto( "한국기술교육대학교", "BCSD Lab", ClubCategory.ACADEMIC, diff --git a/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java b/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java index f99391137..1c33a978a 100644 --- a/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java +++ b/src/test/java/gg/agit/konect/unit/domain/club/service/ClubRegistrationRequestServiceTest.java @@ -15,12 +15,12 @@ import org.mockito.Mock; import org.springframework.context.ApplicationEventPublisher; -import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequest; +import gg.agit.konect.domain.club.dto.ClubInformationUpdateRequestDto; import gg.agit.konect.domain.club.dto.ClubRegistrationRequestDto; import gg.agit.konect.domain.club.enums.ClubCategory; import gg.agit.konect.domain.club.event.ClubInformationUpdateRequestedEvent; import gg.agit.konect.domain.club.event.ClubRegistrationRequestedEvent; -import gg.agit.konect.domain.club.model.ClubInformationUpdateRequestEntity; +import gg.agit.konect.domain.club.model.ClubInformationUpdateRequest; import gg.agit.konect.domain.club.model.ClubRegistrationRequest; import gg.agit.konect.domain.club.repository.ClubInformationUpdateRequestRepository; import gg.agit.konect.domain.club.repository.ClubRegistrationRequestRepository; @@ -107,7 +107,7 @@ void requestInformationUpdatePublishesClubInformationUpdateRequestedEvent() { "현재 동아리명", ClubCategory.HOBBY ); - ClubInformationUpdateRequest request = new ClubInformationUpdateRequest( + ClubInformationUpdateRequestDto request = new ClubInformationUpdateRequestDto( "한국기술교육대학교", "요청 동아리명", ClubCategory.ACADEMIC, @@ -117,7 +117,7 @@ void requestInformationUpdatePublishesClubInformationUpdateRequestedEvent() { "수정 상세 소개입니다.", List.of("https://example.com/image1.jpg") ); - ClubInformationUpdateRequestEntity saved = ClubInformationUpdateRequestEntity.builder() + ClubInformationUpdateRequest saved = ClubInformationUpdateRequest.builder() .id(10) .club(club) .universityName(request.universityName()) @@ -130,8 +130,7 @@ void requestInformationUpdatePublishesClubInformationUpdateRequestedEvent() { .build(); saved.addImages(request.imageUrls()); given(websiteQueryRepository.findClub(club.getId())).willReturn(Optional.of(club)); - given(clubInformationUpdateRequestRepository.save(any(ClubInformationUpdateRequestEntity.class))) - .willReturn(saved); + given(clubInformationUpdateRequestRepository.save(any(ClubInformationUpdateRequest.class))).willReturn(saved); // when clubRegistrationRequestService.requestInformationUpdate(club.getId(), request);