Merge branch 'be/feat/37-project' into 'be/develop'
Feat: 프로젝트 로직 추가 - S11P21S002-37 See merge request s11-s-project/S11P21S002!16
This commit is contained in:
commit
bd944c8365
@ -1,7 +1,6 @@
|
|||||||
package com.worlabel.domain.label.entity;
|
package com.worlabel.domain.label.entity;
|
||||||
|
|
||||||
import com.worlabel.domain.image.entity.Image;
|
import com.worlabel.domain.image.entity.Image;
|
||||||
import com.worlabel.domain.project.entity.LabelCategory;
|
|
||||||
import com.worlabel.global.common.BaseEntity;
|
import com.worlabel.global.common.BaseEntity;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
|
@ -1,10 +1,14 @@
|
|||||||
package com.worlabel.domain.participant.entity;
|
package com.worlabel.domain.participant.entity;
|
||||||
|
|
||||||
|
import com.worlabel.domain.member.entity.Member;
|
||||||
|
import com.worlabel.domain.project.entity.Project;
|
||||||
import com.worlabel.global.common.BaseEntity;
|
import com.worlabel.global.common.BaseEntity;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.hibernate.annotations.OnDelete;
|
||||||
|
import org.hibernate.annotations.OnDeleteAction;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 프로젝트 참여 Entity
|
* 프로젝트 참여 Entity
|
||||||
@ -16,17 +20,43 @@ import lombok.NoArgsConstructor;
|
|||||||
public class Participant extends BaseEntity {
|
public class Participant extends BaseEntity {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 프로젝트 PK
|
* 프로젝트 참여 PK
|
||||||
*/
|
*/
|
||||||
@Id
|
@Id
|
||||||
@Column(name = "participant_id")
|
@Column(name = "participant_id")
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 프로젝트
|
||||||
|
*/
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "project_id", nullable = false)
|
||||||
|
@OnDelete(action = OnDeleteAction.CASCADE)
|
||||||
|
private Project project;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사용자
|
||||||
|
*/
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "member_id", nullable = false)
|
||||||
|
@OnDelete(action = OnDeleteAction.CASCADE)
|
||||||
|
private Member member;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 프로젝트 권한
|
* 프로젝트 권한
|
||||||
*/
|
*/
|
||||||
@Column(name = "participant_privilege", nullable = false)
|
@Column(name = "participant_privilege", nullable = false)
|
||||||
@Enumerated(EnumType.STRING)
|
@Enumerated(EnumType.STRING)
|
||||||
private PrivilegeType privilege;
|
private PrivilegeType privilege;
|
||||||
|
|
||||||
|
private Participant(final Project project, final Member member, final PrivilegeType privilege) {
|
||||||
|
this.project = project;
|
||||||
|
this.member = member;
|
||||||
|
this.privilege = privilege;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Participant of(final Project project, final Member member, final PrivilegeType privilege) {
|
||||||
|
return new Participant(project, member, privilege);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,51 @@
|
|||||||
|
package com.worlabel.domain.participant.entity;
|
||||||
|
|
||||||
|
import com.worlabel.domain.member.entity.Member;
|
||||||
|
import com.worlabel.domain.workspace.entity.Workspace;
|
||||||
|
import com.worlabel.global.common.BaseEntity;
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.hibernate.annotations.OnDelete;
|
||||||
|
import org.hibernate.annotations.OnDeleteAction;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Entity
|
||||||
|
@Table(name = "workspace_participant")
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
|
public class WorkspaceParticipant extends BaseEntity {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 워크스페이스 프로젝트 참여 PK
|
||||||
|
*/
|
||||||
|
@Id
|
||||||
|
@Column(name = "workspace_participant_id")
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 프로젝트
|
||||||
|
*/
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "workspace_id", nullable = false)
|
||||||
|
@OnDelete(action = OnDeleteAction.CASCADE)
|
||||||
|
private Workspace workspace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 사용자
|
||||||
|
*/
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "member_id", nullable = false)
|
||||||
|
@OnDelete(action = OnDeleteAction.CASCADE)
|
||||||
|
private Member member;
|
||||||
|
|
||||||
|
private WorkspaceParticipant(final Workspace workspace, final Member member) {
|
||||||
|
this.workspace = workspace;
|
||||||
|
this.member = member;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static WorkspaceParticipant of(final Workspace workspace, final Member member) {
|
||||||
|
return new WorkspaceParticipant(workspace, member);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.worlabel.domain.participant.repository;
|
||||||
|
|
||||||
|
import com.worlabel.domain.participant.entity.Participant;
|
||||||
|
import com.worlabel.domain.participant.entity.PrivilegeType;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface ParticipantRepository extends JpaRepository<Participant, Integer> {
|
||||||
|
|
||||||
|
boolean existsByMemberIdAndProjectId(Integer memberId, Integer projectId);
|
||||||
|
|
||||||
|
boolean existsByProjectIdAndMemberIdAndPrivilege(Integer projectId, Integer memberId, PrivilegeType privilege);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.worlabel.domain.participant.repository;
|
||||||
|
|
||||||
|
import com.worlabel.domain.participant.entity.WorkspaceParticipant;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface WorkspaceParticipantRepository extends JpaRepository<WorkspaceParticipant, Integer> {
|
||||||
|
|
||||||
|
boolean existsByMemberIdAndWorkspaceId(Integer memberId, Integer workspaceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,87 @@
|
|||||||
|
package com.worlabel.domain.project.controller;
|
||||||
|
|
||||||
|
import com.worlabel.domain.project.entity.Project;
|
||||||
|
import com.worlabel.domain.project.entity.dto.ProjectRequest;
|
||||||
|
import com.worlabel.domain.project.entity.dto.ProjectResponse;
|
||||||
|
import com.worlabel.domain.project.entity.dto.ProjectResponses;
|
||||||
|
import com.worlabel.domain.project.service.ProjectService;
|
||||||
|
import com.worlabel.global.annotation.CurrentUser;
|
||||||
|
import com.worlabel.global.config.swagger.SwaggerApiError;
|
||||||
|
import com.worlabel.global.config.swagger.SwaggerApiSuccess;
|
||||||
|
import com.worlabel.global.exception.ErrorCode;
|
||||||
|
import com.worlabel.global.response.BaseResponse;
|
||||||
|
import com.worlabel.global.response.SuccessResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Tag(name = "프로젝트 관련 API")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ProjectController {
|
||||||
|
|
||||||
|
private final ProjectService projectService;
|
||||||
|
|
||||||
|
@Operation(summary = "프로젝트 생성", description = "새로운 프로젝트를 생성합니다.")
|
||||||
|
@SwaggerApiSuccess(description = "프로젝트를 성공적으로 생성합니다.")
|
||||||
|
@SwaggerApiError({ErrorCode.EMPTY_REQUEST_PARAMETER, ErrorCode.SERVER_ERROR})
|
||||||
|
@PostMapping("/workspaces/{workspace_id}/projects")
|
||||||
|
public BaseResponse<ProjectResponse> createProject(
|
||||||
|
@CurrentUser final Integer memberId,
|
||||||
|
@PathVariable("workspace_id") final Integer workspaceId,
|
||||||
|
@Valid @RequestBody final ProjectRequest projectRequest) {
|
||||||
|
ProjectResponse project = projectService.createProject(memberId, workspaceId, projectRequest);
|
||||||
|
return SuccessResponse.of(project);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "프로젝트 조회", description = "프로젝트를 조회합니다.")
|
||||||
|
@SwaggerApiSuccess(description = "프로젝트를 성공적으로 조회합니다.")
|
||||||
|
@SwaggerApiError({ErrorCode.PROJECT_NOT_FOUND, ErrorCode.PARTICIPANT_UNAUTHORIZED, ErrorCode.SERVER_ERROR})
|
||||||
|
@GetMapping("/projects/{project_id}")
|
||||||
|
public BaseResponse<ProjectResponse> getProject(@CurrentUser final Integer memberId, @PathVariable("project_id") final Integer projectId) {
|
||||||
|
ProjectResponse project = projectService.getProjectById(memberId, projectId);
|
||||||
|
return SuccessResponse.of(project);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "전체 프로젝트 조회", description = "모든 프로젝트를 조회합니다.")
|
||||||
|
@SwaggerApiSuccess(description = "전체 프로젝트를 성공적으로 조회합니다.")
|
||||||
|
@SwaggerApiError({ErrorCode.SERVER_ERROR})
|
||||||
|
@GetMapping("/workspaces/{workspace_id}/projects")
|
||||||
|
public BaseResponse<ProjectResponses> getProjects(
|
||||||
|
@PathVariable("workspace_id") Integer workspaceId,
|
||||||
|
@CurrentUser final Integer memberId,
|
||||||
|
@Parameter(name = "마지막 프로젝트 id", description = "마지막 프로젝트 id를 넣으면 그 아래 부터 가져옴, 넣지않으면 가장 최신", example = "1") @RequestParam(required = false) Integer lastProjectId,
|
||||||
|
@Parameter(name = "가져올 프로젝트 수", description = "가져올 프로젝트 수 default = 10", example = "20") @RequestParam(defaultValue = "10") Integer limitPage) {
|
||||||
|
List<ProjectResponse> projects = projectService.getProjectsByWorkspaceId(workspaceId, memberId, lastProjectId, limitPage);
|
||||||
|
return SuccessResponse.of(ProjectResponses.from(projects));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "프로젝트 수정", description = "프로젝트를 수정합니다.")
|
||||||
|
@SwaggerApiSuccess(description = "프로젝트를 성공적으로 수정합니다.")
|
||||||
|
@SwaggerApiError({ErrorCode.PROJECT_NOT_FOUND, ErrorCode.PARTICIPANT_UNAUTHORIZED, ErrorCode.SERVER_ERROR})
|
||||||
|
@PutMapping("/projects/{project_id}")
|
||||||
|
public BaseResponse<ProjectResponse> updateProject(
|
||||||
|
@CurrentUser final Integer memberId,
|
||||||
|
@PathVariable("project_id") final Integer projectId,
|
||||||
|
@Valid @RequestBody final ProjectRequest projectRequest) {
|
||||||
|
ProjectResponse project = projectService.updateProject(memberId, projectId, projectRequest);
|
||||||
|
return SuccessResponse.of(project);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operation(summary = "프로젝트 삭제", description = "프로젝트를 삭제합니다.")
|
||||||
|
@SwaggerApiSuccess(description = "프로젝트를 성공적으로 삭제합니다.")
|
||||||
|
@SwaggerApiError({ErrorCode.PROJECT_NOT_FOUND, ErrorCode.PARTICIPANT_UNAUTHORIZED, ErrorCode.SERVER_ERROR})
|
||||||
|
@DeleteMapping("/projects/{project_id}")
|
||||||
|
public BaseResponse<Void> deleteProject(@CurrentUser final Integer memberId,
|
||||||
|
@PathVariable("project_id") final Integer projectId) {
|
||||||
|
projectService.deleteProject(memberId, projectId);
|
||||||
|
return SuccessResponse.empty();
|
||||||
|
}
|
||||||
|
}
|
@ -1,37 +0,0 @@
|
|||||||
package com.worlabel.domain.project.entity;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import com.worlabel.global.common.BaseEntity;
|
|
||||||
import jakarta.persistence.*;
|
|
||||||
import lombok.AccessLevel;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Entity
|
|
||||||
@Table(name = "label_category")
|
|
||||||
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
|
||||||
public class LabelCategory extends BaseEntity {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 카테고리 ID
|
|
||||||
*/
|
|
||||||
@Id
|
|
||||||
@Column(name = "label_category_id", nullable = false)
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
private Integer id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 레이블 카테고리 이름
|
|
||||||
*/
|
|
||||||
@Column(name = "label_category_name",nullable = false,length = 50)
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 사용되는 프로젝트
|
|
||||||
*/
|
|
||||||
@ManyToOne(fetch = FetchType.LAZY)
|
|
||||||
@JoinColumn(name = "project_id", nullable = false)
|
|
||||||
@JsonIgnore
|
|
||||||
private Project project;
|
|
||||||
}
|
|
@ -8,9 +8,6 @@ import lombok.AccessLevel;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "project")
|
@Table(name = "project")
|
||||||
@ -40,8 +37,24 @@ public class Project extends BaseEntity {
|
|||||||
private Workspace workspace;
|
private Workspace workspace;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 사용하는 레이블 카테고리
|
* 프로젝트 유형
|
||||||
*/
|
*/
|
||||||
@OneToMany(mappedBy = "project", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
|
@Column(name = "project_type", nullable = false)
|
||||||
private List<LabelCategory> labelCategoryList = new ArrayList<>();
|
@Enumerated(EnumType.STRING)
|
||||||
|
private ProjectType projectType;
|
||||||
|
|
||||||
|
private Project(final String title, final Workspace workspace, final ProjectType projectType) {
|
||||||
|
this.title = title;
|
||||||
|
this.workspace = workspace;
|
||||||
|
this.projectType = projectType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Project of(final String title, final Workspace workspace, final ProjectType projectType) {
|
||||||
|
return new Project(title, workspace, projectType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateProject(final String title, final ProjectType projectType) {
|
||||||
|
this.title = title;
|
||||||
|
this.projectType = projectType;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package com.worlabel.domain.project.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
|
|
||||||
|
public enum ProjectType {
|
||||||
|
|
||||||
|
CLASSIFICATION("classification"),
|
||||||
|
DETECTION("detection"),
|
||||||
|
SEGMENTATION("segmentation");
|
||||||
|
|
||||||
|
private final String value;
|
||||||
|
|
||||||
|
ProjectType(String value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
|
public static ProjectType from(String value) {
|
||||||
|
for (ProjectType status : ProjectType.values()) {
|
||||||
|
if (status.getValue().equals(value)) {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonValue
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.worlabel.domain.project.entity.dto;
|
||||||
|
|
||||||
|
import com.worlabel.domain.project.entity.ProjectType;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Schema(name = "프로젝트 요청 dto", description = "프로젝트 요청 DTO")
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public class ProjectRequest {
|
||||||
|
|
||||||
|
@Schema(description = "프로젝트 제목", example = "삼성 갤럭시 s23")
|
||||||
|
@NotEmpty(message = "제목을 입력하세요.")
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@Schema(description = "프로젝트 유형", example = "classification")
|
||||||
|
@NotNull(message = "카테고리를 입력하세요.")
|
||||||
|
private ProjectType projectType;
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package com.worlabel.domain.project.entity.dto;
|
||||||
|
|
||||||
|
import com.worlabel.domain.project.entity.Project;
|
||||||
|
import com.worlabel.domain.project.entity.ProjectType;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Schema(name = "프로젝트 응답 dto", description = "프로젝트 응답 DTO")
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class ProjectResponse {
|
||||||
|
|
||||||
|
@Schema(description = "워크스페이스 ID", example = "1")
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
@Schema(description = "제목", example = "project")
|
||||||
|
private String title;
|
||||||
|
|
||||||
|
@Schema(description = "워크스페이스 id", example = "1")
|
||||||
|
private Integer projectId;
|
||||||
|
|
||||||
|
@Schema(description = "프로젝트 타입", example = "classification")
|
||||||
|
private ProjectType projectType;
|
||||||
|
|
||||||
|
@Schema(description = "생성일시", example = "2024-07-25 17:51:02")
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
|
||||||
|
@Schema(description = "수정일시", example = "2024-07-28 17:51:02")
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
|
||||||
|
public static ProjectResponse from(final Project project) {
|
||||||
|
return new ProjectResponse(
|
||||||
|
project.getId(),
|
||||||
|
project.getTitle(),
|
||||||
|
project.getWorkspace().getId(),
|
||||||
|
project.getProjectType(),
|
||||||
|
project.getCreatedAt(),
|
||||||
|
project.getUpdatedAt()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.worlabel.domain.project.entity.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Schema(name = "프로젝트 목록 응답 dto", description = "프로젝트 목록 응답 DTO")
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class ProjectResponses {
|
||||||
|
|
||||||
|
@Schema(description = "프로젝트 목록", example = "")
|
||||||
|
private List<ProjectResponse> workspaceResponses;
|
||||||
|
|
||||||
|
public static ProjectResponses from(final List<ProjectResponse> projectResponses) {
|
||||||
|
return new ProjectResponses(projectResponses);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package com.worlabel.domain.project.repository;
|
||||||
|
|
||||||
|
import com.worlabel.domain.project.entity.Project;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface ProjectRepository extends JpaRepository<Project, Integer> {
|
||||||
|
|
||||||
|
@Query(value = "SELECT p.* FROM project p " +
|
||||||
|
"JOIN participant pt ON p.project_id = pt.project_id " +
|
||||||
|
"WHERE p.workspace_id = :workspaceId " +
|
||||||
|
"AND pt.member_id = :memberId " +
|
||||||
|
"AND (:lastProjectId IS NULL OR p.project_id < :lastProjectId) " +
|
||||||
|
"ORDER BY p.project_id DESC " +
|
||||||
|
"LIMIT :pageSize",
|
||||||
|
nativeQuery = true)
|
||||||
|
List<Project> findProjectsByWorkspaceIdAndMemberIdWithPagination(
|
||||||
|
@Param("workspaceId") Integer workspaceId,
|
||||||
|
@Param("memberId") Integer memberId,
|
||||||
|
@Param("lastProjectId") Integer lastProjectId,
|
||||||
|
@Param("pageSize") Integer pageSize);
|
||||||
|
}
|
@ -0,0 +1,99 @@
|
|||||||
|
package com.worlabel.domain.project.service;
|
||||||
|
|
||||||
|
import com.worlabel.domain.member.entity.Member;
|
||||||
|
import com.worlabel.domain.member.repository.MemberRepository;
|
||||||
|
import com.worlabel.domain.participant.entity.Participant;
|
||||||
|
import com.worlabel.domain.participant.entity.PrivilegeType;
|
||||||
|
import com.worlabel.domain.participant.repository.ParticipantRepository;
|
||||||
|
import com.worlabel.domain.project.entity.Project;
|
||||||
|
import com.worlabel.domain.project.entity.dto.ProjectRequest;
|
||||||
|
import com.worlabel.domain.project.entity.dto.ProjectResponse;
|
||||||
|
import com.worlabel.domain.project.repository.ProjectRepository;
|
||||||
|
import com.worlabel.domain.workspace.entity.Workspace;
|
||||||
|
import com.worlabel.domain.workspace.repository.WorkspaceRepository;
|
||||||
|
import com.worlabel.global.exception.CustomException;
|
||||||
|
import com.worlabel.global.exception.ErrorCode;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Transactional
|
||||||
|
public class ProjectService {
|
||||||
|
|
||||||
|
private final ProjectRepository projectRepository;
|
||||||
|
private final WorkspaceRepository workspaceRepository;
|
||||||
|
private final ParticipantRepository participantRepository;
|
||||||
|
private final MemberRepository memberRepository;
|
||||||
|
|
||||||
|
public ProjectResponse createProject(final Integer memberId, final Integer workspaceId, final ProjectRequest projectRequest) {
|
||||||
|
Workspace workspace = getWorkspace(memberId, workspaceId);
|
||||||
|
Member member = getMember(memberId);
|
||||||
|
|
||||||
|
Project project = Project.of(projectRequest.getTitle(), workspace, projectRequest.getProjectType());
|
||||||
|
Participant participant = Participant.of(project, member, PrivilegeType.ADMIN);
|
||||||
|
|
||||||
|
projectRepository.save(project);
|
||||||
|
participantRepository.save(participant);
|
||||||
|
|
||||||
|
return ProjectResponse.from(project);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public ProjectResponse getProjectById(final Integer memberId, final Integer projectId) {
|
||||||
|
checkExistParticipant(memberId, projectId);
|
||||||
|
Project project = getProject(projectId);
|
||||||
|
|
||||||
|
return ProjectResponse.from(project);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<ProjectResponse> getProjectsByWorkspaceId(final Integer workspaceId, final Integer memberId, final Integer lastProjectId, final Integer pageSize) {
|
||||||
|
return projectRepository.findProjectsByWorkspaceIdAndMemberIdWithPagination(workspaceId, memberId, lastProjectId, pageSize).stream()
|
||||||
|
.map(ProjectResponse::from)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ProjectResponse updateProject(final Integer memberId, final Integer projectId, final ProjectRequest projectRequest) {
|
||||||
|
checkAdminParticipant(memberId, projectId);
|
||||||
|
Project project = getProject(projectId);
|
||||||
|
project.updateProject(projectRequest.getTitle(), projectRequest.getProjectType());
|
||||||
|
|
||||||
|
return ProjectResponse.from(project);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteProject(final Integer memberId, final Integer projectId) {
|
||||||
|
checkAdminParticipant(memberId, projectId);
|
||||||
|
projectRepository.deleteById(projectId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Workspace getWorkspace(final Integer memberId, final Integer workspaceId) {
|
||||||
|
return workspaceRepository.findByMemberIdAndId(memberId, workspaceId)
|
||||||
|
.orElseThrow(() -> new CustomException(ErrorCode.WORKSPACE_NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Member getMember(final Integer memberId) {
|
||||||
|
return memberRepository.findById(memberId)
|
||||||
|
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Project getProject(final Integer projectId) {
|
||||||
|
return projectRepository.findById(projectId)
|
||||||
|
.orElseThrow(() -> new CustomException(ErrorCode.PROJECT_NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkExistParticipant(final Integer memberId, final Integer projectId) {
|
||||||
|
if (!participantRepository.existsByMemberIdAndProjectId(memberId, projectId)) {
|
||||||
|
throw new CustomException(ErrorCode.PARTICIPANT_UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkAdminParticipant(final Integer memberId, final Integer projectId) {
|
||||||
|
if (!participantRepository.existsByProjectIdAndMemberIdAndPrivilege(projectId, memberId, PrivilegeType.ADMIN)) {
|
||||||
|
throw new CustomException(ErrorCode.PARTICIPANT_UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -20,4 +20,15 @@ public interface WorkspaceRepository extends JpaRepository<Workspace, Integer> {
|
|||||||
"LIMIT :pageSize",
|
"LIMIT :pageSize",
|
||||||
nativeQuery = true)
|
nativeQuery = true)
|
||||||
List<Workspace> findWorkspacesByMemberIdAndLastWorkspaceId(@Param("memberId") Integer memberId, @Param("lastWorkspaceId") Integer lastWorkspaceId, @Param("pageSize") Integer pageSize);
|
List<Workspace> findWorkspacesByMemberIdAndLastWorkspaceId(@Param("memberId") Integer memberId, @Param("lastWorkspaceId") Integer lastWorkspaceId, @Param("pageSize") Integer pageSize);
|
||||||
|
|
||||||
|
@Query(value = "SELECT w.* FROM workspace w " +
|
||||||
|
"JOIN workspace_participant wp ON w.workspace_id = wp.workspace_id " +
|
||||||
|
"WHERE wp.member_id = :memberId " +
|
||||||
|
"AND (:lastWorkspaceId IS NULL OR w.workspace_id < :lastWorkspaceId) " +
|
||||||
|
"ORDER BY w.workspace_id DESC " +
|
||||||
|
"LIMIT :pageSize", nativeQuery = true)
|
||||||
|
List<Workspace> findWorkspacesByMemberIdWithPagination(
|
||||||
|
@Param("memberId") Integer memberId,
|
||||||
|
@Param("lastWorkspaceId") Integer lastWorkspaceId,
|
||||||
|
@Param("pageSize") Integer pageSize);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ package com.worlabel.domain.workspace.service;
|
|||||||
|
|
||||||
import com.worlabel.domain.member.entity.Member;
|
import com.worlabel.domain.member.entity.Member;
|
||||||
import com.worlabel.domain.member.repository.MemberRepository;
|
import com.worlabel.domain.member.repository.MemberRepository;
|
||||||
|
import com.worlabel.domain.participant.entity.WorkspaceParticipant;
|
||||||
|
import com.worlabel.domain.participant.repository.WorkspaceParticipantRepository;
|
||||||
import com.worlabel.domain.workspace.entity.Workspace;
|
import com.worlabel.domain.workspace.entity.Workspace;
|
||||||
import com.worlabel.domain.workspace.entity.dto.WorkspaceRequest;
|
import com.worlabel.domain.workspace.entity.dto.WorkspaceRequest;
|
||||||
import com.worlabel.domain.workspace.entity.dto.WorkspaceResponse;
|
import com.worlabel.domain.workspace.entity.dto.WorkspaceResponse;
|
||||||
@ -21,6 +23,7 @@ public class WorkspaceService {
|
|||||||
|
|
||||||
private final WorkspaceRepository workspaceRepository;
|
private final WorkspaceRepository workspaceRepository;
|
||||||
private final MemberRepository memberRepository;
|
private final MemberRepository memberRepository;
|
||||||
|
private final WorkspaceParticipantRepository workspaceParticipantRepository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 새로운 워크스페이스 생성
|
* 새로운 워크스페이스 생성
|
||||||
@ -29,6 +32,7 @@ public class WorkspaceService {
|
|||||||
Member member = getMember(memberId);
|
Member member = getMember(memberId);
|
||||||
Workspace workspace = Workspace.of(member, workspaceRequest.getTitle(), workspaceRequest.getContent());
|
Workspace workspace = Workspace.of(member, workspaceRequest.getTitle(), workspaceRequest.getContent());
|
||||||
workspaceRepository.save(workspace);
|
workspaceRepository.save(workspace);
|
||||||
|
workspaceParticipantRepository.save(WorkspaceParticipant.of(workspace, member));
|
||||||
|
|
||||||
return WorkspaceResponse.from(workspace);
|
return WorkspaceResponse.from(workspace);
|
||||||
}
|
}
|
||||||
@ -36,16 +40,22 @@ public class WorkspaceService {
|
|||||||
/**
|
/**
|
||||||
* 특정 워크스페이스 조회
|
* 특정 워크스페이스 조회
|
||||||
*/
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
public WorkspaceResponse getWorkspaceById(final Integer memberId, final Integer workspaceId) {
|
public WorkspaceResponse getWorkspaceById(final Integer memberId, final Integer workspaceId) {
|
||||||
Workspace workspace = getWorkspace(memberId, workspaceId);
|
checkWorkspaceAuthorized(memberId, workspaceId);
|
||||||
|
|
||||||
|
Workspace workspace = workspaceRepository.findById(workspaceId)
|
||||||
|
.orElseThrow(() -> new CustomException(ErrorCode.WORKSPACE_NOT_FOUND));
|
||||||
|
|
||||||
return WorkspaceResponse.from(workspace);
|
return WorkspaceResponse.from(workspace);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 전체 워크스페이스 조회
|
* 전체 워크스페이스 조회
|
||||||
*/
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
public List<WorkspaceResponse> getAllWorkspaces(final Integer memberId, final Integer lastWorkspaceId, final Integer pageSize) {
|
public List<WorkspaceResponse> getAllWorkspaces(final Integer memberId, final Integer lastWorkspaceId, final Integer pageSize) {
|
||||||
return workspaceRepository.findWorkspacesByMemberIdAndLastWorkspaceId(memberId, lastWorkspaceId, pageSize).stream()
|
return workspaceRepository.findWorkspacesByMemberIdWithPagination(memberId, lastWorkspaceId, pageSize).stream()
|
||||||
.map(WorkspaceResponse::from)
|
.map(WorkspaceResponse::from)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
@ -54,7 +64,7 @@ public class WorkspaceService {
|
|||||||
* 워크스페이스 수정
|
* 워크스페이스 수정
|
||||||
*/
|
*/
|
||||||
public WorkspaceResponse updateWorkspace(final Integer memberId, final Integer workspaceId, final WorkspaceRequest updatedWorkspace) {
|
public WorkspaceResponse updateWorkspace(final Integer memberId, final Integer workspaceId, final WorkspaceRequest updatedWorkspace) {
|
||||||
Workspace workspace = getWorkspace(memberId, workspaceId);
|
Workspace workspace = getWorkspaceWithWriter(memberId, workspaceId);
|
||||||
workspace.updateWorkspace(updatedWorkspace.getTitle(), updatedWorkspace.getContent());
|
workspace.updateWorkspace(updatedWorkspace.getTitle(), updatedWorkspace.getContent());
|
||||||
|
|
||||||
return WorkspaceResponse.from(workspace);
|
return WorkspaceResponse.from(workspace);
|
||||||
@ -64,7 +74,7 @@ public class WorkspaceService {
|
|||||||
* 워크스페이스 삭제
|
* 워크스페이스 삭제
|
||||||
*/
|
*/
|
||||||
public void deleteWorkspace(final Integer memberId, final Integer workspaceId) {
|
public void deleteWorkspace(final Integer memberId, final Integer workspaceId) {
|
||||||
Workspace workspace = getWorkspace(memberId, workspaceId);
|
Workspace workspace = getWorkspaceWithWriter(memberId, workspaceId);
|
||||||
workspaceRepository.delete(workspace);
|
workspaceRepository.delete(workspace);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,8 +87,14 @@ public class WorkspaceService {
|
|||||||
* 작성자와 같은 워크스페이스만 가져옴
|
* 작성자와 같은 워크스페이스만 가져옴
|
||||||
* 기존 방식은 DB 2번 접근 -> 쿼리로 한번에 접근하도록 바꿈
|
* 기존 방식은 DB 2번 접근 -> 쿼리로 한번에 접근하도록 바꿈
|
||||||
*/
|
*/
|
||||||
private Workspace getWorkspace(final Integer memberId, final Integer workspaceId) {
|
private Workspace getWorkspaceWithWriter(final Integer memberId, final Integer workspaceId) {
|
||||||
return workspaceRepository.findByMemberIdAndId(memberId, workspaceId)
|
return workspaceRepository.findByMemberIdAndId(memberId, workspaceId)
|
||||||
.orElseThrow(() -> new CustomException(ErrorCode.WORKSPACE_NOT_FOUND));
|
.orElseThrow(() -> new CustomException(ErrorCode.NOT_AUTHOR));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkWorkspaceAuthorized(final Integer memberId, final Integer workspaceId) {
|
||||||
|
if (!workspaceParticipantRepository.existsByMemberIdAndWorkspaceId(memberId, workspaceId)) {
|
||||||
|
throw new CustomException(ErrorCode.NOT_AUTHOR);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,13 @@ public enum ErrorCode {
|
|||||||
// Workspace - 3000
|
// Workspace - 3000
|
||||||
NOT_AUTHOR(HttpStatus.FORBIDDEN, 3001, "작성자가 아닙니다. 이 작업을 수행할 권한이 없습니다."),
|
NOT_AUTHOR(HttpStatus.FORBIDDEN, 3001, "작성자가 아닙니다. 이 작업을 수행할 권한이 없습니다."),
|
||||||
WORKSPACE_NOT_FOUND(HttpStatus.BAD_REQUEST, 3002, "해당 워크스페이스는 존재하지 않습니다."),
|
WORKSPACE_NOT_FOUND(HttpStatus.BAD_REQUEST, 3002, "해당 워크스페이스는 존재하지 않습니다."),
|
||||||
|
|
||||||
|
// Project - 4000
|
||||||
|
PROJECT_NOT_FOUND(HttpStatus.NOT_FOUND, 4000, "프로젝트를 찾을 수 없습니다"),
|
||||||
|
|
||||||
|
// Participant - 5000
|
||||||
|
PARTICIPANT_UNAUTHORIZED(HttpStatus.UNAUTHORIZED, 5000, "해당 프로젝트에 접근 권한이 없습니다.")
|
||||||
|
;
|
||||||
;
|
;
|
||||||
|
|
||||||
private final HttpStatus status;
|
private final HttpStatus status;
|
||||||
|
Loading…
Reference in New Issue
Block a user