Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
40 changes: 35 additions & 5 deletions src/main/java/gg/agit/konect/domain/club/controller/ClubApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import gg.agit.konect.domain.club.dto.ClubApplyQuestionsResponse;
import gg.agit.konect.domain.club.dto.ClubApplyRequest;
import gg.agit.konect.domain.club.dto.ClubCondition;
import gg.agit.konect.domain.club.dto.ClubCreateRequest;
import gg.agit.konect.domain.club.dto.ClubDetailResponse;
import gg.agit.konect.domain.club.dto.ClubFeeInfoReplaceRequest;
import gg.agit.konect.domain.club.dto.ClubFeeInfoResponse;
Expand All @@ -22,6 +23,7 @@
import gg.agit.konect.domain.club.dto.ClubRecruitmentCreateRequest;
import gg.agit.konect.domain.club.dto.ClubRecruitmentResponse;
import gg.agit.konect.domain.club.dto.ClubRecruitmentUpdateRequest;
import gg.agit.konect.domain.club.dto.ClubUpdateRequest;
import gg.agit.konect.domain.club.dto.ClubsResponse;
import gg.agit.konect.global.auth.annotation.UserId;
import io.swagger.v3.oas.annotations.Operation;
Expand Down Expand Up @@ -53,6 +55,34 @@ ResponseEntity<ClubDetailResponse> getClubDetail(
@UserId Integer userId
);

@Operation(summary = "새로운 동아리를 생성한다.", description = """
새로운 동아리를 생성하고, 생성한 사용자를 회장으로 등록합니다.

## 에러
- NOT_FOUND_USER (404): 유저를 찾을 수 없습니다.
""")
@PostMapping
ResponseEntity<ClubDetailResponse> createClub(
@Valid @RequestBody ClubCreateRequest request,
@UserId Integer userId
);

@Operation(summary = "동아리 정보를 수정한다.", description = """
동아리 회장 또는 매니저만 동아리 정보를 수정할 수 있습니다.
수정 가능 항목: 동아리명, 한 줄 소개, 로고 이미지, 위치, 분과, 상세 소개

## 에러
- FORBIDDEN_CLUB_MANAGER_ACCESS (403): 동아리 매니저 권한이 없습니다.
- NOT_FOUND_CLUB (404): 동아리를 찾을 수 없습니다.
- NOT_FOUND_USER (404): 유저를 찾을 수 없습니다.
""")
@PutMapping("/{clubId}")
ResponseEntity<ClubDetailResponse> updateClub(
@PathVariable(name = "clubId") Integer clubId,
@Valid @RequestBody ClubUpdateRequest request,
@UserId Integer userId
);

