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

feat-be(dashboard): 특정 동아리의 대시보드(공고) 생성 API 구현 #215

Merged
merged 34 commits into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
951a912
Create draft PR for #214
github-actions[bot] Jul 31, 2024
4319876
feat(ApplyForm): 기초 Entity 및 Repository 구현
Dobby-Kim Jul 31, 2024
314654a
style: 컨벤션 미적용 사항 적용
Dobby-Kim Jul 31, 2024
662c654
Revert "style: 컨벤션 미적용 사항 적용"
Dobby-Kim Jul 31, 2024
ccad5a2
style: 컨벤션 미적용 사항 적용
Dobby-Kim Jul 31, 2024
bf6210b
feat(Question): 질문 유형 ENUM 생성 및 엔티티 적
Dobby-Kim Jul 31, 2024
f4db251
feat(Question): 엔티티 field 변경에 따른 toString 수정
Dobby-Kim Jul 31, 2024
e62e34b
chore(Question): 생성자 변경에 따른 더미 데이터 수정
Dobby-Kim Jul 31, 2024
810acf1
feat(DTO): 대시보드 생성 요청에 사용되는 DTO 추가
Dobby-Kim Jul 31, 2024
4eba6cc
feat(QuestionType): QuestionType 선택지 존재 여부 field 추가
Dobby-Kim Jul 31, 2024
e06b8a0
refactor(Choice): 선택지 순서 field 추가 및 test 수
Dobby-Kim Jul 31, 2024
823e5a7
refactor(DataLoader): ApplyForm 추가에 따른 더미 데이터 저장 코드 추가
Dobby-Kim Jul 31, 2024
06f0a5c
refactor(DashboardCreateRequest): 날짜 Json 파라미터 입력 포맷 설정 및 NotBlack를 N…
Dobby-Kim Jul 31, 2024
6ff67cc
test(ApplicantServiceTest): Question 엔티티 필드 추가에 따른 생성자 수정
Dobby-Kim Jul 31, 2024
656ab1c
feat(Choice): 객관식 질문의 선택지 저장 및 검증 기능 구
Dobby-Kim Aug 1, 2024
592294b
feat(Question): 질문 저장 및 검증 기능 구현
Dobby-Kim Aug 1, 2024
f70b6c1
feat(ApplyFormService): 지원서 저장 기능 구현
Dobby-Kim Aug 1, 2024
2b3e13d
feat(DashboardService): 대시보드 초기 프로세스 저장 기능, Dashboard 엔티디 변경에 따른 테스트 …
Dobby-Kim Aug 1, 2024
68dbd96
feat(DashboardFacade): 모집 공고 및 대시보드 생성 기능 소유한 파사드 서비스 생성
Dobby-Kim Aug 1, 2024
0871c77
Merge remote-tracking branch 'origin/be/develop' into be-214-CREATE_D…
Dobby-Kim Aug 1, 2024
3ee4f0e
refactor: conflict 해결 및 import optimized
Dobby-Kim Aug 1, 2024
63fb0ef
refactor(DTO): JsonProperty 세팅
Dobby-Kim Aug 1, 2024
37032b8
refactor(ChoiceException): 예외명 및 스타일 리팩터링
Dobby-Kim Aug 1, 2024
061b55b
refactor(ApplyForm): column명 통일성 적용
Dobby-Kim Aug 1, 2024
2206400
refactor(ApplyFormService): 접근제어자 및 메서드 순서 정렬 변경
Dobby-Kim Aug 1, 2024
0c6684a
refactor(ChoiceRepositoryTest): Choice sequence 0부터 시작하게 test에 사용된 인스…
Dobby-Kim Aug 1, 2024
71acff3
refactor(ChoiceService): Choice 저장 메서드 시그니쳐 변경
Dobby-Kim Aug 1, 2024
6943bbb
refactor(QuestionService): createAll 메서드 생성 및 변경 사항 적용
Dobby-Kim Aug 1, 2024
72fffb9
refactor(DashboardController): 미사용 field 제거
Dobby-Kim Aug 1, 2024
6f1a988
refactor(ApplyFormRepositoryTest): 변경된 field getter명 수정
Dobby-Kim Aug 1, 2024
40573b2
refactor(Question): Question의 선택지 소유 여부 판단 메서드 생성
Dobby-Kim Aug 1, 2024
597bc4a
refactor(ChoiceServiceTest): given 주석 누락 추가 및 assert절 내 사용 변수 추출
Dobby-Kim Aug 1, 2024
a3253d4
style(ApplyForm): 코드 컨벤션 적용
Dobby-Kim Aug 1, 2024
415485a
Merge branch 'be/develop' into be-214-CREATE_DASHBOARD_01
Dobby-Kim Aug 1, 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
4 changes: 2 additions & 2 deletions backend/src/main/java/com/cruru/DataLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ private void runDataLoader() {
Question essayQuestion = questionRepository.save(
new Question(SHORT_ANSWER, "좋아하는 숫자가 무엇인가요?", 1, applyForm));

Choice maleChoice = choiceRepository.save(new Choice(1L, "남", choiceQuestion));
Choice femaleChoice = choiceRepository.save(new Choice(2L, "여", choiceQuestion));
Choice maleChoice = choiceRepository.save(new Choice(1L, "남", 1, choiceQuestion));
Choice femaleChoice = choiceRepository.save(new Choice(2L, "여", 2, choiceQuestion));

List<Answer> answers = List.of(
new Answer(1L, maleChoice.getContent(), choiceQuestion, lurgi),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.cruru.applyform.controller.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotBlank;
import java.time.LocalDateTime;

public record ApplyFormCreateRequest(
@NotBlank(message = "제목은 필수 값입니다.")
String title,

@JsonProperty("posting_content")
String postingContent,

@NotBlank(message = "시작 날짜는 필수 값입니다.")
LocalDateTime startDate,

@NotBlank(message = "종료 날짜는 필수 값입니다.")
LocalDateTime dueDate
) {

}
32 changes: 24 additions & 8 deletions backend/src/main/java/com/cruru/applyform/domain/ApplyForm.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
Expand All @@ -31,10 +32,11 @@ public class ApplyForm extends BaseEntity {

private String description;

@Setter
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다른 도메인에서(Process)는 @Setter 를 직접 사용하지 않고 update필드명 으로 메서드명을 지어준 것 같습니다. 이에 대해 혹시 어떻게 생각하시나용?

  1. 하나로 통일하는 것(세터가 필요한 곳에 @Setter 또는 update필드명
  2. 혼용해서 쓰자

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

해당 Process에서는 기존에 존재하는 정보를 '변경'하는 메서드로 update가 맞지만, 생성되기 전까지 Url이 존재하지 않는 이 케이스에 관해서는 setter의 의미가 더 적절한 것 같습니다.
오히려 update의 의미를 가진 메서드를 만들면 URL이 변동 가능성이 있는 field로 받아들여질 것 같습니다.
저는 필요와 의미에 따라 혼용하는 것을 선택할 것 같습니다

private String url;

@Column(name = "start_date")
private LocalDateTime startDate;
@Column(name = "open_date")
private LocalDateTime openDate;

@Column(name = "due_date")
private LocalDateTime dueDate;
Expand All @@ -47,14 +49,28 @@ public ApplyForm(
String title,
String description,
String url,
LocalDateTime startDate,
LocalDateTime openDate,
LocalDateTime dueDate,
Dashboard dashboard
) {
this.title = title;
this.description = description;
this.url = url;
this.startDate = startDate;
this.openDate = openDate;
this.dueDate = dueDate;
this.dashboard = dashboard;
}

public ApplyForm(
String title,
String description,
LocalDateTime openDate,
LocalDateTime dueDate,
Dashboard dashboard
) {
this.title = title;
this.description = description;
this.openDate = openDate;
this.dueDate = dueDate;
this.dashboard = dashboard;
}
Expand All @@ -78,13 +94,13 @@ public int hashCode() {
@Override
public String toString() {
return "ApplyForm{" +
"id=" + id +
"dashboard=" + dashboard +
", id=" + id +
", title='" + title + '\'' +
", description='" + description + '\'' +
", url='" + url + '\'' +
", dashboard=" + dashboard +
", startDate=" + startDate +
", openDate=" + openDate +
", dueDate=" + dueDate +
", description='" + description + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
package com.cruru.applyform.service;


import com.cruru.advice.InternalServerException;
import com.cruru.answer.domain.Answer;
import com.cruru.answer.domain.repository.AnswerRepository;
import com.cruru.applicant.domain.Applicant;
import com.cruru.applicant.domain.repository.ApplicantRepository;
import com.cruru.applyform.controller.dto.AnswerCreateRequest;
import com.cruru.applyform.controller.dto.ApplyFormCreateRequest;
import com.cruru.applyform.controller.dto.ApplyFormSubmitRequest;
import com.cruru.applyform.domain.ApplyForm;
import com.cruru.applyform.domain.repository.ApplyFormRepository;
import com.cruru.applyform.exception.ApplyFormNotFoundException;
import com.cruru.applyform.exception.PersonalDataProcessingException;
import com.cruru.dashboard.domain.Dashboard;
import com.cruru.process.domain.Process;
import com.cruru.process.domain.repository.ProcessRepository;
import com.cruru.question.domain.Question;
Expand All @@ -26,13 +27,36 @@
@RequiredArgsConstructor
public class ApplyFormService {

private static final String APPLY_FORM_BASE_URL = "www.cruru.kr/applyform/";

private final ApplicantRepository applicantRepository;
private final AnswerRepository answerRepository;
private final QuestionRepository questionRepository;
private final ApplyFormRepository applyFormRepository;
private final ProcessRepository processRepository;

@Transactional
public ApplyForm create(ApplyFormCreateRequest request, Dashboard createdDashboard) {
ApplyForm applyForm = toApplyForm(request, createdDashboard);

ApplyForm savedApplyForm = applyFormRepository.save(applyForm);
Long savedId = savedApplyForm.getId();
String generatedUrl = APPLY_FORM_BASE_URL + savedId;
savedApplyForm.setUrl(generatedUrl);

return savedApplyForm;
}

private ApplyForm toApplyForm(ApplyFormCreateRequest request, Dashboard createdDashboard) {
return new ApplyForm(
request.title(),
request.postingContent(),
request.startDate(),
request.dueDate(),
createdDashboard
);
}

public void submit(ApplyFormSubmitRequest request, long applyFormId) {
validatePersonalDataCollection(request);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.cruru.choice.controller.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotBlank;

public record ChoiceCreateRequest(
@NotBlank(message = "객관식 질문의 선택지는 필수 값입니다.")
String choice,

@JsonProperty("order_index")
int orderIndex
Dobby-Kim marked this conversation as resolved.
Show resolved Hide resolved
) {

}
5 changes: 4 additions & 1 deletion backend/src/main/java/com/cruru/choice/domain/Choice.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,15 @@ public class Choice {

private String content;

private Integer sequence;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "question_id")
private Question question;

public Choice(String content, Question question) {
public Choice(String content, Integer sequence, Question question) {
this.content = content;
this.sequence = sequence;
this.question = question;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.cruru.choice.exception;

import com.cruru.advice.badrequest.BadRequestException;

public class ChoiceEmptyBadRequestException extends BadRequestException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

컨벤션에 대해선 추후 논의해봅시다.


private static final String MESSAGE = "객관식 질문에는 1개 이상의 객관식 선택지를 포함해야합니다.";

public ChoiceEmptyBadRequestException() {
super(MESSAGE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.cruru.choice.exception;

import com.cruru.advice.badrequest.BadRequestException;

public class ChoiceIllegalSaveException extends BadRequestException {

private static final String MESSAGE = "선택지를 저장할 수 없는 질문입니다.";

public ChoiceIllegalSaveException() {
super(MESSAGE);
}
}
43 changes: 43 additions & 0 deletions backend/src/main/java/com/cruru/choice/service/ChoiceService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.cruru.choice.service;

import com.cruru.choice.controller.dto.ChoiceCreateRequest;
import com.cruru.choice.domain.Choice;
import com.cruru.choice.domain.repository.ChoiceRepository;
import com.cruru.choice.exception.ChoiceEmptyBadRequestException;
import com.cruru.choice.exception.ChoiceIllegalSaveException;
import com.cruru.question.domain.Question;
import com.cruru.question.domain.repository.QuestionRepository;
import com.cruru.question.exception.QuestionNotFoundException;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class ChoiceService {

private final ChoiceRepository choiceRepository;
private final QuestionRepository questionRepository;

@Transactional
public List<Choice> createAll(List<ChoiceCreateRequest> requests, long questionId) {
Question targetQuestion = questionRepository.findById(questionId)
.orElseThrow(QuestionNotFoundException::new);

if (!targetQuestion.hasChoice()) {
throw new ChoiceIllegalSaveException();
}
if (requests.isEmpty()) {
throw new ChoiceEmptyBadRequestException();
}
return choiceRepository.saveAll(toChoices(requests, targetQuestion));
}

private List<Choice> toChoices(List<ChoiceCreateRequest> requests, Question question) {
return requests.stream()
.map(request -> new Choice(request.choice(), request.orderIndex(), question))
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.cruru.dashboard.controller;

import com.cruru.dashboard.controller.dto.DashboardCreateRequest;
import com.cruru.dashboard.service.DashboardService;
import com.cruru.dashboard.service.facade.DashboardFacade;
import jakarta.validation.Valid;
import java.net.URI;
import lombok.RequiredArgsConstructor;
Expand All @@ -17,13 +17,14 @@
@RequiredArgsConstructor
public class DashboardController {

private final DashboardService dashboardService;
private final DashboardFacade dashboardFacade;

@PostMapping
public ResponseEntity<Void> create(
@RequestParam(name = "club_id") Long clubId,
@RequestBody @Valid DashboardCreateRequest request) {
long dashboardId = dashboardService.create(clubId, request);

Dobby-Kim marked this conversation as resolved.
Show resolved Hide resolved
long dashboardId = dashboardFacade.create(clubId, request);
return ResponseEntity.created(URI.create("/v1/dashboards/" + dashboardId)).build();
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
package com.cruru.dashboard.controller.dto;

import com.cruru.question.controller.dto.QuestionCreateRequest;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonFormat.Shape;
import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import java.time.LocalDateTime;
import java.util.List;

public record DashboardCreateRequest(
@NotBlank(message = "대시보드 이름은 필수 값입니다.")
String name
@NotBlank(message = "공고 제목은 필수 값입니다.")
String title,

@JsonProperty("posting_content")
String postingContent,

List<QuestionCreateRequest> questions,

@NotNull(message = "시작 날짜는 필수 값입니다.")
@JsonFormat(shape = Shape.STRING)
Dobby-Kim marked this conversation as resolved.
Show resolved Hide resolved
@JsonProperty("start_date")
LocalDateTime startDate,

@NotNull(message = "종료 날짜는 필수 값입니다.")
@JsonFormat(shape = Shape.STRING)
@JsonProperty("due_date")
LocalDateTime dueDate
) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
import com.cruru.club.domain.Club;
import com.cruru.club.domain.repository.ClubRepository;
import com.cruru.club.exception.ClubNotFoundException;
import com.cruru.dashboard.controller.dto.DashboardCreateRequest;
import com.cruru.dashboard.domain.Dashboard;
import com.cruru.dashboard.domain.repository.DashboardRepository;
import com.cruru.process.domain.Process;
import com.cruru.process.domain.ProcessFactory;
import com.cruru.process.domain.repository.ProcessRepository;
import java.util.List;
import lombok.RequiredArgsConstructor;
Expand All @@ -18,33 +18,21 @@
@RequiredArgsConstructor
public class DashboardService {

private static final Process DEFAULT_FIRST_PROCESS = new Process(1, "지원 접수", "지원자가 지원서를 제출하는 단계", null);
private static final Process DEFAULT_LAST_PROCESS = new Process(2, "합격", "지원자가 최종적으로 합격한 단계", null);

private final DashboardRepository dashboardRepository;
private final ClubRepository clubRepository;
private final ProcessRepository processRepository;

@Transactional
public long create(long clubId, DashboardCreateRequest request) {
public Dashboard create(long clubId) {
Club club = clubRepository.findById(clubId)
.orElseThrow(ClubNotFoundException::new);
Dashboard savedDashboard = dashboardRepository.save(new Dashboard(club));

Process firstProcess = new Process(
DEFAULT_FIRST_PROCESS.getSequence(),
DEFAULT_FIRST_PROCESS.getName(),
DEFAULT_FIRST_PROCESS.getDescription(),
savedDashboard
);
Process lastProcess = new Process(
DEFAULT_LAST_PROCESS.getSequence(),
DEFAULT_LAST_PROCESS.getName(),
DEFAULT_LAST_PROCESS.getDescription(),
savedDashboard
);
Process firstProcess = ProcessFactory.createFirstOf(savedDashboard);
Process lastProcess = ProcessFactory.createLastOf(savedDashboard);

processRepository.saveAll(List.of(firstProcess, lastProcess));
return savedDashboard.getId();

return savedDashboard;
}
}
Loading