Skip to content
Open
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
@@ -0,0 +1,48 @@
package es.princip.getp.api.controller.project.command;

import es.princip.getp.api.controller.project.command.dto.request.AssignmentPeopleRequest;
import es.princip.getp.api.support.dto.ApiResponse;
import es.princip.getp.api.support.dto.ApiResponse.ApiSuccessResult;
import es.princip.getp.application.auth.service.PrincipalDetails;
import es.princip.getp.application.project.assign.dto.command.AssignmentPeopleCommand;
import es.princip.getp.application.project.assign.dto.response.AssignmentPeopleResponse;
import es.princip.getp.application.project.assign.port.in.AssignmentPeopleUseCase;
import es.princip.getp.domain.member.model.MemberId;
import es.princip.getp.domain.project.commission.model.ProjectId;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/projects")
public class AssignmentPeopleController {
private final AssignmentPeopleUseCase confirmationProjectUseCase;
private final ProjectCommandMapper projectCommandMapper;

/**
* 확정자 할당
*
* @param request 프로젝트 확정자 할당 요청
* @param principalDetails 로그인한 사용자 정보
* @param projectId 프로젝트 ID
*/
@PostMapping("/{projectId}/assignment")
@PreAuthorize("hasRole('CLIENT') and isAuthenticated()")
public ResponseEntity<ApiSuccessResult<AssignmentPeopleResponse>> assignmentPeople(
@RequestBody @Valid final AssignmentPeopleRequest request,
@AuthenticationPrincipal final PrincipalDetails principalDetails,
@PathVariable Long projectId
) {
final MemberId memberId = principalDetails.getMember().getId();
final ProjectId pid = new ProjectId(projectId);
final AssignmentPeopleCommand command = projectCommandMapper.mapToCommand(memberId, pid, request);
final Long confirmationId = confirmationProjectUseCase.assign(command).getValue();
final AssignmentPeopleResponse response = new AssignmentPeopleResponse(confirmationId);
return ApiResponse.success(HttpStatus.CREATED, response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import es.princip.getp.application.project.apply.dto.command.ApplyProjectAsIndividualCommand;
import es.princip.getp.application.project.apply.dto.command.ApplyProjectAsTeamCommand;
import es.princip.getp.application.project.apply.dto.command.ApplyProjectCommand;
import es.princip.getp.application.project.assign.dto.command.AssignmentPeopleCommand;
import es.princip.getp.application.project.commission.dto.command.CommissionProjectCommand;
import es.princip.getp.application.project.meeting.dto.command.ScheduleMeetingCommand;
import es.princip.getp.domain.common.model.AttachmentFile;
Expand All @@ -25,9 +26,9 @@
abstract class ProjectCommandMapper {

ApplyProjectCommand mapToCommand(
Member member,
ProjectId projectId,
ApplyProjectRequest request
Member member,
ProjectId projectId,
ApplyProjectRequest request
) {
if (request instanceof ApplyProjectAsIndividualRequest req) {
return mapToCommand(member, projectId, req);
Expand All @@ -42,32 +43,39 @@ ApplyProjectCommand mapToCommand(
@Mapping(source = "request.description", target = "description")
@Mapping(source = "request.attachmentFiles", target = "attachmentFiles")
protected abstract ApplyProjectAsIndividualCommand mapToCommand(
Member member,
ProjectId projectId,
ApplyProjectAsIndividualRequest request
Member member,
ProjectId projectId,
ApplyProjectAsIndividualRequest request
);

@Mapping(source = "request.expectedDuration", target = "expectedDuration")
@Mapping(source = "request.description", target = "description")
@Mapping(source = "request.attachmentFiles", target = "attachmentFiles")
@Mapping(source = "request.teammates", target = "teammates")
protected abstract ApplyProjectAsTeamCommand mapToCommand(
Member member,
ProjectId projectId,
ApplyProjectAsTeamRequest request
Member member,
ProjectId projectId,
ApplyProjectAsTeamRequest request
);

protected abstract AttachmentFile mapToAttachmentFile(URL url);

abstract CommissionProjectCommand mapToCommand(
MemberId memberId,
CommissionProjectRequest request
MemberId memberId,
CommissionProjectRequest request
);

@Mapping(source = "request.applicantId", target = "applicantId.value")
abstract ScheduleMeetingCommand mapToCommand(
MemberId memberId,
ProjectId projectId,
ScheduleMeetingRequest request
MemberId memberId,
ProjectId projectId,
ScheduleMeetingRequest request
);

@Mapping(source = "request.applicantId", target = "applicantId.value")
abstract AssignmentPeopleCommand mapToCommand(
MemberId memberId,
ProjectId projectId,
AssignmentPeopleRequest request
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package es.princip.getp.api.controller.project.command.dto.request;

public record AssignmentPeopleRequest(
Long applicantId
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package es.princip.getp.api.controller.project.command;

import com.epages.restdocs.apispec.ResourceSnippetParameters;
import com.epages.restdocs.apispec.Schema;
import es.princip.getp.api.controller.project.command.dto.request.AssignmentPeopleRequest;
import es.princip.getp.api.security.annotation.WithCustomMockUser;
import es.princip.getp.api.support.ControllerTest;
import es.princip.getp.application.project.assign.dto.command.AssignmentPeopleCommand;
import es.princip.getp.application.project.assign.port.in.AssignmentPeopleUseCase;
import es.princip.getp.domain.member.model.MemberType;
import es.princip.getp.domain.project.commission.model.ProjectId;
import es.princip.getp.domain.project.confirmation.model.AssignmentPeopleId;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.web.servlet.ResultActions;

import static com.epages.restdocs.apispec.MockMvcRestDocumentationWrapper.document;
import static es.princip.getp.api.controller.project.command.description.ConfirmProjectRequestDescription.confirmProjectRequestDescription;
import static es.princip.getp.api.controller.project.command.description.ConfirmProjectResponseDescription.confirmProjectResponseDescription;
import static es.princip.getp.api.controller.project.command.fixture.AssignmentPeopleRequestFixture.assignmentPeopleRequest;
import static es.princip.getp.api.docs.HeaderDescriptorHelper.authorizationHeaderDescription;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders;
import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

class AssignmentPeopleControllerTest extends ControllerTest {

@Autowired private AssignmentPeopleUseCase assignmentPeopleUseCase;

@Nested
class 프로젝트_확정 {
private final ProjectId projectId = new ProjectId(1L);
private final AssignmentPeopleId assignmentPeopleId = new AssignmentPeopleId(1L);

private ResultActions perform(final AssignmentPeopleRequest request) throws Exception {
return mockMvc.perform(post("/projects/{projectId}/assignment", projectId.getValue())
.header("Authorization", "Bearer ${ACCESS_TOKEN}")
.content(objectMapper.writeValueAsString(request)));
}

@Test
@WithCustomMockUser(memberType = MemberType.ROLE_CLIENT)
void 의뢰자는_지원자를_확정할_수_있다() throws Exception {
final AssignmentPeopleRequest request = assignmentPeopleRequest();
given(assignmentPeopleUseCase.assign(any(AssignmentPeopleCommand.class)))
.willReturn(assignmentPeopleId);

perform(request)
.andExpect(status().isCreated())
.andDo(document("project/assignment",
ResourceSnippetParameters.builder()
.tag("프로젝트 지원자 확정")
.description("의뢰자는 지원자를 확정할 수 있다.")
.summary("프로젝트 확정")
.requestSchema(Schema.schema("AssignmentPeopleRequest"))
.responseSchema(Schema.schema("AssignmentPeopleResponse")),
requestHeaders(authorizationHeaderDescription()),
pathParameters(parameterWithName("projectId").description("프로젝트 ID")),
requestFields(confirmProjectRequestDescription()),
responseFields(confirmProjectResponseDescription())
))
.andDo(print());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package es.princip.getp.api.controller.project.command.description;

import es.princip.getp.api.controller.project.command.dto.request.AssignmentPeopleRequest;
import org.springframework.restdocs.payload.FieldDescriptor;

import static es.princip.getp.api.docs.ConstraintDescriptor.fieldWithConstraint;

public class ConfirmProjectRequestDescription {

public static FieldDescriptor[] confirmProjectRequestDescription() {
final Class<?> clazz = AssignmentPeopleRequest.class;
return new FieldDescriptor[] {
fieldWithConstraint("applicantId", clazz).description("지원자 ID")
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package es.princip.getp.api.controller.project.command.description;

import org.springframework.restdocs.payload.FieldDescriptor;

import static es.princip.getp.api.docs.StatusFieldDescriptor.statusField;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;

public class ConfirmProjectResponseDescription {
public static FieldDescriptor[] confirmProjectResponseDescription() {
return new FieldDescriptor[] {
statusField(),
fieldWithPath("data.assignmentId").description("할당 ID"),
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package es.princip.getp.api.controller.project.command.fixture;

import es.princip.getp.api.controller.project.command.dto.request.AssignmentPeopleRequest;

public class AssignmentPeopleRequestFixture {
public static AssignmentPeopleRequest assignmentPeopleRequest() {
return new AssignmentPeopleRequest(1L);
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package es.princip.getp.application.project.apply.port.out;

import es.princip.getp.domain.people.model.PeopleId;
import es.princip.getp.domain.project.apply.model.ProjectApplication;
import es.princip.getp.domain.project.apply.model.ProjectApplicationId;
import es.princip.getp.domain.project.commission.model.ProjectId;

public interface LoadProjectApplicantPort {

ProjectApplication loadBy(ProjectApplicationId applicationId);

ProjectApplication loadBy(ProjectId projectId, PeopleId peopleId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package es.princip.getp.application.project.assign;

import es.princip.getp.application.client.port.out.LoadClientPort;
import es.princip.getp.application.people.port.out.LoadPeoplePort;
import es.princip.getp.application.project.apply.port.out.CheckProjectApplicationPort;
import es.princip.getp.application.project.apply.port.out.LoadProjectApplicantPort;
import es.princip.getp.application.project.apply.port.out.UpdateProjectApplicantPort;
import es.princip.getp.application.project.assign.dto.command.AssignmentPeopleCommand;
import es.princip.getp.application.project.commission.port.out.LoadProjectPort;
import es.princip.getp.application.project.assign.port.in.AssignmentPeopleUseCase;
import es.princip.getp.application.project.assign.port.out.SaveAssignmentPeoplePort;
import es.princip.getp.application.project.meeting.exception.NotApplicantException;
import es.princip.getp.application.project.meeting.exception.NotClientOfProjectException;
import es.princip.getp.domain.client.model.Client;
import es.princip.getp.domain.people.model.People;
import es.princip.getp.domain.people.model.PeopleId;
import es.princip.getp.domain.project.apply.model.ProjectApplication;
import es.princip.getp.domain.project.commission.model.Project;
import es.princip.getp.domain.project.commission.model.ProjectId;
import es.princip.getp.domain.project.confirmation.model.AssignmentPeople;
import es.princip.getp.domain.project.confirmation.model.AssignmentPeopleId;
import es.princip.getp.domain.project.confirmation.service.AssignPeople;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class AssignmentPeopleService implements AssignmentPeopleUseCase {

private final SaveAssignmentPeoplePort saveProjectConfirmationPort;
private final UpdateProjectApplicantPort updateProjectApplicantPort;
private final LoadProjectApplicantPort loadProjectApplicantPort;
private final LoadPeoplePort loadPeoplePort;
private final LoadClientPort loadClientPort;
private final LoadProjectPort loadProjectPort;

private final CheckProjectApplicationPort checkProjectApplicationPort;

private final AssignPeople assignPeople;

/**
* 진행자 할당
* @param command 진행자 할당 명령
* @return ProjectConfirmation ID
*/
@Transactional
public AssignmentPeopleId assign(AssignmentPeopleCommand command) {
Client client = loadClientPort.loadBy(command.memberId());
Project project = loadProjectPort.loadBy(command.projectId());

checkPeopleIsApplicant(command.applicantId(), command.projectId());
checkClientOfProject(client, project);

final People people = loadPeoplePort.loadBy(command.applicantId());
final ProjectApplication projectApplication = loadProjectApplicantPort.loadBy(command.projectId(), command.applicantId());

final AssignmentPeople projectConfirmation = assignPeople.confirm(project, people, projectApplication);
projectApplicationConfirm(projectApplication);

AssignmentPeopleId id = new AssignmentPeopleId(saveProjectConfirmationPort.save(projectConfirmation));
return id;
}

private void checkClientOfProject(final Client client, final Project project) {
if (!project.isClient(client)) {
throw new NotClientOfProjectException();
}
}

private void checkPeopleIsApplicant(final PeopleId applicantId, final ProjectId projectId) {
if (!checkProjectApplicationPort.existsBy(applicantId, projectId)) {
throw new NotApplicantException();
}
}

private void projectApplicationConfirm(ProjectApplication projectApplication) {
projectApplication.confirm();
updateProjectApplicantPort.update(projectApplication);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package es.princip.getp.application.project.assign.dto.command;

import es.princip.getp.domain.member.model.MemberId;
import es.princip.getp.domain.people.model.PeopleId;
import es.princip.getp.domain.project.commission.model.ProjectId;

public record AssignmentPeopleCommand(
MemberId memberId,
ProjectId projectId,
PeopleId applicantId
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package es.princip.getp.application.project.assign.dto.response;

public record AssignmentPeopleResponse(
Long assignmentId
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package es.princip.getp.application.project.assign.port.in;

import es.princip.getp.application.project.assign.dto.command.AssignmentPeopleCommand;
import es.princip.getp.domain.project.confirmation.model.AssignmentPeopleId;

public interface AssignmentPeopleUseCase {
AssignmentPeopleId assign(AssignmentPeopleCommand command);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package es.princip.getp.application.project.assign.port.out;

import es.princip.getp.domain.project.confirmation.model.AssignmentPeople;

public interface SaveAssignmentPeoplePort {
Long save(AssignmentPeople projectConfirmation);
}
Loading
Loading