@Operation(summary = "가입한 동아리 리스트를 조회한다.")
@GetMapping("/joined")
ResponseEntity<ClubMembershipsResponse> getJoinedClubs(
Expand All @@ -75,7 +105,7 @@ ResponseEntity<ClubMembersResponse> getClubMembers(
@Operation(summary = "동아리 가입 신청을 한다.", description = """
동아리 가입 신청서를 제출합니다.
설문 질문이 없는 경우 answers는 빈 배열을 전달합니다.

- ALREADY_APPLIED_CLUB (409): 이미 가입 신청을 완료한 사용자입니다.
- NOT_FOUND_CLUB_APPLY_QUESTION (404): 존재하지 않는 가입 문항입니다.
- DUPLICATE_CLUB_APPLY_QUESTION (409): 중복된 id의 가입 문항이 포함되어 있습니다.
Expand Down Expand Up @@ -130,7 +160,7 @@ ResponseEntity<ClubApplyQuestionsResponse> getApplyQuestions(
- questionId가 없으면 생성
- 요청에 없는 기존 문항은 삭제됩니다.
- 저장된 문항 목록을 반환합니다.

## 에러
- FORBIDDEN_CLUB_MANAGER_ACCESS (403): 동아리 매니저 권한이 없습니다.
- NOT_FOUND_CLUB_APPLY_QUESTION (404): 존재하지 않는 가입 문항입니다.
Expand All @@ -145,10 +175,10 @@ ResponseEntity<ClubApplyQuestionsResponse> replaceApplyQuestions(

@Operation(summary = "동아리 모집 정보를 조회한다.", description = """
동아리의 모집 공고 상세 정보를 조회합니다.

- status는 모집 기간에 따라 BEFORE(모집 전), ONGOING(모집 중), CLOSED(모집 마감)으로 반환됩니다.
- 동아리 멤버이거나 지원 이력이 존재할 경우 isApplied는 true로 반환됩니다.

## 에러
- NOT_FOUND_CLUB (404): 동아리를 찾을 수 없습니다.
- NOT_FOUND_USER (404): 유저를 찾을 수 없습니다.
Expand All @@ -163,7 +193,7 @@ ResponseEntity<ClubRecruitmentResponse> getRecruitments(
@Operation(summary = "동아리 모집 정보를 생성한다.", description = """
동아리 회장만 모집 공고를 생성할 수 있습니다.
한 동아리당 하나의 모집 공고만 생성 가능합니다.

## 에러
- INVALID_RECRUITMENT_DATE_NOT_ALLOWED (400): 상시 모집인 경우 모집 시작일과 마감일을 지정할 수 없습니다.
- INVALID_RECRUITMENT_DATE_REQUIRED (400): 상시 모집이 아닐 경우 모집 시작일과 마감일이 필수입니다.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import gg.agit.konect.domain.club.dto.ClubApplyQuestionsReplaceRequest;
import gg.agit.konect.domain.club.dto.ClubApplyRequest;
import gg.agit.konect.domain.club.dto.ClubCondition;
import gg.agit.konect.domain.club.dto.ClubCreateRequest;
import gg.agit.konect.domain.club.dto.ClubDetailResponse;
import gg.agit.konect.domain.club.dto.ClubFeeInfoReplaceRequest;
import gg.agit.konect.domain.club.dto.ClubFeeInfoResponse;
Expand All @@ -20,6 +21,7 @@
import gg.agit.konect.domain.club.dto.ClubRecruitmentCreateRequest;
import gg.agit.konect.domain.club.dto.ClubRecruitmentResponse;
import gg.agit.konect.domain.club.dto.ClubRecruitmentUpdateRequest;
import gg.agit.konect.domain.club.dto.ClubUpdateRequest;
import gg.agit.konect.domain.club.dto.ClubsResponse;
import gg.agit.konect.domain.club.service.ClubService;
import gg.agit.konect.global.auth.annotation.UserId;
Expand Down Expand Up @@ -51,6 +53,25 @@ public ResponseEntity<ClubDetailResponse> getClubDetail(
return ResponseEntity.ok(response);
}

@Override
public ResponseEntity<ClubDetailResponse> createClub(
@Valid @RequestBody ClubCreateRequest request,
@UserId Integer userId
) {
ClubDetailResponse response = clubService.createClub(userId, request);
return ResponseEntity.ok(response);
}

@Override
public ResponseEntity<ClubDetailResponse> updateClub(
@PathVariable(name = "clubId") Integer clubId,
@Valid @RequestBody ClubUpdateRequest request,
@UserId Integer userId
) {
ClubDetailResponse response = clubService.updateClub(clubId, userId, request);
return ResponseEntity.ok(response);
}

@Override
public ResponseEntity<ClubMembershipsResponse> getJoinedClubs(@UserId Integer userId) {
ClubMembershipsResponse response = clubService.getJoinedClubs(userId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package gg.agit.konect.domain.club.dto;

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 io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

public record ClubCreateRequest(
@Schema(description = "동아리 이름", example = "BCSD Lab", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "동아리 이름은 필수 입력입니다.")
@Size(max = 50, message = "동아리 이름은 50자 이하여야 합니다.")
String name,

@Schema(description = "동아리 한 줄 소개", example = "즐겁게 일하고 열심히 노는 IT 특성화 동아리",
requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "동아리 소개는 필수 입력입니다.")
@Size(max = 100, message = "동아리 소개는 100자 이하여야 합니다.")
String description,

@Schema(description = "동아리 상세 소개", example = "BCSD에서 얻을 수 있는 경험\n1. IT 실무 경험",
requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "상세 소개는 필수 입력입니다.")
String introduce,

@Schema(description = "동아리 로고 이미지 URL", example = "https://example.com/logo.png",
requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "이미지 URL은 필수 입력입니다.")
@Size(max = 255, message = "이미지 URL은 255자 이하여야 합니다.")
String imageUrl,

@Schema(description = "동아리 방 위치", example = "학생회관 101호", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "동아리 위치는 필수입니다.")
@Size(max = 255, message = "동아리 위치는 255자 이하여야 합니다.")
String location,

@Schema(description = "동아리 분과", example = "ACADEMIC", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "동아리 분과는 필수입니다.")
ClubCategory clubCategory
) {
public Club toEntity(University university) {
return Club.builder()
.name(name)
.description(description)
.introduce(introduce)
.imageUrl(imageUrl)
.location(location)
.clubCategory(clubCategory)
.university(university)
.build();
}
Comment on lines +43 to +53
Copy link
Contributor

Choose a reason for hiding this comment

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

toEntity 메소드보다 Club 엔티티에서 of 라는 네이밍의 정적 팩토리 메소드를 만드는건 어떻게 생각하시나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

이 부분은 깊게 고민 못해봤던 것 같습니다
조금 고민해보았는데, 말씀해주신 대로 Clubof 를 만드는 것이 더 적절한 것 같습니다
정적 팩토리 메소드도 생성자처럼 객체를 만들어주는 역할을 하기 때문에,
객체를 생성하는 것과 관련된 건 객체(Club)안에 응집시키는 것이 좋을 것 같습니다. 수정하겠습니다

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package gg.agit.konect.domain.club.dto;

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;

public record ClubUpdateRequest(
@Schema(description = "동아리 이름", example = "BCSD Lab", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "동아리 이름은 필수 입력입니다.")
@Size(max = 50, message = "동아리 이름은 50자 이하여야 합니다.")
String name,

@Schema(description = "동아리 한 줄 소개", example = "즐겁게 일하고 열심히 노는 IT 특성화 동아리",
requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "동아리 소개는 필수 입력입니다.")
@Size(max = 100, message = "동아리 소개는 100자 이하여야 합니다.")
String description,

@Schema(description = "동아리 로고 이미지 URL", example = "https://example.com/logo.png",
requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "이미지 URL은 필수 입력입니다.")
@Size(max = 255, message = "이미지 URL은 255자 이하여야 합니다.")
String imageUrl,

@Schema(description = "동아리 방 위치", example = "학생회관 101호", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "동아리 위치는 필수입니다.")
@Size(max = 255, message = "동아리 위치는 255자 이하여야 합니다.")
String location,

@Schema(description = "동아리 분과", example = "ACADEMIC", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "동아리 분과는 필수입니다.")
ClubCategory clubCategory,

@Schema(description = "동아리 상세 소개", example = "BCSD에서 얻을 수 있는 경험\n1. IT 실무 경험",
requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "상세 소개는 필수 입력입니다.")
String introduce
) {
}
29 changes: 29 additions & 0 deletions src/main/java/gg/agit/konect/domain/club/model/Club.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import org.springframework.util.StringUtils;

import gg.agit.konect.domain.club.dto.ClubCreateRequest;
import gg.agit.konect.domain.club.enums.ClubCategory;
import gg.agit.konect.domain.university.model.University;
import gg.agit.konect.global.exception.CustomException;
Expand Down Expand Up @@ -115,6 +116,18 @@ private Club(
this.clubRecruitment = clubRecruitment;
}

public static Club of(ClubCreateRequest request, University university) {
return Club.builder()
.name(request.name())
.description(request.description())
.introduce(request.introduce())
.imageUrl(request.imageUrl())
.location(request.location())
.clubCategory(request.clubCategory())
.university(university)
.build();
}

public void replaceFeeInfo(
Integer feeAmount,
String feeBank,
Expand All @@ -134,6 +147,22 @@ public void replaceFeeInfo(
updateFeeInfo(feeAmount, feeBank, feeAccountNumber, feeAccountHolder, feeDeadline);
}

public void update(
String name,
String description,
String imageUrl,
String location,
ClubCategory clubCategory,
String introduce
) {
this.name = name;
this.description = description;
this.imageUrl = imageUrl;
this.location = location;
this.clubCategory = clubCategory;
this.introduce = introduce;
}

private boolean isFeeInfoEmpty(
Integer feeAmount,
String feeBank,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,8 @@ public boolean isPresident() {
public boolean isSameUser(Integer userId) {
return this.user.getId().equals(userId);
}

public void updatePosition(ClubPosition clubPosition) {
this.clubPosition = clubPosition;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,6 @@ boolean existsByClubIdAndUserIdAndPositionGroupIn(
List<ClubMember> findByUserIdIn(@Param("userIds") List<Integer> userIds);

void deleteByUserId(Integer userId);

ClubMember save(ClubMember clubMember);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package gg.agit.konect.domain.club.repository;

import org.springframework.data.repository.Repository;

import gg.agit.konect.domain.club.model.ClubPosition;

public interface ClubPositionRepository extends Repository<ClubPosition, Integer> {

ClubPosition save(ClubPosition clubPosition);
}


Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,6 @@ default Club getById(Integer id) {
return findById(id).orElseThrow(() ->
CustomException.of(ApiResponseCode.NOT_FOUND_CLUB));
}

Club save(Club club);
}
Loading
Loading