diff --git a/src/main/java/es/princip/getp/api/controller/project/command/ProjectApplicationController.java b/src/main/java/es/princip/getp/api/controller/project/command/ProjectApplicationController.java index aca8d17a..9879a033 100644 --- a/src/main/java/es/princip/getp/api/controller/project/command/ProjectApplicationController.java +++ b/src/main/java/es/princip/getp/api/controller/project/command/ProjectApplicationController.java @@ -5,8 +5,8 @@ import es.princip.getp.api.controller.project.command.dto.request.ApplyProjectRequest; import es.princip.getp.api.controller.project.command.dto.response.ApplyProjectResponse; import es.princip.getp.api.security.details.PrincipalDetails; -import es.princip.getp.application.project.apply.ProjectApplicationService; import es.princip.getp.application.project.apply.command.ApplyProjectCommand; +import es.princip.getp.application.project.apply.port.in.ApplyProjectUseCase; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; @@ -20,7 +20,7 @@ @RequiredArgsConstructor public class ProjectApplicationController { - private final ProjectApplicationService projectApplicationService; + private final ApplyProjectUseCase applyProjectUseCase; private final ProjectCommandMapper projectCommandMapper; /** @@ -39,7 +39,7 @@ public ResponseEntity> applyForProject( ) { final Long memberId = principalDetails.getMember().getMemberId(); final ApplyProjectCommand command = projectCommandMapper.mapToCommand(memberId, projectId, request); - final Long applicationId = projectApplicationService.applyForProject(command); + final Long applicationId = applyProjectUseCase.apply(command); final ApplyProjectResponse response = new ApplyProjectResponse(applicationId); return ApiResponse.success(HttpStatus.CREATED, response); } diff --git a/src/main/java/es/princip/getp/application/project/apply/GetAppliedProjectService.java b/src/main/java/es/princip/getp/application/project/apply/GetAppliedProjectService.java index 9af3e23d..a7ab6002 100644 --- a/src/main/java/es/princip/getp/application/project/apply/GetAppliedProjectService.java +++ b/src/main/java/es/princip/getp/application/project/apply/GetAppliedProjectService.java @@ -12,7 +12,7 @@ @Service @RequiredArgsConstructor @Transactional(readOnly = true) -public class GetAppliedProjectService implements GetAppliedProjectQuery { +class GetAppliedProjectService implements GetAppliedProjectQuery { private final FindAppliedProjectPort findAppliedProjectPort; diff --git a/src/main/java/es/princip/getp/application/project/apply/GetProjectApplicantService.java b/src/main/java/es/princip/getp/application/project/apply/GetProjectApplicantService.java index 28a7bd82..43701497 100644 --- a/src/main/java/es/princip/getp/application/project/apply/GetProjectApplicantService.java +++ b/src/main/java/es/princip/getp/application/project/apply/GetProjectApplicantService.java @@ -18,7 +18,7 @@ @Service @RequiredArgsConstructor @Transactional(readOnly = true) -public class GetProjectApplicantService implements GetProjectApplicantQuery { +class GetProjectApplicantService implements GetProjectApplicantQuery { private final FindProjectApplicantPort findProjectApplicantPort; private final LoadClientPort loadClientPort; diff --git a/src/main/java/es/princip/getp/application/project/apply/ProjectApplicationService.java b/src/main/java/es/princip/getp/application/project/apply/ProjectApplicationService.java index b69142a2..768d0ab6 100644 --- a/src/main/java/es/princip/getp/application/project/apply/ProjectApplicationService.java +++ b/src/main/java/es/princip/getp/application/project/apply/ProjectApplicationService.java @@ -1,6 +1,10 @@ package es.princip.getp.application.project.apply; import es.princip.getp.application.project.apply.command.ApplyProjectCommand; +import es.princip.getp.application.project.apply.exception.AlreadyAppliedProjectException; +import es.princip.getp.application.project.apply.port.in.ApplyProjectUseCase; +import es.princip.getp.application.project.apply.port.out.CheckProjectApplicationPort; +import es.princip.getp.application.project.apply.port.out.SaveProjectApplicationPort; import es.princip.getp.application.project.commission.port.out.LoadProjectPort; import es.princip.getp.domain.people.command.domain.People; import es.princip.getp.domain.people.command.domain.PeopleRepository; @@ -15,10 +19,13 @@ @Service @RequiredArgsConstructor @Transactional(readOnly = true) -public class ProjectApplicationService { +class ProjectApplicationService implements ApplyProjectUseCase { private final LoadProjectPort loadProjectPort; + private final CheckProjectApplicationPort checkProjectApplicationPort; + private final SaveProjectApplicationPort saveProjectApplicationPort; private final PeopleRepository peopleRepository; + private final ProjectApplier projectApplier; /** @@ -27,18 +34,25 @@ public class ProjectApplicationService { * @param command 프로젝트 지원 명령 * @return 프로젝트 지원 ID */ + @Override @Transactional - public Long applyForProject(final ApplyProjectCommand command) { - final People people = peopleRepository.findByMemberId(command.memberId()) + public Long apply(final ApplyProjectCommand command) { + final People applicant = peopleRepository.findByMemberId(command.memberId()) .orElseThrow(NotFoundPeopleException::new); + final Long applicantId = applicant.getPeopleId(); + final Long projectId = command.projectId(); final Project project = loadProjectPort.loadBy(command.projectId()); + if (checkProjectApplicationPort.existsByApplicantIdAndProjectId(applicantId, projectId)) { + throw new AlreadyAppliedProjectException(); + } final ProjectApplication application = projectApplier.applyForProject( - people, + applicant, project, command.expectedDuration(), command.description(), command.attachmentFiles() ); + saveProjectApplicationPort.save(application); return application.getApplicationId(); } } diff --git a/src/main/java/es/princip/getp/domain/project/apply/exception/AlreadyAppliedProjectException.java b/src/main/java/es/princip/getp/application/project/apply/exception/AlreadyAppliedProjectException.java similarity index 88% rename from src/main/java/es/princip/getp/domain/project/apply/exception/AlreadyAppliedProjectException.java rename to src/main/java/es/princip/getp/application/project/apply/exception/AlreadyAppliedProjectException.java index 088daa24..ef2e7ad2 100644 --- a/src/main/java/es/princip/getp/domain/project/apply/exception/AlreadyAppliedProjectException.java +++ b/src/main/java/es/princip/getp/application/project/apply/exception/AlreadyAppliedProjectException.java @@ -1,4 +1,4 @@ -package es.princip.getp.domain.project.apply.exception; +package es.princip.getp.application.project.apply.exception; import es.princip.getp.common.exception.BusinessLogicException; import es.princip.getp.common.exception.ErrorDescription; diff --git a/src/main/java/es/princip/getp/application/project/apply/port/in/ApplyProjectUseCase.java b/src/main/java/es/princip/getp/application/project/apply/port/in/ApplyProjectUseCase.java new file mode 100644 index 00000000..1034eeb3 --- /dev/null +++ b/src/main/java/es/princip/getp/application/project/apply/port/in/ApplyProjectUseCase.java @@ -0,0 +1,8 @@ +package es.princip.getp.application.project.apply.port.in; + +import es.princip.getp.application.project.apply.command.ApplyProjectCommand; + +public interface ApplyProjectUseCase { + + Long apply(final ApplyProjectCommand command); +} diff --git a/src/main/java/es/princip/getp/application/project/apply/port/out/CheckProjectApplicationPort.java b/src/main/java/es/princip/getp/application/project/apply/port/out/CheckProjectApplicationPort.java index 22a18a0a..5465d737 100644 --- a/src/main/java/es/princip/getp/application/project/apply/port/out/CheckProjectApplicationPort.java +++ b/src/main/java/es/princip/getp/application/project/apply/port/out/CheckProjectApplicationPort.java @@ -1,5 +1,6 @@ package es.princip.getp.application.project.apply.port.out; public interface CheckProjectApplicationPort { - + + boolean existsByApplicantIdAndProjectId(Long applicantId, Long projectId); } diff --git a/src/main/java/es/princip/getp/application/project/apply/port/out/LoadProjectApplicationPort.java b/src/main/java/es/princip/getp/application/project/apply/port/out/LoadProjectApplicationPort.java deleted file mode 100644 index 980731bb..00000000 --- a/src/main/java/es/princip/getp/application/project/apply/port/out/LoadProjectApplicationPort.java +++ /dev/null @@ -1,5 +0,0 @@ -package es.princip.getp.application.project.apply.port.out; - -public interface LoadProjectApplicationPort { - -} diff --git a/src/main/java/es/princip/getp/application/project/apply/port/out/SaveProjectApplicationPort.java b/src/main/java/es/princip/getp/application/project/apply/port/out/SaveProjectApplicationPort.java index 4759eed0..4adb03ec 100644 --- a/src/main/java/es/princip/getp/application/project/apply/port/out/SaveProjectApplicationPort.java +++ b/src/main/java/es/princip/getp/application/project/apply/port/out/SaveProjectApplicationPort.java @@ -1,5 +1,8 @@ package es.princip.getp.application.project.apply.port.out; +import es.princip.getp.domain.project.apply.model.ProjectApplication; + public interface SaveProjectApplicationPort { - + + Long save(ProjectApplication projectApplication); } diff --git a/src/main/java/es/princip/getp/application/project/apply/port/out/UpdateProjectApplicationPort.java b/src/main/java/es/princip/getp/application/project/apply/port/out/UpdateProjectApplicationPort.java deleted file mode 100644 index bdc25bef..00000000 --- a/src/main/java/es/princip/getp/application/project/apply/port/out/UpdateProjectApplicationPort.java +++ /dev/null @@ -1,5 +0,0 @@ -package es.princip.getp.application.project.apply.port.out; - -public interface UpdateProjectApplicationPort { - -} diff --git a/src/main/java/es/princip/getp/application/project/meeting/ProjectMeetingService.java b/src/main/java/es/princip/getp/application/project/meeting/ProjectMeetingService.java index 4540999b..c88c61ff 100644 --- a/src/main/java/es/princip/getp/application/project/meeting/ProjectMeetingService.java +++ b/src/main/java/es/princip/getp/application/project/meeting/ProjectMeetingService.java @@ -1,5 +1,6 @@ package es.princip.getp.application.project.meeting; +import es.princip.getp.application.project.apply.port.out.CheckProjectApplicationPort; import es.princip.getp.application.project.commission.port.out.LoadProjectPort; import es.princip.getp.application.project.meeting.command.ScheduleMeetingCommand; import es.princip.getp.application.project.meeting.exception.NotApplicantException; @@ -9,7 +10,6 @@ import es.princip.getp.domain.people.command.domain.People; import es.princip.getp.domain.people.command.domain.PeopleRepository; import es.princip.getp.domain.people.exception.NotFoundPeopleException; -import es.princip.getp.domain.project.apply.ProjectApplicationRepository; import es.princip.getp.domain.project.commission.model.Project; import es.princip.getp.domain.project.meeting.model.ProjectMeeting; import lombok.RequiredArgsConstructor; @@ -24,8 +24,7 @@ public class ProjectMeetingService { private final PeopleRepository peopleRepository; private final LoadProjectPort loadProjectPort; - private final ProjectApplicationRepository applicationRepository; - + private final CheckProjectApplicationPort checkProjectApplicationPort; private final SaveProjectMeetingPort saveProjectMeetingPort; private final CheckProjectMeetingPort checkProjectMeetingPort; @@ -68,7 +67,7 @@ private void checkMemberIsClientOfProject(final Long memberId, final Long projec } private void checkPeopleIsApplicant(final Long applicantId, final Long projectId) { - if (!applicationRepository.existsByApplicantIdAndProjectId(applicantId, projectId)) { + if (!checkProjectApplicationPort.existsByApplicantIdAndProjectId(applicantId, projectId)) { throw new NotApplicantException(); } } diff --git a/src/main/java/es/princip/getp/domain/common/model/AttachmentFile.java b/src/main/java/es/princip/getp/domain/common/model/AttachmentFile.java index 7f5766ae..b0a20574 100644 --- a/src/main/java/es/princip/getp/domain/common/model/AttachmentFile.java +++ b/src/main/java/es/princip/getp/domain/common/model/AttachmentFile.java @@ -1,21 +1,22 @@ package es.princip.getp.domain.common.model; -import jakarta.persistence.Embeddable; -import jakarta.persistence.Embedded; -import lombok.*; +import es.princip.getp.domain.BaseModel; +import jakarta.validation.constraints.NotNull; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; @Getter @ToString -@Embeddable -@EqualsAndHashCode -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class AttachmentFile { +@EqualsAndHashCode(callSuper = false) +public class AttachmentFile extends BaseModel { - @Embedded - private URL url; + @NotNull private final URL url; public AttachmentFile(final URL url) { this.url = url; + + validate(); } public static AttachmentFile from(final String url) { diff --git a/src/main/java/es/princip/getp/domain/common/model/Duration.java b/src/main/java/es/princip/getp/domain/common/model/Duration.java index 1db1ddaf..772af356 100644 --- a/src/main/java/es/princip/getp/domain/common/model/Duration.java +++ b/src/main/java/es/princip/getp/domain/common/model/Duration.java @@ -1,9 +1,11 @@ package es.princip.getp.domain.common.model; +import es.princip.getp.domain.BaseModel; import es.princip.getp.domain.common.exception.StartDateIsAfterEndDateException; -import jakarta.persistence.Column; -import jakarta.persistence.Embeddable; -import lombok.*; +import jakarta.validation.constraints.NotNull; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; import java.time.Clock; import java.time.LocalDate; @@ -11,24 +13,22 @@ @Getter @ToString -@Embeddable -@EqualsAndHashCode -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class Duration { +@EqualsAndHashCode(callSuper = false) +public class Duration extends BaseModel { - @Column(name = "application_start_date") - private LocalDate startDate; - - @Column(name = "application_end_date") - private LocalDate endDate; + @NotNull private final LocalDate startDate; + @NotNull private final LocalDate endDate; public Duration(final LocalDate startDate, final LocalDate endDate) { - validate(startDate, endDate); this.startDate = startDate; this.endDate = endDate; + + validate(); } - private void validate(final LocalDate startDate, final LocalDate endDate) { + @Override + protected void validate() { + super.validate(); if (startDate.isAfter(endDate)) { throw new StartDateIsAfterEndDateException(); } diff --git a/src/main/java/es/princip/getp/domain/common/model/MeetingSchedule.java b/src/main/java/es/princip/getp/domain/common/model/MeetingSchedule.java index 5c7f815e..4ce33304 100644 --- a/src/main/java/es/princip/getp/domain/common/model/MeetingSchedule.java +++ b/src/main/java/es/princip/getp/domain/common/model/MeetingSchedule.java @@ -39,7 +39,7 @@ public static MeetingSchedule of( } @Override - public void validate() { + protected void validate() { super.validate(); if (startTime.isAfter(endTime)) { throw new StartTimeIsAfterEndTimeException(); diff --git a/src/main/java/es/princip/getp/domain/project/apply/ProjectApplicationRepository.java b/src/main/java/es/princip/getp/domain/project/apply/ProjectApplicationRepository.java deleted file mode 100644 index 3cd39827..00000000 --- a/src/main/java/es/princip/getp/domain/project/apply/ProjectApplicationRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package es.princip.getp.domain.project.apply; - -import es.princip.getp.domain.project.apply.model.ProjectApplication; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface ProjectApplicationRepository extends JpaRepository { - - boolean existsByApplicantIdAndProjectId(Long applicantId, Long projectId); -} \ No newline at end of file diff --git a/src/main/java/es/princip/getp/domain/project/apply/model/ProjectApplication.java b/src/main/java/es/princip/getp/domain/project/apply/model/ProjectApplication.java index 3a514daf..567cf178 100644 --- a/src/main/java/es/princip/getp/domain/project/apply/model/ProjectApplication.java +++ b/src/main/java/es/princip/getp/domain/project/apply/model/ProjectApplication.java @@ -1,76 +1,50 @@ package es.princip.getp.domain.project.apply.model; -import es.princip.getp.common.domain.BaseTimeEntity; +import es.princip.getp.domain.BaseEntity; import es.princip.getp.domain.common.model.AttachmentFile; import es.princip.getp.domain.common.model.Duration; -import jakarta.persistence.*; -import lombok.AccessLevel; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Builder; import lombok.Getter; -import lombok.NoArgsConstructor; -import java.util.ArrayList; +import java.time.LocalDateTime; import java.util.List; @Getter -@Entity -@Table(name = "project_application") -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class ProjectApplication extends BaseTimeEntity { +public class ProjectApplication extends BaseEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "project_application_id") - @Getter private Long applicationId; - - // 지원자의 피플 ID - @Column(name = "people_id") - private Long applicantId; - - // 지원한 프로젝트 ID - @Column(name = "project_id") - private Long projectId; - - // 희망 작업 기간 - @Embedded - @AttributeOverrides( - { - @AttributeOverride(name = "startDate", column = @Column(name = "expected_start_date")), - @AttributeOverride(name = "endDate", column = @Column(name = "expected_end_date")) - } - ) - private Duration expectedDuration; - - // 지원 상태 - @Enumerated(EnumType.STRING) - @Column(name = "status") - private ProjectApplicationStatus applicationStatus; - - // 지원 내용 - @Column(name = "description") - private String description; - - // 첨부 파일 목록 - @ElementCollection - @CollectionTable(name = "project_application_attachment_file", joinColumns = @JoinColumn(name = "project_application_id")) - private List attachmentFiles = new ArrayList<>(); + @NotNull private final Long applicantId; + @NotNull private final Long projectId; + @NotNull private Duration expectedDuration; + @NotNull private ProjectApplicationStatus applicationStatus; + @NotBlank private String description; + private final List<@NotNull AttachmentFile> attachmentFiles; @Builder public ProjectApplication( + final Long applicationId, final Long applicantId, final Long projectId, final Duration expectedDuration, final ProjectApplicationStatus applicationStatus, final String description, - final List attachmentFiles + final List attachmentFiles, + final LocalDateTime createdAt, + final LocalDateTime updatedAt ) { + super(createdAt, updatedAt); + + this.applicationId = applicationId; this.applicantId = applicantId; this.projectId = projectId; this.expectedDuration = expectedDuration; this.applicationStatus = applicationStatus; this.description = description; this.attachmentFiles = attachmentFiles; + + validate(); } public void setStatus(final ProjectApplicationStatus applicationStatus) { diff --git a/src/main/java/es/princip/getp/domain/project/apply/service/ProjectApplier.java b/src/main/java/es/princip/getp/domain/project/apply/service/ProjectApplier.java index 4b3aeba7..1cbeb595 100644 --- a/src/main/java/es/princip/getp/domain/project/apply/service/ProjectApplier.java +++ b/src/main/java/es/princip/getp/domain/project/apply/service/ProjectApplier.java @@ -5,8 +5,6 @@ import es.princip.getp.domain.common.model.Duration; import es.princip.getp.domain.people.command.domain.People; import es.princip.getp.domain.people.exception.NotRegisteredPeopleProfileException; -import es.princip.getp.domain.project.apply.ProjectApplicationRepository; -import es.princip.getp.domain.project.apply.exception.AlreadyAppliedProjectException; import es.princip.getp.domain.project.apply.exception.ClosedProjectApplicationException; import es.princip.getp.domain.project.apply.model.ProjectApplication; import es.princip.getp.domain.project.apply.model.ProjectApplicationStatus; @@ -21,7 +19,6 @@ @RequiredArgsConstructor public class ProjectApplier { - private final ProjectApplicationRepository projectApplicationRepository; private final ClockHolder clockHolder; /** @@ -41,11 +38,6 @@ public ProjectApplication applyForProject( final String description, final List attachmentFiles ) { - final Long peopleId = people.getPeopleId(); - final Long projectId = project.getProjectId(); - if (projectApplicationRepository.existsByApplicantIdAndProjectId(peopleId, projectId)) { - throw new AlreadyAppliedProjectException(); - } final Clock clock = clockHolder.getClock(); if (project.isApplicationClosed(clock)) { throw new ClosedProjectApplicationException(); @@ -53,7 +45,7 @@ public ProjectApplication applyForProject( if (!people.isProfileRegistered()) { throw new NotRegisteredPeopleProfileException(); } - final ProjectApplication application = ProjectApplication.builder() + return ProjectApplication.builder() .applicantId(people.getPeopleId()) .projectId(project.getProjectId()) .expectedDuration(expectedDuration) @@ -61,7 +53,5 @@ public ProjectApplication applyForProject( .attachmentFiles(attachmentFiles) .applicationStatus(ProjectApplicationStatus.APPLICATION_COMPLETED) .build(); - projectApplicationRepository.save(application); - return application; } } diff --git a/src/main/java/es/princip/getp/persistence/adapter/project/apply/FindAppliedProjectAdapter.java b/src/main/java/es/princip/getp/persistence/adapter/project/apply/FindAppliedProjectAdapter.java index 8e506c5c..046e067b 100644 --- a/src/main/java/es/princip/getp/persistence/adapter/project/apply/FindAppliedProjectAdapter.java +++ b/src/main/java/es/princip/getp/persistence/adapter/project/apply/FindAppliedProjectAdapter.java @@ -16,13 +16,14 @@ import java.util.Map; import static es.princip.getp.domain.people.command.domain.QPeople.people; -import static es.princip.getp.domain.project.apply.model.QProjectApplication.projectApplication; import static es.princip.getp.persistence.adapter.project.ProjectPersistenceUtil.toProjectIds; @Repository @RequiredArgsConstructor class FindAppliedProjectAdapter extends QueryDslSupport implements FindAppliedProjectPort { + private static final QProjectApplicationJpaEntity projectApplication + = QProjectApplicationJpaEntity.projectApplicationJpaEntity; private static final QProjectJpaEntity project = QProjectJpaEntity.projectJpaEntity; private final FindProjectApplicationAdapter findProjectApplicationAdapter; diff --git a/src/main/java/es/princip/getp/persistence/adapter/project/apply/FindProjectApplicantAdapter.java b/src/main/java/es/princip/getp/persistence/adapter/project/apply/FindProjectApplicantAdapter.java index 7f5c7340..542da0d3 100644 --- a/src/main/java/es/princip/getp/persistence/adapter/project/apply/FindProjectApplicantAdapter.java +++ b/src/main/java/es/princip/getp/persistence/adapter/project/apply/FindProjectApplicantAdapter.java @@ -20,7 +20,6 @@ import static es.princip.getp.domain.people.command.domain.QPeople.people; import static es.princip.getp.domain.people.query.dao.PeopleDaoUtil.orderSpecifiersFromSort; import static es.princip.getp.domain.people.query.dao.PeopleDaoUtil.toPeopleIds; -import static es.princip.getp.domain.project.apply.model.QProjectApplication.projectApplication; import static java.util.stream.Collectors.toMap; @Repository @@ -28,6 +27,8 @@ // TODO: 조회 성능 개선 필요 class FindProjectApplicantAdapter extends QueryDslSupport implements FindProjectApplicantPort { + private static final QProjectApplicationJpaEntity projectApplication + = QProjectApplicationJpaEntity.projectApplicationJpaEntity; private static final QMemberJpaEntity member = QMemberJpaEntity.memberJpaEntity; private final PeopleLikeDao peopleLikeDao; diff --git a/src/main/java/es/princip/getp/persistence/adapter/project/apply/FindProjectApplicationAdapter.java b/src/main/java/es/princip/getp/persistence/adapter/project/apply/FindProjectApplicationAdapter.java index 184c53c2..d7b9b699 100644 --- a/src/main/java/es/princip/getp/persistence/adapter/project/apply/FindProjectApplicationAdapter.java +++ b/src/main/java/es/princip/getp/persistence/adapter/project/apply/FindProjectApplicationAdapter.java @@ -8,12 +8,13 @@ import java.util.Optional; import java.util.stream.Collectors; -import static es.princip.getp.domain.project.apply.model.QProjectApplication.projectApplication; - @Repository @RequiredArgsConstructor public class FindProjectApplicationAdapter extends QueryDslSupport { + private static final QProjectApplicationJpaEntity projectApplication + = QProjectApplicationJpaEntity.projectApplicationJpaEntity; + public Map countByProjectIds(final Long... projectId) { return queryFactory.select(projectApplication.projectId, projectApplication.count()) .from(projectApplication) diff --git a/src/main/java/es/princip/getp/persistence/adapter/project/apply/ProjectApplicationJpaEntity.java b/src/main/java/es/princip/getp/persistence/adapter/project/apply/ProjectApplicationJpaEntity.java new file mode 100644 index 00000000..98a94c61 --- /dev/null +++ b/src/main/java/es/princip/getp/persistence/adapter/project/apply/ProjectApplicationJpaEntity.java @@ -0,0 +1,54 @@ +package es.princip.getp.persistence.adapter.project.apply; + +import es.princip.getp.domain.project.apply.model.ProjectApplicationStatus; +import es.princip.getp.persistence.adapter.BaseTimeJpaEntity; +import es.princip.getp.persistence.adapter.common.DurationJpaVO; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Getter +@Entity +@Builder +@AllArgsConstructor +@Table(name = "project_application") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +class ProjectApplicationJpaEntity extends BaseTimeJpaEntity { + + @Id + @Column(name = "project_application_id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long applicationId; + + @Column(name = "people_id") + private Long applicantId; + + @Column(name = "project_id") + private Long projectId; + + @Embedded + @AttributeOverrides( + { + @AttributeOverride(name = "startDate", column = @Column(name = "expected_start_date")), + @AttributeOverride(name = "endDate", column = @Column(name = "expected_end_date")) + } + ) + private DurationJpaVO expectedDuration; + + @Enumerated(EnumType.STRING) + @Column(name = "status") + private ProjectApplicationStatus applicationStatus; + + @Column(name = "description") + private String description; + + @Builder.Default + @ElementCollection + @CollectionTable( + name = "project_application_attachment_file", + joinColumns = @JoinColumn(name = "project_application_id") + ) + private List attachmentFiles = new ArrayList<>(); +} diff --git a/src/main/java/es/princip/getp/persistence/adapter/project/apply/ProjectApplicationJpaRepository.java b/src/main/java/es/princip/getp/persistence/adapter/project/apply/ProjectApplicationJpaRepository.java new file mode 100644 index 00000000..7a6799c4 --- /dev/null +++ b/src/main/java/es/princip/getp/persistence/adapter/project/apply/ProjectApplicationJpaRepository.java @@ -0,0 +1,10 @@ +package es.princip.getp.persistence.adapter.project.apply; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +interface ProjectApplicationJpaRepository extends JpaRepository { + + boolean existsByApplicantIdAndProjectId(Long applicantId, Long projectId); +} \ No newline at end of file diff --git a/src/main/java/es/princip/getp/persistence/adapter/project/apply/ProjectApplicationPersistenceAdapter.java b/src/main/java/es/princip/getp/persistence/adapter/project/apply/ProjectApplicationPersistenceAdapter.java new file mode 100644 index 00000000..e9ab060c --- /dev/null +++ b/src/main/java/es/princip/getp/persistence/adapter/project/apply/ProjectApplicationPersistenceAdapter.java @@ -0,0 +1,28 @@ +package es.princip.getp.persistence.adapter.project.apply; + +import es.princip.getp.application.project.apply.port.out.CheckProjectApplicationPort; +import es.princip.getp.application.project.apply.port.out.SaveProjectApplicationPort; +import es.princip.getp.domain.project.apply.model.ProjectApplication; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +@Repository +@RequiredArgsConstructor +class ProjectApplicationPersistenceAdapter implements + SaveProjectApplicationPort, + CheckProjectApplicationPort { + + private final ProjectApplicationPersistenceMapper mapper; + private final ProjectApplicationJpaRepository repository; + + @Override + public Long save(final ProjectApplication application) { + final ProjectApplicationJpaEntity jpaEntity = mapper.mapToJpa(application); + return repository.save(jpaEntity).getApplicationId(); + } + + @Override + public boolean existsByApplicantIdAndProjectId(final Long applicantId, final Long projectId) { + return repository.existsByApplicantIdAndProjectId(applicantId, projectId); + } +} diff --git a/src/main/java/es/princip/getp/persistence/adapter/project/apply/ProjectApplicationPersistenceMapper.java b/src/main/java/es/princip/getp/persistence/adapter/project/apply/ProjectApplicationPersistenceMapper.java new file mode 100644 index 00000000..99c17ba2 --- /dev/null +++ b/src/main/java/es/princip/getp/persistence/adapter/project/apply/ProjectApplicationPersistenceMapper.java @@ -0,0 +1,13 @@ +package es.princip.getp.persistence.adapter.project.apply; + +import es.princip.getp.domain.project.apply.model.ProjectApplication; +import es.princip.getp.persistence.adapter.common.mapper.AttachmentFilePersistenceMapper; +import org.mapstruct.Mapper; + +@Mapper(componentModel = "spring", uses = {AttachmentFilePersistenceMapper.class}) +interface ProjectApplicationPersistenceMapper { + + ProjectApplication mapToDomain(ProjectApplicationJpaEntity applicationJpaEntity); + + ProjectApplicationJpaEntity mapToJpa(ProjectApplication application); +} diff --git a/src/test/java/es/princip/getp/api/controller/project/command/ProjectApplicationControllerTest.java b/src/test/java/es/princip/getp/api/controller/project/command/ProjectApplicationControllerTest.java index e27c72df..fb30d72e 100644 --- a/src/test/java/es/princip/getp/api/controller/project/command/ProjectApplicationControllerTest.java +++ b/src/test/java/es/princip/getp/api/controller/project/command/ProjectApplicationControllerTest.java @@ -6,8 +6,8 @@ import es.princip.getp.api.controller.project.command.dto.request.ApplyProjectRequest; import es.princip.getp.api.docs.PayloadDocumentationHelper; import es.princip.getp.api.security.annotation.WithCustomMockUser; -import es.princip.getp.application.project.apply.ProjectApplicationService; import es.princip.getp.application.project.apply.command.ApplyProjectCommand; +import es.princip.getp.application.project.apply.port.in.ApplyProjectUseCase; import es.princip.getp.domain.member.model.MemberType; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -34,7 +34,7 @@ class ProjectApplicationControllerTest extends ControllerTest { private ProjectCommandMapper projectCommandMapper; @Autowired - private ProjectApplicationService projectApplicationService; + private ApplyProjectUseCase applyProjectUseCase; @BeforeEach void setUp() { @@ -54,7 +54,7 @@ class ApplyForProject { @WithCustomMockUser(memberType = MemberType.ROLE_PEOPLE) @DisplayName("피플은 프로젝트에 지원할 수 있다.") void applyForProject() throws Exception { - given(projectApplicationService.applyForProject(any(ApplyProjectCommand.class))) + given(applyProjectUseCase.apply(any(ApplyProjectCommand.class))) .willReturn(applicationId); mockMvc.perform(post("/projects/{projectId}/applications", projectId) diff --git a/src/test/java/es/princip/getp/application/project/meeting/ProjectMeetingServiceTest.java b/src/test/java/es/princip/getp/application/project/meeting/ProjectMeetingServiceTest.java index d8a1287e..ebc95460 100644 --- a/src/test/java/es/princip/getp/application/project/meeting/ProjectMeetingServiceTest.java +++ b/src/test/java/es/princip/getp/application/project/meeting/ProjectMeetingServiceTest.java @@ -1,5 +1,6 @@ package es.princip.getp.application.project.meeting; +import es.princip.getp.application.project.apply.port.out.CheckProjectApplicationPort; import es.princip.getp.application.project.commission.port.out.LoadProjectPort; import es.princip.getp.application.project.meeting.command.ScheduleMeetingCommand; import es.princip.getp.application.project.meeting.exception.NotApplicantException; @@ -9,7 +10,6 @@ import es.princip.getp.domain.people.command.domain.People; import es.princip.getp.domain.people.command.domain.PeopleRepository; import es.princip.getp.domain.people.command.domain.PeopleType; -import es.princip.getp.domain.project.apply.ProjectApplicationRepository; import es.princip.getp.domain.project.commission.model.Project; import es.princip.getp.domain.project.commission.model.ProjectStatus; import es.princip.getp.domain.project.meeting.model.ProjectMeeting; @@ -36,7 +36,7 @@ @ExtendWith(MockitoExtension.class) class ProjectMeetingServiceTest { - @Mock private ProjectApplicationRepository applicationRepository; + @Mock private CheckProjectApplicationPort checkProjectApplicationPort; @Mock private PeopleRepository peopleRepository; @Mock private LoadProjectPort loadProjectPort; @@ -65,7 +65,7 @@ void setUp() { void 의뢰자는_프로젝트_지원자에게_미팅을_신청할_수_있다() { given(checkProjectMeetingPort.existsApplicantByProjectIdAndMemberId(projectId, memberId)) .willReturn(true); - given(applicationRepository.existsByApplicantIdAndProjectId(applicantId, projectId)) + given(checkProjectApplicationPort.existsByApplicantIdAndProjectId(applicantId, projectId)) .willReturn(true); given(saveProjectMeetingPort.save(any(ProjectMeeting.class))) .willReturn(meetingId); @@ -95,7 +95,7 @@ void setUp() { void 의뢰자는_프로젝트_지원자가_아닌_피플에게_미팅을_신청할_수_없다() { given(checkProjectMeetingPort.existsApplicantByProjectIdAndMemberId(projectId, memberId)) .willReturn(true); - given(applicationRepository.existsByApplicantIdAndProjectId(applicantId, projectId)) + given(checkProjectApplicationPort.existsByApplicantIdAndProjectId(applicantId, projectId)) .willReturn(false); final ScheduleMeetingCommand command = scheduleMeetingCommand(memberId, projectId, applicantId); diff --git a/src/test/java/es/princip/getp/fixture/project/ProjectApplicationFixture.java b/src/test/java/es/princip/getp/fixture/project/ProjectApplicationFixture.java index 813d0ec1..c1135c0b 100644 --- a/src/test/java/es/princip/getp/fixture/project/ProjectApplicationFixture.java +++ b/src/test/java/es/princip/getp/fixture/project/ProjectApplicationFixture.java @@ -2,12 +2,16 @@ import es.princip.getp.domain.common.model.Duration; import es.princip.getp.domain.project.apply.model.ProjectApplication; -import es.princip.getp.domain.project.apply.model.ProjectApplicationStatus; import java.time.LocalDate; +import static es.princip.getp.domain.project.apply.model.ProjectApplicationStatus.APPLICATION_ACCEPTED; +import static es.princip.getp.fixture.project.AttachmentFileFixture.attachmentFiles; + public class ProjectApplicationFixture { + public static final String DESCRIPTION = "프로젝트 지원 내용"; + public static ProjectApplication projectApplication(final Long peopleId, final Long projectId) { return ProjectApplication.builder() .applicantId(peopleId) @@ -16,8 +20,9 @@ public static ProjectApplication projectApplication(final Long peopleId, final L LocalDate.of(2024, 7, 1), LocalDate.of(2024, 7, 31) )) - .applicationStatus(ProjectApplicationStatus.APPLICATION_ACCEPTED) - .description("프로젝트 지원 내용") + .applicationStatus(APPLICATION_ACCEPTED) + .description(DESCRIPTION) + .attachmentFiles(attachmentFiles()) .build(); } } diff --git a/src/test/java/es/princip/getp/fixture/project/ProjectFixture.java b/src/test/java/es/princip/getp/fixture/project/ProjectFixture.java index 6789843e..fe2d2bea 100644 --- a/src/test/java/es/princip/getp/fixture/project/ProjectFixture.java +++ b/src/test/java/es/princip/getp/fixture/project/ProjectFixture.java @@ -1,15 +1,15 @@ package es.princip.getp.fixture.project; -import es.princip.getp.domain.common.model.AttachmentFile; import es.princip.getp.domain.common.model.Duration; -import es.princip.getp.domain.common.model.Hashtag; import es.princip.getp.domain.project.commission.model.MeetingType; import es.princip.getp.domain.project.commission.model.Project; import es.princip.getp.domain.project.commission.model.ProjectCategory; import es.princip.getp.domain.project.commission.model.ProjectStatus; import java.time.LocalDate; -import java.util.List; + +import static es.princip.getp.fixture.common.HashtagFixture.hashtags; +import static es.princip.getp.fixture.project.AttachmentFileFixture.attachmentFiles; public class ProjectFixture { @@ -17,56 +17,33 @@ public class ProjectFixture { public static final String TITLE = "프로젝트 제목"; public static final String DESCRIPTION = "프로젝트 설명"; + private static final Project.ProjectBuilder builder = Project.builder() + .category(ProjectCategory.BACKEND) + .attachmentFiles(attachmentFiles()) + .payment(PAYMENT) + .title(TITLE) + .description(DESCRIPTION) + .meetingType(MeetingType.IN_PERSON) + .estimatedDuration(Duration.of( + LocalDate.of(2024, 8, 1), + LocalDate.of(2024, 8, 31) + )) + .hashtags(hashtags()); + public static Project project(final Long clientId, final ProjectStatus status) { - return Project.builder() - .clientId(clientId) - .category(ProjectCategory.BACKEND) - .attachmentFiles(List.of( - AttachmentFile.from("https://example.com/attachment1"), - AttachmentFile.from("https://example.com/attachment2") - )) - .payment(PAYMENT) + return builder.clientId(clientId) .status(status) - .title(TITLE) - .description(DESCRIPTION) - .meetingType(MeetingType.IN_PERSON) .applicationDuration(Duration.of( LocalDate.of(2024, 7, 1), LocalDate.of(2024, 7, 31) )) - .estimatedDuration(Duration.of( - LocalDate.of(2024, 8, 1), - LocalDate.of(2024, 8, 31) - )) - .hashtags(List.of( - Hashtag.from("Java"), - Hashtag.from("Spring Boot") - )) .build(); } public static Project project(final Long clientId, final ProjectStatus status, final Duration applicationDuration) { - return Project.builder() - .clientId(clientId) - .category(ProjectCategory.BACKEND) - .attachmentFiles(List.of( - AttachmentFile.from("https://example.com/attachment1"), - AttachmentFile.from("https://example.com/attachment2") - )) - .payment(PAYMENT) + return builder.clientId(clientId) .status(status) - .title(TITLE) - .description(DESCRIPTION) - .meetingType(MeetingType.IN_PERSON) .applicationDuration(applicationDuration) - .estimatedDuration(Duration.of( - LocalDate.of(2024, 8, 1), - LocalDate.of(2024, 8, 31) - )) - .hashtags(List.of( - Hashtag.from("Java"), - Hashtag.from("Spring Boot") - )) .build(); } } diff --git a/src/test/java/es/princip/getp/persistence/adapter/project/apply/FindAppliedProjectAdapterTest.java b/src/test/java/es/princip/getp/persistence/adapter/project/apply/FindAppliedProjectAdapterTest.java index 1b68214e..2a116495 100644 --- a/src/test/java/es/princip/getp/persistence/adapter/project/apply/FindAppliedProjectAdapterTest.java +++ b/src/test/java/es/princip/getp/persistence/adapter/project/apply/FindAppliedProjectAdapterTest.java @@ -27,7 +27,8 @@ public class FindAppliedProjectAdapterTest extends PersistenceAdapterTest { @PersistenceContext private EntityManager entityManager; @Autowired private FindAppliedProjectAdapter adapter; - @Autowired private ProjectPersistenceMapper mapper; + @Autowired private ProjectApplicationPersistenceMapper applicationMapper; + @Autowired private ProjectPersistenceMapper projectMapper; private List dataLoaders; @@ -35,8 +36,8 @@ public class FindAppliedProjectAdapterTest extends PersistenceAdapterTest { void setUp() { dataLoaders = List.of( new PeopleDataLoader(entityManager), - new ProjectDataLoader(mapper, entityManager), - new ProjectApplicationDataLoader(entityManager) + new ProjectDataLoader(projectMapper, entityManager), + new ProjectApplicationDataLoader(applicationMapper, entityManager) ); dataLoaders.forEach(dataLoader -> dataLoader.load(TEST_SIZE)); } @@ -53,9 +54,9 @@ void teardown() { void 지원한_프로젝트_목록을_조회한다() { final Page response = adapter.findBy(1L, pageable); - assertThat(response.getContent()).allSatisfy(content -> { - assertThat(content).usingRecursiveComparison().isNotNull(); - }); + assertThat(response.getContent()).allSatisfy(content -> + assertThat(content).usingRecursiveComparison().isNotNull() + ); assertThat(response.getNumberOfElements()).isGreaterThan(0); } } diff --git a/src/test/java/es/princip/getp/persistence/adapter/project/apply/FindProjectApplicantAdapterTest.java b/src/test/java/es/princip/getp/persistence/adapter/project/apply/FindProjectApplicantAdapterTest.java index b27b855a..e55a0df4 100644 --- a/src/test/java/es/princip/getp/persistence/adapter/project/apply/FindProjectApplicantAdapterTest.java +++ b/src/test/java/es/princip/getp/persistence/adapter/project/apply/FindProjectApplicantAdapterTest.java @@ -27,7 +27,8 @@ public class FindProjectApplicantAdapterTest extends PersistenceAdapterTest { @PersistenceContext private EntityManager entityManager; @Autowired private FindProjectApplicantAdapter adapter; - @Autowired private ProjectPersistenceMapper mapper; + @Autowired private ProjectApplicationPersistenceMapper applicationMapper; + @Autowired private ProjectPersistenceMapper projectMapper; private List dataLoaders; @@ -35,8 +36,8 @@ public class FindProjectApplicantAdapterTest extends PersistenceAdapterTest { void setUp() { dataLoaders = List.of( new PeopleDataLoader(entityManager), - new ProjectDataLoader(mapper, entityManager), - new ProjectApplicationDataLoader(entityManager) + new ProjectDataLoader(projectMapper, entityManager), + new ProjectApplicationDataLoader(applicationMapper, entityManager) ); dataLoaders.forEach(dataLoader -> dataLoader.load(TEST_SIZE)); } diff --git a/src/test/java/es/princip/getp/persistence/adapter/project/apply/FindProjectApplicationAdapterTest.java b/src/test/java/es/princip/getp/persistence/adapter/project/apply/FindProjectApplicationAdapterTest.java index 4d103ccb..255960b2 100644 --- a/src/test/java/es/princip/getp/persistence/adapter/project/apply/FindProjectApplicationAdapterTest.java +++ b/src/test/java/es/princip/getp/persistence/adapter/project/apply/FindProjectApplicationAdapterTest.java @@ -20,6 +20,7 @@ class FindProjectApplicationAdapterTest extends PersistenceAdapterTest { private static final int TEST_SIZE = 10; @PersistenceContext private EntityManager entityManager; + @Autowired private ProjectApplicationPersistenceMapper applicationMapper; @Autowired private FindProjectApplicationAdapter adapter; private List dataLoaders; @@ -27,7 +28,7 @@ class FindProjectApplicationAdapterTest extends PersistenceAdapterTest { @BeforeEach void setUp() { dataLoaders = List.of( - new ProjectApplicationDataLoader(entityManager) + new ProjectApplicationDataLoader(applicationMapper, entityManager) ); dataLoaders.forEach(dataLoader -> dataLoader.load(TEST_SIZE)); } diff --git a/src/test/java/es/princip/getp/persistence/adapter/project/apply/ProjectApplicationDataLoader.java b/src/test/java/es/princip/getp/persistence/adapter/project/apply/ProjectApplicationDataLoader.java index 631da579..738de6b4 100644 --- a/src/test/java/es/princip/getp/persistence/adapter/project/apply/ProjectApplicationDataLoader.java +++ b/src/test/java/es/princip/getp/persistence/adapter/project/apply/ProjectApplicationDataLoader.java @@ -1,7 +1,6 @@ package es.princip.getp.persistence.adapter.project.apply; import es.princip.getp.common.util.DataLoader; -import es.princip.getp.domain.project.apply.model.ProjectApplication; import jakarta.persistence.EntityManager; import lombok.RequiredArgsConstructor; @@ -14,14 +13,15 @@ @RequiredArgsConstructor public class ProjectApplicationDataLoader implements DataLoader { + private final ProjectApplicationPersistenceMapper mapper; private final EntityManager entityManager; @Override public void load(final int size) { - final List projectApplicationList = new ArrayList<>(); + final List projectApplicationList = new ArrayList<>(); LongStream.rangeClosed(1, size).forEach(projectId -> LongStream.rangeClosed(1, size).forEach(peopleId -> - projectApplicationList.add(projectApplication(peopleId, projectId)) + projectApplicationList.add(mapper.mapToJpa(projectApplication(peopleId, projectId))) ) ); projectApplicationList.forEach(entityManager::persist); @@ -29,7 +29,7 @@ public void load(final int size) { @Override public void teardown() { - entityManager.createQuery("DELETE FROM ProjectApplication").executeUpdate(); + entityManager.createQuery("DELETE FROM ProjectApplicationJpaEntity").executeUpdate(); entityManager.createNativeQuery("ALTER TABLE project_application AUTO_INCREMENT = 1") .executeUpdate(); }