Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
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.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
Expand All @@ -32,14 +32,12 @@
@RequestMapping("/api/v1/projects")
public class ProjectController {

private static final String CURRENT_USER_ID_HEADER = "X-USER-ID";

private final ProjectService projectService;

@Operation(summary = "프로젝트 목록 조회")
@GetMapping
public ApiResponse<ProjectListResponse> getProjects(
@RequestHeader(CURRENT_USER_ID_HEADER) Long currentUserId,
@AuthenticationPrincipal Long currentUserId,
@RequestParam(required = false) ProjectStatus status,
@RequestParam(required = false) Long cursor,
@RequestParam(defaultValue = "20") int size
Expand All @@ -53,7 +51,7 @@ public ApiResponse<ProjectListResponse> getProjects(
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public ApiResponse<ProjectResponse> createProject(
@RequestHeader(CURRENT_USER_ID_HEADER) Long currentUserId,
@AuthenticationPrincipal Long currentUserId,
@Valid @RequestBody ProjectCreateRequest request
) {
ProjectResponse response = projectService.createProject(currentUserId, request);
Expand All @@ -64,7 +62,7 @@ public ApiResponse<ProjectResponse> createProject(
@Operation(summary = "프로젝트 상세 조회")
@GetMapping("/{projectId}")
public ApiResponse<ProjectDetailResponse> getProject(
@RequestHeader(CURRENT_USER_ID_HEADER) Long currentUserId,
@AuthenticationPrincipal Long currentUserId,
@PathVariable Long projectId
) {
ProjectDetailResponse response = projectService.getProject(projectId, currentUserId);
Expand All @@ -75,7 +73,7 @@ public ApiResponse<ProjectDetailResponse> getProject(
@Operation(summary = "프로젝트 수정")
@PatchMapping("/{projectId}")
public ApiResponse<ProjectResponse> updateProject(
@RequestHeader(CURRENT_USER_ID_HEADER) Long currentUserId,
@AuthenticationPrincipal Long currentUserId,
@PathVariable Long projectId,
@Valid @RequestBody ProjectUpdateRequest request
) {
Expand All @@ -87,7 +85,7 @@ public ApiResponse<ProjectResponse> updateProject(
@Operation(summary = "프로젝트 삭제")
@DeleteMapping("/{projectId}")
public ApiResponse<Void> deleteProject(
@RequestHeader(CURRENT_USER_ID_HEADER) Long currentUserId,
@AuthenticationPrincipal Long currentUserId,
@PathVariable Long projectId
) {
projectService.deleteProject(projectId, currentUserId);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.slatto.domain.project.controller;

import com.slatto.domain.project.dto.ProjectInvitationAcceptResponse;
import com.slatto.domain.project.dto.ProjectInvitationAcceptRequest;
import com.slatto.domain.project.dto.ProjectInvitationCreateRequest;
import com.slatto.domain.project.dto.ProjectInvitationCreateResponse;
import com.slatto.domain.project.dto.ProjectInvitationDetailResponse;
Expand All @@ -12,11 +13,11 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
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.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -27,15 +28,13 @@
@RequestMapping("/api/v1")
public class ProjectInvitationController {

private static final String CURRENT_USER_ID_HEADER = "X-USER-ID";

private final ProjectInvitationService projectInvitationService;

@Operation(summary = "프로젝트 초대 링크 생성")
@PostMapping("/projects/{projectId}/invitations")
@ResponseStatus(HttpStatus.CREATED)
public ApiResponse<ProjectInvitationCreateResponse> createInvitation(
@RequestHeader(CURRENT_USER_ID_HEADER) Long currentUserId,
@AuthenticationPrincipal Long currentUserId,
@PathVariable Long projectId,
@Valid @RequestBody(required = false) ProjectInvitationCreateRequest request
) {
Expand All @@ -61,12 +60,14 @@ public ApiResponse<ProjectInvitationDetailResponse> getInvitation(
@Operation(summary = "프로젝트 초대 수락")
@PostMapping("/project-invitations/{token}/accept")
public ApiResponse<ProjectInvitationAcceptResponse> acceptInvitation(
@RequestHeader(CURRENT_USER_ID_HEADER) Long currentUserId,
@PathVariable String token
@AuthenticationPrincipal Long currentUserId,
@PathVariable String token,
@Valid @RequestBody ProjectInvitationAcceptRequest request
) {
ProjectInvitationAcceptResponse response = projectInvitationService.acceptInvitation(
token,
currentUserId
currentUserId,
request
);

return ApiResponse.success(CommonSuccessCode.OK, response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -25,14 +25,12 @@
@RequestMapping("/api/v1/projects/{projectId}/members")
public class ProjectMemberController {

private static final String CURRENT_USER_ID_HEADER = "X-USER-ID";

private final ProjectMemberService projectMemberService;

@Operation(summary = "프로젝트 멤버 목록 조회")
@GetMapping
public ApiResponse<ProjectMemberListResponse> getProjectMembers(
@RequestHeader(CURRENT_USER_ID_HEADER) Long currentUserId,
@AuthenticationPrincipal Long currentUserId,
@PathVariable Long projectId
) {
ProjectMemberListResponse response = projectMemberService.getProjectMembers(
Expand All @@ -46,7 +44,7 @@ public ApiResponse<ProjectMemberListResponse> getProjectMembers(
@Operation(summary = "프로젝트 나가기")
@DeleteMapping("/me")
public ApiResponse<Void> leaveProject(
@RequestHeader(CURRENT_USER_ID_HEADER) Long currentUserId,
@AuthenticationPrincipal Long currentUserId,
@PathVariable Long projectId
) {
projectMemberService.leaveProject(projectId, currentUserId);
Expand All @@ -57,7 +55,7 @@ public ApiResponse<Void> leaveProject(
@Operation(summary = "프로젝트 멤버 상세 조회")
@GetMapping("/{memberId}")
public ApiResponse<ProjectMemberDetailResponse> getProjectMember(
@RequestHeader(CURRENT_USER_ID_HEADER) Long currentUserId,
@AuthenticationPrincipal Long currentUserId,
@PathVariable Long projectId,
@PathVariable Long memberId
) {
Expand All @@ -73,7 +71,7 @@ public ApiResponse<ProjectMemberDetailResponse> getProjectMember(
@Operation(summary = "프로젝트 멤버 역할 수정")
@PatchMapping("/{memberId}")
public ApiResponse<ProjectMemberDetailResponse> updateProjectMemberRoles(
@RequestHeader(CURRENT_USER_ID_HEADER) Long currentUserId,
@AuthenticationPrincipal Long currentUserId,
@PathVariable Long projectId,
@PathVariable Long memberId,
@Valid @RequestBody ProjectMemberUpdateRequest request
Expand All @@ -91,7 +89,7 @@ public ApiResponse<ProjectMemberDetailResponse> updateProjectMemberRoles(
@Operation(summary = "프로젝트 멤버 삭제")
@DeleteMapping("/{memberId}")
public ApiResponse<Void> removeProjectMember(
@RequestHeader(CURRENT_USER_ID_HEADER) Long currentUserId,
@AuthenticationPrincipal Long currentUserId,
@PathVariable Long projectId,
@PathVariable Long memberId
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,13 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
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.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
Expand All @@ -30,14 +30,12 @@
@RequestMapping("/api/v1/projects/{projectId}/notices")
public class ProjectNoticeController {

private static final String CURRENT_USER_ID_HEADER = "X-USER-ID";

private final ProjectNoticeService projectNoticeService;

@Operation(summary = "프로젝트 공지 목록 조회")
@GetMapping
public ApiResponse<ProjectNoticeListResponse> getProjectNotices(
@RequestHeader(CURRENT_USER_ID_HEADER) Long currentUserId,
@AuthenticationPrincipal Long currentUserId,
@PathVariable Long projectId,
@RequestParam(required = false) Long cursor,
@RequestParam(defaultValue = "20") int size
Expand All @@ -56,7 +54,7 @@ public ApiResponse<ProjectNoticeListResponse> getProjectNotices(
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public ApiResponse<ProjectNoticeResponse> createProjectNotice(
@RequestHeader(CURRENT_USER_ID_HEADER) Long currentUserId,
@AuthenticationPrincipal Long currentUserId,
@PathVariable Long projectId,
@Valid @RequestBody ProjectNoticeCreateRequest request
) {
Expand All @@ -72,7 +70,7 @@ public ApiResponse<ProjectNoticeResponse> createProjectNotice(
@Operation(summary = "프로젝트 공지 수정")
@PatchMapping("/{noticeId}")
public ApiResponse<ProjectNoticeResponse> updateProjectNotice(
@RequestHeader(CURRENT_USER_ID_HEADER) Long currentUserId,
@AuthenticationPrincipal Long currentUserId,
@PathVariable Long projectId,
@PathVariable Long noticeId,
@Valid @RequestBody ProjectNoticeUpdateRequest request
Expand All @@ -90,7 +88,7 @@ public ApiResponse<ProjectNoticeResponse> updateProjectNotice(
@Operation(summary = "프로젝트 공지 삭제")
@DeleteMapping("/{noticeId}")
public ApiResponse<Void> deleteProjectNotice(
@RequestHeader(CURRENT_USER_ID_HEADER) Long currentUserId,
@AuthenticationPrincipal Long currentUserId,
@PathVariable Long projectId,
@PathVariable Long noticeId
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.slatto.domain.project.dto;

import com.slatto.domain.user.enums.RoleName;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.List;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class ProjectInvitationAcceptRequest {

@NotEmpty(message = "프로젝트에서의 역할은 1개 이상 선택해야 합니다.")
private List<@NotNull(message = "프로젝트에서의 역할은 필수입니다.") RoleName> roleNames;
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.slatto.domain.project.dto;

import com.slatto.domain.user.enums.RoleName;
import lombok.Builder;
import lombok.Getter;

import java.time.LocalDateTime;
import java.util.List;

@Getter
@Builder
Expand All @@ -13,5 +15,7 @@ public class ProjectInvitationAcceptResponse {

private Long memberId;

private List<RoleName> roleNames;

private LocalDateTime joinedAt;
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
package com.slatto.domain.project.service;

import com.slatto.domain.project.dto.ProjectInvitationAcceptRequest;
import com.slatto.domain.project.dto.ProjectInvitationAcceptResponse;
import com.slatto.domain.project.dto.ProjectInvitationCreateRequest;
import com.slatto.domain.project.dto.ProjectInvitationCreateResponse;
import com.slatto.domain.project.dto.ProjectInvitationDetailResponse;
import com.slatto.domain.project.entity.Project;
import com.slatto.domain.project.entity.ProjectInvitation;
import com.slatto.domain.project.entity.ProjectMember;
import com.slatto.domain.project.entity.ProjectUserRole;
import com.slatto.domain.project.enums.ExpirationPeriod;
import com.slatto.domain.project.exception.ProjectErrorCode;
import com.slatto.domain.project.repository.ProjectInvitationRepository;
import com.slatto.domain.project.repository.ProjectMemberRepository;
import com.slatto.domain.project.repository.ProjectUserRoleRepository;
import com.slatto.domain.user.entity.Users;
import com.slatto.domain.user.enums.RoleName;
import com.slatto.domain.user.repository.UserRepository;
import com.slatto.global.config.properties.ProjectInvitationProperties;
import com.slatto.global.exception.BaseException;
Expand All @@ -27,6 +31,7 @@
import java.time.LocalDateTime;
import java.util.Base64;
import java.util.HexFormat;
import java.util.List;

@Service
@RequiredArgsConstructor
Expand All @@ -39,6 +44,7 @@ public class ProjectInvitationService {

private final ProjectInvitationRepository projectInvitationRepository;
private final ProjectMemberRepository projectMemberRepository;
private final ProjectUserRoleRepository projectUserRoleRepository;
private final UserRepository userRepository;
private final ProjectAccessValidator projectAccessValidator;
private final ProjectInvitationProperties projectInvitationProperties;
Expand Down Expand Up @@ -84,7 +90,11 @@ public ProjectInvitationDetailResponse getInvitation(String token) {
}

@Transactional
public ProjectInvitationAcceptResponse acceptInvitation(String token, Long currentUserId) {
public ProjectInvitationAcceptResponse acceptInvitation(
String token,
Long currentUserId,
ProjectInvitationAcceptRequest request
) {
ProjectInvitation projectInvitation = getInvitationByTokenForUpdate(token);

validateAcceptableInvitation(projectInvitation);
Expand All @@ -98,15 +108,29 @@ public ProjectInvitationAcceptResponse acceptInvitation(String token, Long curre

ProjectMember projectMember = ProjectMember.createMember(project, accepter);
projectMemberRepository.save(projectMember);
List<RoleName> roleNames = request.getRoleNames()
.stream()
.distinct()
.toList();
saveProjectRoles(projectMember, roleNames);
projectInvitation.accept(accepter);

return ProjectInvitationAcceptResponse.builder()
.projectId(project.getId())
.memberId(projectMember.getId())
.roleNames(roleNames)
.joinedAt(projectMember.getJoinedAt())
.build();
}

private void saveProjectRoles(ProjectMember projectMember, List<RoleName> roleNames) {
List<ProjectUserRole> projectUserRoles = roleNames.stream()
.map(roleName -> ProjectUserRole.create(projectMember, roleName))
.toList();

projectUserRoleRepository.saveAll(projectUserRoles);
}

private void validateAcceptableInvitation(ProjectInvitation projectInvitation) {
if (projectInvitation.isAccepted()) {
throw new BaseException(ProjectErrorCode.PROJECT_INVITATION_ALREADY_ACCEPTED);
Expand Down
Loading
Loading