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
55 changes: 48 additions & 7 deletions src/main/java/gg/agit/konect/domain/club/controller/ClubApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
import gg.agit.konect.domain.club.dto.ClubApplyQuestionsReplaceRequest;
import gg.agit.konect.domain.club.dto.ClubApplyQuestionsResponse;
import gg.agit.konect.domain.club.dto.ClubApplyRequest;
import gg.agit.konect.domain.club.dto.ClubBasicInfoUpdateRequest;
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.ClubDetailUpdateRequest;
import gg.agit.konect.domain.club.dto.ClubFeeInfoReplaceRequest;
import gg.agit.konect.domain.club.dto.ClubFeeInfoResponse;
import gg.agit.konect.domain.club.dto.ClubMemberAddRequest;
Expand All @@ -30,10 +32,11 @@
import gg.agit.konect.domain.club.dto.ClubPositionCreateRequest;
import gg.agit.konect.domain.club.dto.ClubPositionUpdateRequest;
import gg.agit.konect.domain.club.dto.ClubPositionsResponse;
import gg.agit.konect.domain.club.dto.ClubProfileUpdateRequest;
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.ClubTagsResponse;
import gg.agit.konect.domain.club.dto.ClubsResponse;
import gg.agit.konect.domain.club.dto.MemberPositionChangeRequest;
import gg.agit.konect.domain.club.dto.PresidentTransferRequest;
Expand Down Expand Up @@ -81,22 +84,60 @@ ResponseEntity<ClubDetailResponse> createClub(
@UserId Integer userId
);

