Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
7a2bc28
feat : club icon_image_path 컬럼 마이그레이션 추가
rlagkswn00 Feb 27, 2026
64de3e3
feat : 동아리 생성 성공 응답 코드 추가
rlagkswn00 Feb 27, 2026
c952f0b
feat : 동아리 중복 생성 에러 코드 추가
rlagkswn00 Feb 27, 2026
ed432da
feat : 어드민 동아리 생성 요청 DTO 확장
rlagkswn00 Feb 27, 2026
8c2a32a
feat : 어드민 동아리 생성 응답 DTO 추가
rlagkswn00 Feb 27, 2026
e5e82e2
feat : 어드민 동아리 생성 API 추가
rlagkswn00 Feb 27, 2026
ec908cb
feat : 동아리 생성 유스케이스 인터페이스 추가
rlagkswn00 Feb 27, 2026
0723951
feat : 동아리 생성 커맨드 정의 추가
rlagkswn00 Feb 27, 2026
9e5c9ec
feat : 동아리 생성 CommandPort 메서드 추가
rlagkswn00 Feb 27, 2026
9c060dd
feat : 동아리 생성 이벤트 포트 확장
rlagkswn00 Feb 27, 2026
dcf603a
feat : 동아리 중복 조회 포트 메서드 추가
rlagkswn00 Feb 27, 2026
db5c4f1
feat : 동아리 이름 소속 중복 조회 리포지토리 메서드 추가
rlagkswn00 Feb 27, 2026
e2d3d48
feat : ClubSns JPA Repository 구현 추가
rlagkswn00 Feb 27, 2026
1630111
feat : 동아리 생성 중복체크 persistence 구현 추가
rlagkswn00 Feb 27, 2026
60b4628
feat : 동아리 생성 필드 및 생성자 확장
rlagkswn00 Feb 27, 2026
82b7da5
feat : 동아리 카테고리 한글 매핑 지원 추가
rlagkswn00 Feb 27, 2026
baa6e94
feat : 동아리 소속 한글 매핑 지원 추가
rlagkswn00 Feb 27, 2026
4506e35
feat : 동아리 SNS URL 도메인 객체 매핑 추가
rlagkswn00 Feb 27, 2026
adf5728
feat : SNS URL 기반 타입 판별 로직 추가
rlagkswn00 Feb 27, 2026
c785833
feat : 스토리지 업로드 유스케이스 인터페이스 정의
rlagkswn00 Feb 27, 2026
b35f837
feat : 다중 파일 업로드 커맨드 모델 추가
rlagkswn00 Feb 27, 2026
2c13cf3
feat : 단일 이미지 업로드 커맨드 모델 추가
rlagkswn00 Feb 27, 2026
73455d0
feat : 이미지 삭제 이벤트 모델 확장
rlagkswn00 Feb 27, 2026
8ae5787
feat : 동아리 생성 이미지 업로드 이벤트 구현
rlagkswn00 Feb 27, 2026
ba010c6
feat : 동아리 생성 후 이미지 업로드 이벤트 리스너 추가
rlagkswn00 Feb 27, 2026
a383b0d
feat : 동아리 생성 이벤트 발행 어댑터 구현
rlagkswn00 Feb 27, 2026
aea95d5
fix : 파일 스트림 업로드 처리 및 예외 로깅 보강
rlagkswn00 Feb 27, 2026
98b746c
feat : 동아리 생성 서비스 로직 및 검증 구현
rlagkswn00 Feb 27, 2026
f882cb8
test : 어드민 동아리 생성 요청 응답 스텝 추가
rlagkswn00 Feb 27, 2026
d44ef89
test : 어드민 동아리 정보 업로드 인수테스트 추가
rlagkswn00 Feb 27, 2026
e358f08
test : ClubCategory 변환 테스트 정리
rlagkswn00 Feb 27, 2026
dfeca2c
test : ClubDivision 변환 테스트 정리
rlagkswn00 Feb 27, 2026
14eb28f
fix : 스토리지 Mock 어댑터 프로필 설정 조정
rlagkswn00 Feb 27, 2026
3181c99
fix : 스토리지 S3 어댑터 프로필 설정 조정
rlagkswn00 Feb 27, 2026
5eca5f4
refactor : logoImage를 iconImage 이름 통일
rlagkswn00 Feb 27, 2026
de5ca50
fix : posterImage Null 분기 처리
rlagkswn00 Feb 27, 2026
5a5a0c9
fix : 주석처리 해놓은 어노테이션 제거
rlagkswn00 Feb 27, 2026
2800922
refactor : 동아리 등록 API 파라미터명 변경
rlagkswn00 Feb 27, 2026
c319b56
fix : 이벤트 생성 간 path 중복 사용 해결
rlagkswn00 Feb 27, 2026
c32f062
remove : 불필요 파일 제거
rlagkswn00 Feb 27, 2026
bb6a1b8
remove : 미사용 파일 제거
rlagkswn00 Feb 27, 2026
eb7b40b
fix : reference type 대신 primitive type 사용하도록 수정
rlagkswn00 Feb 27, 2026
9d517ad
fix : ClubSnsType 매핑 과정 구체화 및 예외 케이스 처리
rlagkswn00 Feb 27, 2026
aca5447
test : ClubSnsType 도메인 테스트 추가
rlagkswn00 Feb 27, 2026
5eced2e
fix : ClubSnsType NPE 예외 처리
rlagkswn00 Feb 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.google.firebase.database.annotations.NotNull;
import com.kustacks.kuring.admin.adapter.in.web.dto.AcademicTestNotificationRequest;
import com.kustacks.kuring.admin.adapter.in.web.dto.AdminAlertCreateRequest;
import com.kustacks.kuring.admin.adapter.in.web.dto.AdminClubCreateRequest;
import com.kustacks.kuring.admin.adapter.in.web.dto.AdminClubCreateResponse;
import com.kustacks.kuring.admin.adapter.in.web.dto.RealNotificationRequest;
import com.kustacks.kuring.admin.adapter.in.web.dto.TestNotificationRequest;
import com.kustacks.kuring.admin.application.port.in.AdminCommandUseCase;
Expand All @@ -13,6 +15,7 @@
import com.kustacks.kuring.auth.authorization.AuthenticationPrincipal;
import com.kustacks.kuring.auth.context.Authentication;
import com.kustacks.kuring.auth.secured.Secured;
import com.kustacks.kuring.club.application.port.in.ClubCreateAdminUseCase;
import com.kustacks.kuring.common.dto.BaseResponse;
import com.kustacks.kuring.common.dto.ResponseCodeAndMessages;
import com.kustacks.kuring.common.utils.converter.StringToDateTimeConverter;
Expand All @@ -22,6 +25,7 @@
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
Expand All @@ -33,10 +37,12 @@
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.ADMIN_CLUB_CREATE_SUCCESS;
import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.ADMIN_EMBEDDING_NOTICE_SUCCESS;
import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.ADMIN_REAL_NOTICE_CREATE_SUCCESS;
import static com.kustacks.kuring.common.dto.ResponseCodeAndMessages.ADMIN_TEST_ACADEMIC_CREATE_SUCCESS;
Expand All @@ -50,6 +56,7 @@
public class AdminCommandApiV2 {

private final AdminCommandUseCase adminCommandUseCase;
private final ClubCreateAdminUseCase clubCreateAdminUseCase;
private final BadWordInitProcessor badWordinitProcessor;
private final WhitelistWordInitProcessor whitelistWordInitProcessor;

Expand Down Expand Up @@ -128,6 +135,21 @@ public ResponseEntity<BaseResponse<String>> embeddingCustomData(@RequestParam(na
return ResponseEntity.ok().body(new BaseResponse<>(ADMIN_EMBEDDING_NOTICE_SUCCESS, null));
}

@Operation(summary = "동아리 생성", description = "어드민이 신규 동아리 정보를 생성합니다")
@SecurityRequirement(name = "JWT")
@Secured(AdminRole.ROLE_ROOT)
@PostMapping(value = "/clubs", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ResponseEntity<BaseResponse<AdminClubCreateResponse>> createClub(
@Valid @RequestPart("request") AdminClubCreateRequest request,
@RequestPart(name = "iconImage", required = true) MultipartFile iconImage,
@RequestPart(name = "posterImage", required = false) MultipartFile posterImage
) {
clubCreateAdminUseCase.createClub(request.toCommand(iconImage, posterImage));

return ResponseEntity.status(ADMIN_CLUB_CREATE_SUCCESS.getCode())
.body(new BaseResponse<>(ADMIN_CLUB_CREATE_SUCCESS, null));
}

@Operation(summary = "금칙어 로드", description = "어드민은 DB에 있는 금칙어를 수동으로 로드할 수 있다.")
@SecurityRequirement(name = "JWT")
@Secured(AdminRole.ROLE_ROOT)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.kustacks.kuring.admin.adapter.in.web.dto;

import com.kustacks.kuring.club.application.port.in.dto.AdminClubCreateCommand;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import org.springframework.web.multipart.MultipartFile;

public record AdminClubCreateRequest(
@NotBlank @Size(max = 30) String name,
@NotBlank @Size(max = 30) String summary,
String description,
@NotBlank String category,
@NotBlank String division,
@NotNull Boolean isAlways,
String recruitStartAt,
String recruitEndAt,
@Size(max = 255) String applyUrl,
String qualifications,
String instagramUrl,
String youtubeUrl,
String etcUrl,
@Size(max = 30) String building,
@Size(max = 30) String room,
Double lat,
Double lon
) {
public AdminClubCreateCommand toCommand(MultipartFile iconImage, MultipartFile posterImage) {
return new AdminClubCreateCommand(
name,
summary,
description,
category,
division,
isAlways,
recruitStartAt,
recruitEndAt,
applyUrl,
qualifications,
instagramUrl,
youtubeUrl,
etcUrl,
building,
room,
lat,
lon,
iconImage,
posterImage
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.kustacks.kuring.admin.adapter.in.web.dto;

public record AdminClubCreateResponse(
Long clubId
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.kustacks.kuring.admin.adapter.out.event;

import com.kustacks.kuring.club.application.port.out.ClubEventPort;
import com.kustacks.kuring.common.domain.Events;
import com.kustacks.kuring.common.exception.InvalidStateException;
import com.kustacks.kuring.common.exception.code.ErrorCode;
import com.kustacks.kuring.storage.adapter.in.event.dto.ClubCreateEvent;
import com.kustacks.kuring.storage.adapter.in.event.dto.ClubCreateEvent.ClubCreateImage;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;

@Component
public class ClubEventAdapter implements ClubEventPort {

@Override
public void publishClubCreate(Long clubId, MultipartFile iconImage, MultipartFile posterImage, String iconImagePath, String posterImagePath) {
try {
ClubCreateImage icon = new ClubCreateImage(iconImagePath, iconImage);

if (posterImage != null) {
ClubCreateImage poster = new ClubCreateImage(posterImagePath, posterImage);
Events.raise(new ClubCreateEvent(clubId, icon, poster));
} else {
Events.raise(new ClubCreateEvent(clubId, icon, null));
}

} catch (IOException e) {
throw new InvalidStateException(ErrorCode.FILE_IO_EXCEPTION);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.kustacks.kuring.club.adapter.out.persistence;

import com.kustacks.kuring.club.application.port.out.ClubCommandPort;
import com.kustacks.kuring.club.application.port.out.ClubQueryPort;
import com.kustacks.kuring.club.application.port.out.ClubSubscriptionCommandPort;
import com.kustacks.kuring.club.application.port.out.ClubSubscriptionQueryPort;
import com.kustacks.kuring.club.domain.Club;
import com.kustacks.kuring.club.domain.ClubDivision;
import com.kustacks.kuring.club.domain.ClubSns;
import com.kustacks.kuring.club.domain.ClubSubscribe;
import com.kustacks.kuring.common.annotation.PersistenceAdapter;
import com.kustacks.kuring.user.domain.RootUser;
Expand All @@ -16,9 +19,11 @@

@PersistenceAdapter
@RequiredArgsConstructor
public class ClubPersistenceAdapter implements ClubQueryPort, ClubSubscriptionCommandPort, ClubSubscriptionQueryPort {
public class ClubPersistenceAdapter implements ClubQueryPort, ClubCommandPort,
ClubSubscriptionCommandPort, ClubSubscriptionQueryPort{

private final ClubRepository clubRepository;
private final ClubSnsRepository clubSnsRepository;
private final ClubSubscribeRepository clubSubscribeRepository;

@Override
Expand Down Expand Up @@ -58,4 +63,19 @@ public void deleteSubscription(RootUser rootUser, Club club) {
public long countSubscriptions(Long rootUserId) {
return clubSubscribeRepository.countByRootUserId(rootUserId);
}

@Override
public boolean existsByNameAndDivision(String name, ClubDivision division) {
return clubRepository.existsByNameAndDivision(name, division);
}

@Override
public Club save(Club club) {
return clubRepository.save(club);
}

@Override
public void saveAll(List<ClubSns> toSave) {
clubSnsRepository.saveAll(toSave);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.kustacks.kuring.club.adapter.out.persistence;

import com.kustacks.kuring.club.domain.Club;
import com.kustacks.kuring.club.domain.ClubDivision;
import org.springframework.data.jpa.repository.JpaRepository;

interface ClubRepository extends JpaRepository<Club, Long>, ClubQueryRepository {
boolean existsByNameAndDivision(String name, ClubDivision division);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.kustacks.kuring.club.adapter.out.persistence;

import com.kustacks.kuring.club.domain.ClubSns;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ClubSnsRepository extends JpaRepository<ClubSns, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.kustacks.kuring.club.application.port.in;

import com.kustacks.kuring.club.application.port.in.dto.AdminClubCreateCommand;

public interface ClubCreateAdminUseCase {
void createClub(AdminClubCreateCommand command);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.kustacks.kuring.club.application.port.in.dto;

import org.springframework.web.multipart.MultipartFile;

public record AdminClubCreateCommand(
String name,
String summary,
String description,
String category,
String division,
boolean isAlways,
String recruitStartAt,
String recruitEndAt,
String applyUrl,
String qualifications,
String instagramUrl,
String youtubeUrl,
String etcUrl,
String building,
String room,
Double lat,
Double lon,
MultipartFile iconImage,
MultipartFile posterImage
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.kustacks.kuring.club.application.port.out;

import com.kustacks.kuring.club.domain.Club;
import com.kustacks.kuring.club.domain.ClubSns;

import java.util.List;

public interface ClubCommandPort {
Club save(Club club);

void saveAll(List<ClubSns> toSave);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.kustacks.kuring.club.application.port.out;

import org.springframework.web.multipart.MultipartFile;

public interface ClubEventPort {
void publishClubCreate(Long clubId, MultipartFile iconImage, MultipartFile posterImage, String iconImagePath, String posterImagePath);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.kustacks.kuring.club.application.port.out;

import com.kustacks.kuring.club.domain.Club;
import com.kustacks.kuring.club.domain.ClubDivision;

import java.time.LocalDateTime;
import java.util.List;
Expand All @@ -13,4 +14,6 @@ public interface ClubQueryPort {
List<Club> findClubsBetweenDates(LocalDateTime start, LocalDateTime end);

List<Club> findNextDayRecruitEndClubs(LocalDateTime now);

boolean existsByNameAndDivision(String name, ClubDivision division);
}
Loading
Loading