Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GETP-157 feat: 프로젝트 지원자 미팅 신청 구현 #107

Merged
merged 15 commits into from
Aug 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
ac09b9e
GETP-157 feat : 미팅 신청 기능 구현
wlgns12370 Jul 30, 2024
4e2bc05
GETP-153 test : 미팅 신청 기능 테스트 작성
wlgns12370 Jul 30, 2024
1149a6a
Merge branch 'develop' of https://github.com/Principes-Artis-Mechanic…
wlgns12370 Jul 30, 2024
c098fd7
Merge branch 'develop' of https://github.com/Principes-Artis-Mechanic…
wlgns12370 Jul 30, 2024
b4680f4
Merge branch 'feature/GETP-157' of https://github.com/Principes-Artis…
wlgns12370 Jul 31, 2024
63b3bfb
GETP-157 chore: 도메인 레이어에 서비스 명시를 위한 도메인 서비스 어노테이션 생성 및 API명 수정
wlgns12370 Jul 31, 2024
f3a98fd
GETP-157 refactor: 미팅 신청에 대한 비즈니스 로직을 도메인 계층과 어플리케이션 계층으로 분리
wlgns12370 Aug 2, 2024
f6053df
Merge branch 'develop' of https://github.com/Principes-Artis-Mechanic…
wlgns12370 Aug 2, 2024
406e987
GETP-157 test: DDD를 적용한 미팅 신청 기능 컨트롤러 테스트 작성
wlgns12370 Aug 3, 2024
750f999
Merge branch 'develop' of https://github.com/Principes-Artis-Mechanic…
scv1702 Aug 4, 2024
e827659
GETP-157 refactor: `SimpleMailMessage` 객체 생성에 대한 책임을 `MailSender`로 이동…
scv1702 Aug 4, 2024
594ac7f
GETP-157 feat: 미팅 신청 시 미팅 일정을 한 번만 등록할 수 있도록 변경
scv1702 Aug 4, 2024
d904560
GETP-157 docs: 프로젝트 미팅 신청 API 문서 작성
scv1702 Aug 4, 2024
435c396
GETP-157 test: 프로젝트 미팅 신청 도메인 서비스 테스트 작성
scv1702 Aug 4, 2024
e5c1e16
GETP-157 fix: PhoneNumberMapper의 NPE 오류 수정
scv1702 Aug 4, 2024
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
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