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
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,33 @@

import com.shinhan.klljs.domain.team.dto.TeamCreateRequest;
import com.shinhan.klljs.domain.team.dto.TeamCreateResponse;
import com.shinhan.klljs.domain.team.dto.TeamRenameRequest;
import com.shinhan.klljs.domain.team.dto.TeamRenameResponse;
import com.shinhan.klljs.domain.team.service.TeamCreateService;
import com.shinhan.klljs.domain.team.service.TeamMemberManagementService;
import com.shinhan.klljs.global.apiPayload.ApiResponse;
import com.shinhan.klljs.global.apiPayload.code.GeneralSuccessCode;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.web.bind.annotation.PatchMapping;
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.RestController;

/**
* 팀 생성 API (docs/team-creation-api-spec.md 5절).
* 팀 생성·수정 API (docs/team-creation-api-spec.md 5절).
*
* 아직 팀이 없는 사용자가 호출하므로 "인증된 사용자인가"만 확인한다 - 팀 소속 검사가 없다.
* 팀 생성은 아직 팀이 없는 사용자가 호출하므로 "인증된 사용자인가"만 확인한다 - 팀 소속 검사가 없다.
*/
@RestController
@RequiredArgsConstructor
public class TeamController implements TeamControllerDocs {

private final TeamCreateService teamCreateService;
private final TeamMemberManagementService teamMemberManagementService;

@Override
@PostMapping("/api/v1/teams")
Expand All @@ -33,4 +39,16 @@ public ApiResponse<TeamCreateResponse> createTeam(
Long userId = Long.valueOf(jwt.getSubject());
return ApiResponse.onSuccess(GeneralSuccessCode.OK, teamCreateService.create(userId, request));
}

@Override
@PatchMapping("/api/v1/teams/{teamId}")
public ApiResponse<TeamRenameResponse> renameTeam(
@AuthenticationPrincipal Jwt jwt,
@PathVariable Long teamId,
@Valid @RequestBody TeamRenameRequest request
) {
Long userId = Long.valueOf(jwt.getSubject());
TeamRenameResponse response = teamMemberManagementService.renameTeam(userId, teamId, request.teamName());
return ApiResponse.onSuccess(GeneralSuccessCode.OK, response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import com.shinhan.klljs.domain.team.dto.TeamCreateRequest;
import com.shinhan.klljs.domain.team.dto.TeamCreateResponse;
import com.shinhan.klljs.domain.team.dto.TeamRenameRequest;
import com.shinhan.klljs.domain.team.dto.TeamRenameResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
Expand All @@ -14,7 +16,7 @@
/**
* {@link TeamController}의 Swagger 문서 전용 인터페이스.
*/
@Tag(name = "팀", description = "팀 생성 API")
@Tag(name = "팀", description = "팀 생성·수정 API")
public interface TeamControllerDocs {

@Operation(
Expand Down Expand Up @@ -70,4 +72,40 @@ com.shinhan.klljs.global.apiPayload.ApiResponse<TeamCreateResponse> createTeam(
@Parameter(hidden = true) Jwt jwt,
TeamCreateRequest request
);

@Operation(
summary = "팀명 수정",
description = """
팀명만 바꾼다. 팀의 `OWNER`/`ADMIN`만 호출할 수 있고, `MEMBER`는 403이다.
"""
)
@ApiResponses({
@ApiResponse(
responseCode = "200",
description = "수정 성공",
content = @Content(mediaType = "application/json", examples = @ExampleObject(value = """
{
"isSuccess": true,
"code": "COMMON_200_001",
"message": "성공적으로 요청을 처리했습니다.",
"result": {
"teamId": 12,
"teamName": "신한 KLLJS 딥비전스 옥외 광고 3팀"
}
}
"""))
),
@ApiResponse(responseCode = "400", description = "`COMMON_400_002`: 팀명 누락 또는 200자 초과"),
@ApiResponse(responseCode = "403", description = """
- `TEAM_403_001`: 요청자가 이 팀 소속이 아님
- `TEAM_403_004`: 팀 소속이지만 `MEMBER`라 팀 설정을 바꿀 권한이 없음"""),
@ApiResponse(responseCode = "404", description = "`TEAM_404_001`: 팀이 없음"),
@ApiResponse(responseCode = "409", description = "`TEAM_409_001`: 팀이 `ACTIVE` 상태가 아님")
})
com.shinhan.klljs.global.apiPayload.ApiResponse<TeamRenameResponse> renameTeam(
@Parameter(hidden = true) Jwt jwt,
@Parameter(description = "팀 ID", example = "12")
Long teamId,
TeamRenameRequest request
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.shinhan.klljs.domain.team.dto;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public record TeamRenameRequest(
@NotBlank(message = "팀명을 입력해 주세요.")
@Size(max = 200, message = "팀명은 200자를 넘을 수 없습니다.")
String teamName
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.shinhan.klljs.domain.team.dto;

import com.shinhan.klljs.domain.team.entity.Team;

public record TeamRenameResponse(
Long teamId,
String teamName
) {

public static TeamRenameResponse of(Team team) {
return new TeamRenameResponse(team.getId(), team.getTeamName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public enum TeamErrorCode implements BaseErrorCode {
"TEAM_403_003",
"캠페인을 등록하거나 삭제할 권한이 없습니다."
),
TEAM_SETTINGS_FORBIDDEN(HttpStatus.FORBIDDEN, "TEAM_403_004", "팀 설정을 변경할 권한이 없습니다."),

TEAM_NOT_FOUND(HttpStatus.NOT_FOUND, "TEAM_404_001", "팀을 찾을 수 없습니다."),
TEAM_MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "TEAM_404_002", "활성 팀원을 찾을 수 없습니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.shinhan.klljs.domain.team.dto.TeamMemberListResponse;
import com.shinhan.klljs.domain.team.dto.TeamMemberRoleChangeResponse;
import com.shinhan.klljs.domain.team.dto.TeamMemberSummary;
import com.shinhan.klljs.domain.team.dto.TeamRenameResponse;
import com.shinhan.klljs.domain.team.dto.UpdatedTeamMember;
import com.shinhan.klljs.domain.team.entity.Team;
import com.shinhan.klljs.domain.team.entity.TeamMember;
Expand Down Expand Up @@ -60,6 +61,21 @@ public TeamMemberListResponse getMembers(Long requesterId, Long teamId, String k
return new TeamMemberListResponse(team.getId(), team.getTeamName(), summaries);
}

/** OWNER/ADMIN만 팀명을 바꿀 수 있다 - MEMBER는 조회만 가능하고 팀 설정은 못 건드린다. */
@Transactional
public TeamRenameResponse renameTeam(Long requesterId, Long teamId, String teamName) {
Team team = lockTeam(teamId);
TeamMember requester = requireActiveMember(requesterId, teamId);
requireActiveTeam(team);

if (requester.getRole() == TeamMemberRole.MEMBER) {
throw new GeneralException(TeamErrorCode.TEAM_SETTINGS_FORBIDDEN);
}

team.rename(teamName);
return TeamRenameResponse.of(team);
}

@Transactional
public TeamMemberRoleChangeResponse changeRole(
Long requesterId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.shinhan.klljs.domain.team.dto.TeamMemberListResponse;
import com.shinhan.klljs.domain.team.dto.TeamMemberRoleChangeResponse;
import com.shinhan.klljs.domain.team.dto.TeamRenameResponse;
import com.shinhan.klljs.domain.team.entity.Team;
import com.shinhan.klljs.domain.team.entity.TeamMember;
import com.shinhan.klljs.domain.team.entity.TeamMemberRole;
Expand Down Expand Up @@ -96,6 +97,31 @@ void changeRole_returnsCurrentRoleForIdempotentRequest() {
assertThat(response.updatedMembers().getFirst().role()).isEqualTo(TeamMemberRole.ADMIN);
}

@Test
void renameTeam_allowsOwnerAndAdminButRejectsMember() {
Team team = persistTeam(TeamStatus.ACTIVE);
TeamMember owner = persistMember(team, "오너", "owner@example.com", TeamMemberRole.OWNER, TeamMemberStatus.ACTIVE);
TeamMember admin = persistMember(team, "관리자", "admin@example.com", TeamMemberRole.ADMIN, TeamMemberStatus.ACTIVE);
TeamMember member = persistMember(team, "멤버", "member@example.com", TeamMemberRole.MEMBER, TeamMemberStatus.ACTIVE);
entityManager.flush();

TeamRenameResponse response = service.renameTeam(owner.getUser().getId(), team.getId(), "새 팀명");
entityManager.flush();

assertThat(response.teamId()).isEqualTo(team.getId());
assertThat(response.teamName()).isEqualTo("새 팀명");
assertThat(team.getTeamName()).isEqualTo("새 팀명");

service.renameTeam(admin.getUser().getId(), team.getId(), "관리자가 바꾼 팀명");
assertThat(team.getTeamName()).isEqualTo("관리자가 바꾼 팀명");

assertTeamError(
() -> service.renameTeam(member.getUser().getId(), team.getId(), "멤버가 시도한 팀명"),
TeamErrorCode.TEAM_SETTINGS_FORBIDDEN
);
assertThat(team.getTeamName()).isEqualTo("관리자가 바꾼 팀명");
}

@Test
void removeAndLeave_changeMembershipStatuses() {
Team team = persistTeam(TeamStatus.ACTIVE);
Expand Down Expand Up @@ -155,6 +181,10 @@ void mutationsRejectInactiveTeam() {
() -> service.removeMember(owner.getUser().getId(), team.getId(), target.getUser().getId()),
TeamErrorCode.TEAM_NOT_ACTIVE
);
assertTeamError(
() -> service.renameTeam(owner.getUser().getId(), team.getId(), "정지된 팀명"),
TeamErrorCode.TEAM_NOT_ACTIVE
);
}

private Team persistTeam(TeamStatus status) {
Expand Down
Loading