@Operation(summary = "동아리 정보를 수정한다.", description = """
동아리 회장 또는 부회장만 동아리 정보를 수정할 수 있습니다.
수정 가능 항목: 동아리명, 한 줄 소개, 로고 이미지, 위치, 분과, 상세 소개
@Operation(summary = "동아리 프로필을 수정한다.", description = """
동아리 회장 또는 부회장만 동아리 프로필을 수정할 수 있습니다.
수정 가능 항목: 한 줄 소개, 로고 이미지, 태그
동아리명과 분과는 수정할 수 없으며, 변경이 필요한 경우 문의하기를 통해 어드민에게 요청하세요.

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

@Operation(summary = "동아리 상세정보를 수정한다.", description = """
동아리 회장 또는 부회장만 동아리 상세정보를 수정할 수 있습니다.
수정 가능 항목: 동방 위치, 상세 소개

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

@Operation(summary = "동아리 기본정보를 수정한다 (어드민 전용).", description = """
어드민만 동아리 기본정보를 수정할 수 있습니다.
수정 가능 항목: 동아리명, 분과
일반 관리자는 이 API를 사용할 수 없으며, 변경이 필요한 경우 문의하기를 통해 어드민에게 요청하세요.

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

@Operation(summary = "사용 가능한 전체 태그 목록을 조회한다.")
@GetMapping("/tags")
ResponseEntity<ClubTagsResponse> getTags();

@Operation(summary = "가입한 동아리 리스트를 조회한다.")
@GetMapping("/joined")
ResponseEntity<ClubMembershipsResponse> getJoinedClubs(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
import gg.agit.konect.domain.club.dto.ClubApplyQuestionsReplaceRequest;
import gg.agit.konect.domain.club.dto.ClubApplyQuestionsResponse;
import gg.agit.konect.domain.club.dto.ClubApplyRequest;
import gg.agit.konect.domain.club.dto.ClubBasicInfoUpdateRequest;
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.ClubDetailUpdateRequest;
import gg.agit.konect.domain.club.dto.ClubFeeInfoReplaceRequest;
import gg.agit.konect.domain.club.dto.ClubFeeInfoResponse;
import gg.agit.konect.domain.club.dto.ClubMemberAddRequest;
Expand All @@ -26,10 +28,11 @@
import gg.agit.konect.domain.club.dto.ClubPositionCreateRequest;
import gg.agit.konect.domain.club.dto.ClubPositionUpdateRequest;
import gg.agit.konect.domain.club.dto.ClubPositionsResponse;
import gg.agit.konect.domain.club.dto.ClubProfileUpdateRequest;
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.ClubTagsResponse;
import gg.agit.konect.domain.club.dto.ClubsResponse;
import gg.agit.konect.domain.club.dto.MemberPositionChangeRequest;
import gg.agit.konect.domain.club.dto.PresidentTransferRequest;
Expand Down Expand Up @@ -78,12 +81,38 @@ public ResponseEntity<ClubDetailResponse> createClub(
}

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

@Override
public ResponseEntity<Void> updateDetails(
@PathVariable(name = "clubId") Integer clubId,
@Valid @RequestBody ClubDetailUpdateRequest request,
@UserId Integer userId
) {
clubService.updateDetails(clubId, userId, request);
return ResponseEntity.noContent().build();
}

@Override
public ResponseEntity<Void> updateBasicInfo(
@PathVariable(name = "clubId") Integer clubId,
@Valid @RequestBody ClubBasicInfoUpdateRequest request,
@UserId Integer userId
) {
clubService.updateBasicInfo(clubId, userId, request);
return ResponseEntity.noContent().build();
}

@Override
public ResponseEntity<ClubTagsResponse> getTags() {
ClubTagsResponse response = clubService.getTags();
return ResponseEntity.ok(response);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
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 ClubBasicInfoUpdateRequest(
@Schema(description = "동아리 이름", example = "BCSD Lab", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "동아리 이름은 필수 입력입니다.")
@Size(max = 50, message = "동아리 이름은 50자 이하여야 합니다.")
String name,

@Schema(description = "동아리 분과", example = "ACADEMIC", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "동아리 분과는 필수 입력입니다.")
ClubCategory clubCategory
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import gg.agit.konect.domain.club.model.Club;
import gg.agit.konect.domain.club.model.ClubMember;
import gg.agit.konect.domain.club.model.ClubRecruitment;
import gg.agit.konect.domain.user.model.User;
import gg.agit.konect.domain.club.model.ClubTagMap;
import io.swagger.v3.oas.annotations.media.Schema;

public record ClubDetailResponse(
Expand Down Expand Up @@ -51,8 +51,11 @@ public record ClubDetailResponse(
@Schema(description = "동아리 모집 정보", requiredMode = REQUIRED)
InnerRecruitment recruitment,

@Schema(description = "동아리 대표 임원진", requiredMode = REQUIRED)
List<InnerRepresentative> representatives,
@Schema(description = "동아리 회장 이름", example = "김철수", requiredMode = REQUIRED)
String presidentName,

@Schema(description = "동아리 태그 목록", requiredMode = REQUIRED)
List<ClubTagResponse> tags,
Comment on lines +54 to +58
Copy link

Copilot AI Jan 25, 2026

Choose a reason for hiding this comment

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

ClubDetailResponse에서 기존 representatives(연락처/이메일 포함) 필드를 제거하고 presidentName만 내려주도록 변경되어 응답 스키마가 깨지는 변경입니다. 하위 호환이 필요하다면 기존 필드를 유지하거나 버저닝/마이그레이션 계획(프론트/외부 소비자 반영)을 명확히 해 주세요.

Copilot uses AI. Check for mistakes.

@Schema(description = "동아리 소속 여부", example = "true", requiredMode = REQUIRED)
Boolean isMember,
Expand Down Expand Up @@ -80,30 +83,19 @@ public static InnerRecruitment from(ClubRecruitment clubRecruitment) {
}
}

public record InnerRepresentative(
@Schema(description = "동아리 대표 임원진 이름", example = "김철수", requiredMode = REQUIRED)
String name,

@Schema(description = "동아리 대표 임원진 전화번호", example = "01012345678", requiredMode = REQUIRED)
String phone,

@Schema(description = "동아리 대표 임원진 이메일", example = "example@koreatech.ac.kr", requiredMode = REQUIRED)
String email
) {
public static InnerRepresentative from(ClubMember clubMember) {
User user = clubMember.getUser();
return new InnerRepresentative(user.getName(), user.getPhoneNumber(), user.getEmail());
}
}

public static ClubDetailResponse of(
Club club,
Integer memberCount,
ClubRecruitment clubRecruitment,
List<ClubMember> clubPresidents,
ClubMember president,
List<ClubTagMap> clubTagMaps,
Boolean isMember,
Boolean isApplied
) {
List<ClubTagResponse> tags = clubTagMaps.stream()
.map(tagMap -> ClubTagResponse.from(tagMap.getTag()))
.toList();

return new ClubDetailResponse(
club.getId(),
club.getName(),
Expand All @@ -114,9 +106,8 @@ public static ClubDetailResponse of(
club.getClubCategory().getDescription(),
memberCount,
InnerRecruitment.from(clubRecruitment),
clubPresidents.stream()
.map(InnerRepresentative::from)
.toList(),
president.getUser().getName(),
tags,
isMember,
isApplied
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package gg.agit.konect.domain.club.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

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

@Schema(description = "동아리 상세 소개", example = "BCSD에서 얻을 수 있는 경험\n1. IT 실무 경험",
requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "상세 소개는 필수 입력입니다.")
String introduce
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package gg.agit.konect.domain.club.dto;

import java.util.List;

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 ClubProfileUpdateRequest(
@Schema(description = "동아리 한 줄 소개", example = "즐겁게 일하고 열심히 노는 IT 특성화 동아리",
requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "한 줄 소개는 필수 입력입니다.")
@Size(max = 20, message = "한 줄 소개는 20자 이하여야 합니다.")
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 = "동아리 태그 ID 목록", example = "[1, 2, 3]",
requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "태그 목록은 필수 입력입니다.")
List<Integer> tagIds
) {
}
16 changes: 16 additions & 0 deletions src/main/java/gg/agit/konect/domain/club/dto/ClubTagResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package gg.agit.konect.domain.club.dto;

import gg.agit.konect.domain.club.model.ClubTag;
import io.swagger.v3.oas.annotations.media.Schema;

public record ClubTagResponse(
@Schema(description = "태그 ID", example = "1", requiredMode = Schema.RequiredMode.REQUIRED)
Integer id,

@Schema(description = "태그 이름", example = "웹개발", requiredMode = Schema.RequiredMode.REQUIRED)
String name
) {
public static ClubTagResponse from(ClubTag clubTag) {
return new ClubTagResponse(clubTag.getId(), clubTag.getName());
}
}
18 changes: 18 additions & 0 deletions src/main/java/gg/agit/konect/domain/club/dto/ClubTagsResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package gg.agit.konect.domain.club.dto;

import java.util.List;

import gg.agit.konect.domain.club.model.ClubTag;
import io.swagger.v3.oas.annotations.media.Schema;

public record ClubTagsResponse(
@Schema(description = "태그 목록", requiredMode = Schema.RequiredMode.REQUIRED)
List<ClubTagResponse> tags
) {
public static ClubTagsResponse from(List<ClubTag> clubTags) {
List<ClubTagResponse> tags = clubTags.stream()
.map(ClubTagResponse::from)
.toList();
return new ClubTagsResponse(tags);
}
}
21 changes: 10 additions & 11 deletions src/main/java/gg/agit/konect/domain/club/model/Club.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,22 +147,21 @@ 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;
public void updateProfile(String introduce, String imageUrl) {
this.description = introduce;
this.imageUrl = imageUrl;
}

public void updateDetails(String location, String introduce) {
this.location = location;
this.clubCategory = clubCategory;
this.introduce = introduce;
}

public void updateBasicInfo(String name, ClubCategory clubCategory) { // 어드민 계정으로만 사용 가능
this.name = name;
this.clubCategory = clubCategory;
}

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

public void updatePosition(ClubPosition clubPosition) {
this.clubPosition = clubPosition;
}


public void changePosition(ClubPosition clubPosition) {
this.clubPosition = clubPosition;
}
Expand Down
Loading
Loading