Skip to content

Commit

Permalink
GETP-157 feat: 프로젝트 지원자 미팅 신청 구현 (#107)
Browse files Browse the repository at this point in the history
* GETP-157 feat : 미팅 신청 기능 구현

* GETP-153 test : 미팅 신청 기능 테스트 작성

* GETP-157 chore: 도메인 레이어에 서비스 명시를 위한 도메인 서비스 어노테이션 생성 및 API명 수정

* GETP-157 refactor: 미팅 신청에 대한 비즈니스 로직을 도메인 계층과 어플리케이션 계층으로 분리

* GETP-157 test: DDD를 적용한 미팅 신청 기능 컨트롤러 테스트 작성

* GETP-157 refactor: `SimpleMailMessage` 객체 생성에 대한 책임을 `MailSender`로 이동 및 추상화 수준에 맞는 예외를 던지도록 변경

* GETP-157 feat: 미팅 신청 시 미팅 일정을 한 번만 등록할 수 있도록 변경

* GETP-157 docs: 프로젝트 미팅 신청 API 문서 작성

* GETP-157 test: 프로젝트 미팅 신청 도메인 서비스 테스트 작성

* GETP-157 fix: PhoneNumberMapper의 NPE 오류 수정

---------

Co-authored-by: wlgns12370 <[email protected]>
  • Loading branch information
scv1702 and wlgns12370 committed Sep 12, 2024
1 parent f15325a commit 45b6ca6
Show file tree
Hide file tree
Showing 63 changed files with 978 additions and 165 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,7 @@ local.sh

# Static Resources
src/main/resources/static/*
storage/
storage/

# Yml File
local-docker-compose.yml
8 changes: 7 additions & 1 deletion src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -157,4 +157,10 @@ include::{docdir}/project/commission-project.adoc[]

의뢰자는 자신이 의뢰한 프로젝트 목록을 조회할 수 있습니다.

include::{docdir}/project/get-my-projects.adoc[]
include::{docdir}/project/get-my-projects.adoc[]

== 프로젝트 미팅 신청

의뢰자는 프로젝트 지원자에게 미팅을 신청할 수 있습니다.

include::{docdir}/project/schedule-meeting.adoc[]
1 change: 1 addition & 0 deletions src/docs/asciidoc/project/schedule-meeting.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
operation::/schedule-meeting/schedule-meeting[snippets="http-request,request-headers,request-fields,http-response,response-fields-data"]
4 changes: 3 additions & 1 deletion src/main/java/es/princip/getp/GetpServerApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableAsync
@EnableScheduling
@SpringBootApplication
public class GetpServerApplication {
public static void main(String[] args) {
SpringApplication.run(GetpServerApplication.class, args);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
package es.princip.getp.domain.auth.application;

import es.princip.getp.domain.member.command.domain.model.MemberRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

@Slf4j
@Service
public class AccessTokenService extends JwtTokenService {

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
@RequiredArgsConstructor
public class SignUpService {

private final EmailVerificationService emailVerificationService;
private final VerificationService emailVerificationService;
private final MemberRepository memberRepository;
private final MemberService memberService;
private final PasswordEncoder passwordEncoder;
Expand All @@ -28,7 +28,7 @@ public void sendEmailVerificationCodeForSignUp(Email email) {
if (memberRepository.existsByEmail(email)) {
throw new BusinessLogicException(SignUpErrorCode.DUPLICATED_EMAIL);
}
emailVerificationService.sendEmailVerificationCode(email);
emailVerificationService.sendVerificationCode(email);
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@

import es.princip.getp.domain.member.command.domain.model.Email;

public interface EmailService {
void sendEmail(Email email, String title, String text);
public interface VerificationCodeSender {
void send(Email email, String verificationCode);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,21 @@

@Service
@Transactional(readOnly = true)
public class EmailVerificationService {
public class VerificationService {

private final EmailService emailService;
private final VerificationCodeSender verificationSender;
private final EmailVerificationRepository emailVerificationRepository;

private final Long EXPIRATION_TIME;
private final Integer VERIFICATION_CODE_LENGTH;

public EmailVerificationService(
EmailService emailService,
public VerificationService(
VerificationCodeSender verificationSender,
EmailVerificationRepository emailVerificationRepository,
@Value("${spring.verification-code.expire-time}") Long EXPIRATION_TIME,
@Value("${spring.verification-code.length}") Integer VERIFICATION_CODE_LENGTH
) {
this.emailService = emailService;
this.verificationSender = verificationSender;
this.emailVerificationRepository = emailVerificationRepository;
this.EXPIRATION_TIME = EXPIRATION_TIME;
this.VERIFICATION_CODE_LENGTH = VERIFICATION_CODE_LENGTH;
Expand All @@ -39,12 +39,12 @@ public Optional<EmailVerification> getByEmail(Email email) {
}

@Transactional
public void sendEmailVerificationCode(Email email) {
public void sendVerificationCode(Email email) {
if (getByEmail(email).isPresent()) {
emailVerificationRepository.deleteById(email.getValue());
}
String verificationCode = RandomUtil.generateRandomCode(VERIFICATION_CODE_LENGTH);
emailService.sendEmail(email, "GET-P 인증 번호", verificationCode);
verificationSender.send(email, verificationCode);
EmailVerification emailVerification = new EmailVerification(email.getValue(), verificationCode, EXPIRATION_TIME);
emailVerificationRepository.save(emailVerification);
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package es.princip.getp.domain.auth.exception;

import es.princip.getp.infra.exception.ErrorDescription;
import es.princip.getp.infra.exception.ExternalApiErrorException;

public class FailedVerificationCodeSendingException extends ExternalApiErrorException {

private static final String code = "FAILED_VERIFICATION_CODE_SENDING";
private static final String message = "인증 코드 전송에 실패했습니다. 잠시 후 다시 시도해주세요.";

public FailedVerificationCodeSendingException() {
super(ErrorDescription.of(code, message));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package es.princip.getp.domain.auth.infra;

import es.princip.getp.domain.auth.application.VerificationCodeSender;
import es.princip.getp.domain.auth.exception.FailedVerificationCodeSendingException;
import es.princip.getp.domain.member.command.domain.model.Email;
import es.princip.getp.infra.mail.MailSender;
import es.princip.getp.infra.mail.command.SendMailCommand;
import lombok.RequiredArgsConstructor;
import org.springframework.mail.MailException;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class MailVerificationCodeSender implements VerificationCodeSender {

private final MailSender mailSender;

private static String title() {
return "[GET-P] 회원가입 인증 코드";
}

private static String text(final String verificationCode) {
return String.format(
"""
안녕하십니까 GET-P입니다.
인증 코드 번호는 %s 입니다.
감사합니다.
""",
verificationCode
);
}

@Override
public void send(final Email email, final String verificationCode) {
final SendMailCommand command = SendMailCommand.of(
email,
title(),
text(verificationCode)
);
try {
mailSender.send(command);
} catch (MailException exception) {
throw new FailedVerificationCodeSendingException();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package es.princip.getp.domain.common.annotation;

import org.springframework.stereotype.Component;

@Component
public @interface DomainService {

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,16 @@
import es.princip.getp.domain.common.exception.StartDateIsAfterEndDateException;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.MappedSuperclass;
import lombok.*;

import java.time.Clock;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

@Getter
@ToString
@Embeddable
@MappedSuperclass
@EqualsAndHashCode
@ToString
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Duration {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
import jakarta.validation.constraints.NotBlank;
import lombok.*;

@Embeddable
@Getter
@EqualsAndHashCode
@ToString
@Embeddable
@EqualsAndHashCode
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Hashtag {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package es.princip.getp.domain.common.domain;

import es.princip.getp.domain.common.exception.StartTimeIsAfterEndTimeException;
import jakarta.persistence.Column;
import jakarta.persistence.Embeddable;
import jakarta.persistence.MappedSuperclass;
import jakarta.validation.constraints.NotNull;
import lombok.AccessLevel;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;

@Getter
@Embeddable
@MappedSuperclass
@EqualsAndHashCode
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class MeetingSchedule {

@Column(name = "date")
@NotNull
private LocalDate date;

@Column(name = "start_time")
@NotNull
private LocalTime startTime;

@Column(name = "end_time")
@NotNull
private LocalTime endTime;

public MeetingSchedule(
final LocalDate date,
final LocalTime startTime,
final LocalTime endTime
) {
validate(startTime, endTime);
this.date = date;
this.startTime = startTime;
this.endTime = endTime;
}

public static MeetingSchedule of(
final LocalDate date,
final LocalTime startTime,
final LocalTime endTime
) {
return new MeetingSchedule(date, startTime, endTime);
}

private void validate(final LocalTime startTime, final LocalTime endTime) {
if (startTime.isAfter(endTime)) {
throw new StartTimeIsAfterEndTimeException();
}
}

@Override
public String toString() {
final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy년 MM월 dd일");
final DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm");

return String.format(
"%s %s ~ %s",
date.format(dateFormatter),
startTime.format(timeFormatter),
endTime.format(timeFormatter)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package es.princip.getp.domain.common.exception;

import es.princip.getp.infra.exception.BusinessLogicException;

public class StartTimeIsAfterEndTimeException extends BusinessLogicException{

public StartTimeIsAfterEndTimeException() {
super("시작 시간이 종료 시간보다 늦을 수 없습니다.");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package es.princip.getp.domain.common.infra;

import es.princip.getp.domain.member.command.domain.model.PhoneNumber;
import org.mapstruct.Mapper;

@Mapper(componentModel = "spring")
public interface PhoneNumberMapper {

PhoneNumber mapToPhoneNumber(String value);
}
Loading

0 comments on commit 45b6ca6

Please sign in to comment.