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

✨ [STMT-62] 스터디 생성 기능 구현 #111

Merged
merged 36 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
d1480d0
:recycle: [STMT-62] response 위치를 application으로 변경
05AM Apr 1, 2024
613fd27
:recycle: [STMT-62] usecase mapper 위치를 application 패키지로 변경
05AM Apr 1, 2024
299a4b2
:recycle: [STMT-62] ScheduleRepetitionType 이름 변경 -> RepetitionType
05AM Apr 1, 2024
4f62d2c
:adhesive_bandage: [STMT-62] NotExistException 내부에 NotFound ErrorCode…
05AM Apr 1, 2024
cde5878
:sparkles: [STMT-62] 스터디 생성 관련 adapter 구현
05AM Apr 1, 2024
f5286aa
:sparkles: [STMT-62] 스터디 메인 이미지 업로드 기능 구현
05AM Apr 1, 2024
f44a76f
:sparkles: [STMT-62] 스터디 생성 기능 구현
05AM Apr 1, 2024
3602f2e
:sparkles: [STMT-62] 스터디 쿼리 변수명 변경
05AM Apr 1, 2024
c387c82
:sparkles: [STMT-62] 요청 변수 검증 메시지 추가
05AM Apr 2, 2024
36bcf1e
:sparkles: [STMT-62] 이미지 필드를 nullable로 수정
05AM Apr 2, 2024
8b14b5c
:adhesive_bandage: [STMT-62] 시작일, 종료일 데이터형을 LocalDateTime에서 LocalDate…
05AM Apr 2, 2024
e79ce74
:white_check_mark: [STMT-62] 테스트 코드에서 @transactional 제거
05AM Apr 2, 2024
bd22f7d
:white_check_mark: [STMT-62] 스터디 생성 api 성공 테스트 추가
05AM Apr 2, 2024
08f7bae
:white_check_mark: [STMT-62] 스터디 생성 api 이미지 null일 경우에도 성공 테스트 케이스 추가
05AM Apr 2, 2024
aa70220
:goal_net: [STMT-146] 공통 커스텀 예외 InvalidFileException 이름 변경 -> Invalid…
05AM Apr 3, 2024
2819bc6
:recycle: [STMT-62] study를 생성하는 로직을 study 내부로 이전
05AM Apr 3, 2024
73d0d00
:sparkles: [STMT-62] 일정 반복 타입을 검증하는 로직 추가
05AM Apr 3, 2024
b8aee97
:goal_net: [STMT-62] InvalidRepetitionDatesException 부모 클래스를 BadReque…
05AM Apr 3, 2024
313f1f9
:goal_net: [STMT-62] studyTag와 repetition dates가 null일 경우 mapping 예외 처리
05AM Apr 3, 2024
540aa6f
:bug: [STMT-62] Repetition validation 로직 버그 수정: 반복 타입 WEEKLY -> DAILY
05AM Apr 3, 2024
47bf511
:test_tube: [STMT-62] StudyMeetingSchedule test 코드 작성
05AM Apr 3, 2024
9a7d7cd
:test_tube: [STMT-62] StudyCreateApi test 코드 작성
05AM Apr 3, 2024
142baa5
:white_check_mark: [STMT-62] 리스트를 문자열로 전달한 경우 대괄호가 결과에 포함되는 에러 해결
05AM Apr 3, 2024
c21e8c5
:memo: [STMT-62] 스터디 생성 API 명세서 작성
05AM Apr 3, 2024
6e9986d
:construction: [STMT-62] 리뷰 자잘한 수정사항 반영
05AM Apr 8, 2024
53ac1f7
:construction: [STMT-62] 기획 요구사항에 따라 생성된 스터디 상세 정보 반환하는 기능 삭제
05AM Apr 8, 2024
1310f2b
:construction: [STMT-62] 스터디 생성 메서드 단순화 리팩토링
05AM Apr 8, 2024
436bb90
:construction: [STMT-62] 스터디 태그가 null일 경우 빈 문자열을 반환하도록 수정
05AM Apr 8, 2024
e90b922
:goal_net: [STMT-62] 커스텀 BadRequestException 예외처리 추가: 상세 메시지 반환
05AM Apr 8, 2024
f06d523
:white_check_mark: [STMT-62] 스터디 생성 테스트 수정: POST 메서드 지정 코드 삭제
05AM Apr 8, 2024
9f53c07
:white_check_mark: [STMT-62] 스터디 생성 테스트 수정: 응답 내용 삭제
05AM Apr 8, 2024
ce93769
:white_check_mark: [STMT-62] 정기일정 테스트 수정: 정기일정 반복 테스트로 변경
05AM Apr 8, 2024
feb376f
Merge branch 'dev' into STMT-62-create_study
05AM Apr 8, 2024
6b9db22
:construction: [STMT-62] StudyFieldPersistenceAdapter 필수 생성자에 접근 제한자 …
05AM Apr 8, 2024
99d9af8
:construction: [STMT-62] StudyDomainCreateService 구현으로 Study Domain 생…
05AM Apr 8, 2024
2f299f8
:lipstick: [STMT-62] 변수명 변경 및 불필요한 코드 제거
05AM Apr 8, 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
28 changes: 28 additions & 0 deletions src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,34 @@ include::{snippets}/get-study-detail/fail/not-found/response-body.adoc[]
include::{snippets}/get-study-detail/fail/not-found/response-fields.adoc[]


=== 스터디 생성

스터디를 생성하는 API 입니다.

==== POST /api/v1/studies

===== 요청
include::{snippets}/create-study/success/http-request.adoc[]
include::{snippets}/create-study/success/request-headers.adoc[]

===== 응답 성공 (200)

include::{snippets}/create-study/success/response-body.adoc[]
include::{snippets}/create-study/success/response-fields.adoc[]

===== 응답 실패 (404)

.존재하지 않는 스터디 분야 ID를 요청한 경우

include::{snippets}/create-study/fail/study-field-not-found/response-body.adoc[]
include::{snippets}/create-study/fail/study-field-not-found/response-fields.adoc[]

.일정 반복 유형 DAILY일 때를 제외하고 반복일을 null 값으로 요청한 경우

include::{snippets}/create-study/fail/invalid-study-meeting-schedule/response-body.adoc[]
include::{snippets}/create-study/fail/invalid-study-meeting-schedule/response-fields.adoc[]


== 스터디 멤버 관리

=== 스터디 멤버 조회
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.stumeet.server.common.exception.model;

import com.stumeet.server.common.response.ErrorCode;

public class BadRequestException extends BusinessException{
public BadRequestException(ErrorCode errorCode) {
super(errorCode);
}

public BadRequestException(String message, ErrorCode errorCode) {
super(message, errorCode);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import com.stumeet.server.common.response.ErrorCode;

public class InvalidFileException extends BusinessException {
public InvalidFileException(ErrorCode errorCode) {
public class InvalidResourceException extends BusinessException {
public InvalidResourceException(ErrorCode errorCode) {
super(errorCode);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public enum ErrorCode {
*/
STUDY_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 스터디 입니다."),
MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 멤버 입니다."),
STUDY_FIELD_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 스터디 분야 입니다."),

/*
500 - INTERNAL SERVER ERROR
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

import org.springframework.web.multipart.MultipartFile;

import com.stumeet.server.common.exception.model.InvalidFileException;
import com.stumeet.server.common.response.ErrorCode;
import com.stumeet.server.file.domain.ImageContentType;
import com.stumeet.server.file.domain.exception.InvalidFileException;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ public interface FileUploadUseCase {

FileUrl uploadUserProfileImage(Long userId, MultipartFile multipartFile);

FileUrl uploadStudyMainImage(MultipartFile multipartFile);

List<FileUrl> uploadStudyActivityImage(Long studyId, List<MultipartFile> multipartFile);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
public class FileUploadService implements FileUploadUseCase {

private static final String USER_PROFILE_IMAGE_DIRECTORY_PATH = "user/%d/profile";
private static final String STUDY_MAIN_IMAGE_DIRECTORY_PATH = "study/main";
private static final String STUDY_ACTIVITY_IMAGE_DIRECTORY_PATH = "study/%d/activity";

private final FileCommandPort fileCommandPort;
Expand All @@ -27,6 +28,11 @@ public FileUrl uploadUserProfileImage(Long userId, MultipartFile multipartFile)
return fileCommandPort.uploadImageFile(multipartFile, path);
}

@Override
public FileUrl uploadStudyMainImage(MultipartFile multipartFile) {
return fileCommandPort.uploadImageFile(multipartFile, STUDY_MAIN_IMAGE_DIRECTORY_PATH);
}

@Override
public List<FileUrl> uploadStudyActivityImage(Long studyId, List<MultipartFile> multipartFiles) {
String path = String.format(STUDY_ACTIVITY_IMAGE_DIRECTORY_PATH, studyId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.stumeet.server.file.domain.exception;

import com.stumeet.server.common.exception.model.InvalidResourceException;
import com.stumeet.server.common.response.ErrorCode;

public class InvalidFileException extends InvalidResourceException {
public InvalidFileException(ErrorCode errorCode) {
super(errorCode);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.stumeet.server.study.adapter.in.web;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.stumeet.server.common.annotation.WebAdapter;
import com.stumeet.server.common.auth.model.LoginMember;
import com.stumeet.server.common.model.ApiResponse;
import com.stumeet.server.study.application.port.in.StudyCreateUseCase;
import com.stumeet.server.study.application.port.in.StudyQueryUseCase;
import com.stumeet.server.study.application.port.in.command.StudyCreateCommand;
import com.stumeet.server.study.application.port.in.response.StudyDetailResponse;

import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;

@WebAdapter
@RequestMapping("/api/v1/studies")
@RequiredArgsConstructor
public class StudyCreateApi {

private final StudyCreateUseCase studyCreateUseCase;
private final StudyQueryUseCase studyQueryUseCase;

@PostMapping(consumes = "multipart/form-data", produces = "application/json")
public ResponseEntity<ApiResponse<StudyDetailResponse>> create(
@AuthenticationPrincipal LoginMember member,
@Valid StudyCreateCommand request
) {
Long createdStudyId = studyCreateUseCase.create(request, member.getMember());
StudyDetailResponse response = studyQueryUseCase.getStudyDetailById(createdStudyId);

return new ResponseEntity<>(
ApiResponse.success(HttpStatus.CREATED.value(), "스터디 그룹 생성에 성공했습니다.", response),
HttpStatus.CREATED);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import com.stumeet.server.common.annotation.WebAdapter;
import com.stumeet.server.common.model.ApiResponse;
import com.stumeet.server.common.response.SuccessCode;
import com.stumeet.server.study.adapter.in.web.response.StudyDetailResponse;
import com.stumeet.server.study.application.port.in.response.StudyDetailResponse;
import com.stumeet.server.study.application.port.in.StudyQueryUseCase;

import lombok.RequiredArgsConstructor;
Expand All @@ -20,11 +20,11 @@ public class StudyQueryApi {

private final StudyQueryUseCase studyQueryUseCase;

@GetMapping("/{id}")
public ResponseEntity<ApiResponse<StudyDetailResponse>> getStudy(
@PathVariable(name = "id") Long id
@GetMapping("/{studyId}")
public ResponseEntity<ApiResponse<StudyDetailResponse>> getStudyDetail(
@PathVariable(name = "studyId") java.lang.Long studyId
) {
StudyDetailResponse response = studyQueryUseCase.getStudyDetailById(id);
StudyDetailResponse response = studyQueryUseCase.getStudyDetailById(studyId);
return ResponseEntity.ok(ApiResponse.success(SuccessCode.GET_SUCCESS, response));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.stumeet.server.study.adapter.out.persistance;

import org.springframework.data.jpa.repository.JpaRepository;

import com.stumeet.server.study.adapter.out.persistance.entity.StudyDomainJpaEntity;

public interface JpaStudyDomainRepository extends JpaRepository<StudyDomainJpaEntity, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.stumeet.server.study.adapter.out.persistance;

import org.springframework.data.jpa.repository.JpaRepository;

import com.stumeet.server.study.adapter.out.persistance.entity.StudyFieldJpaEntity;

public interface JpaStudyFieldRepository extends JpaRepository<StudyFieldJpaEntity, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.stumeet.server.study.adapter.out.persistance;

import org.springframework.data.jpa.repository.JpaRepository;

import com.stumeet.server.study.adapter.out.persistance.entity.StudyTagJpaEntity;

public interface JpaStudyTagRepository extends JpaRepository<StudyTagJpaEntity, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.stumeet.server.study.adapter.out.persistance;

import com.stumeet.server.common.annotation.PersistenceAdapter;
import com.stumeet.server.study.adapter.out.persistance.entity.StudyDomainJpaEntity;
import com.stumeet.server.study.adapter.out.persistance.mapper.StudyDomainPersistenceMapper;
import com.stumeet.server.study.application.port.out.StudyDomainCommandPort;
import com.stumeet.server.study.domain.StudyDomain;

import lombok.RequiredArgsConstructor;

@PersistenceAdapter
@RequiredArgsConstructor
public class StudyDomainPersistenceAdapter implements StudyDomainCommandPort {

private final JpaStudyDomainRepository studyDomainRepository;
private final StudyDomainPersistenceMapper studyDomainPersistenceMapper;

@Override
public StudyDomain save(StudyDomain studyDomain) {
StudyDomainJpaEntity entity = studyDomainRepository.save(studyDomainPersistenceMapper.toEntity(studyDomain));

return studyDomainPersistenceMapper.toDomain(entity);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.stumeet.server.study.adapter.out.persistance;

import com.stumeet.server.common.annotation.PersistenceAdapter;
import com.stumeet.server.study.adapter.out.persistance.entity.StudyFieldJpaEntity;
import com.stumeet.server.study.adapter.out.persistance.mapper.StudyFieldPersistenceMapper;
import com.stumeet.server.study.application.port.out.StudyFieldQueryPort;
import com.stumeet.server.study.domain.StudyField;
import com.stumeet.server.study.domain.exception.StudyFieldNotExistsException;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;

@PersistenceAdapter
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class StudyFieldPersistenceAdapter implements StudyFieldQueryPort {

private final JpaStudyFieldRepository studyFieldRepository;
private final StudyFieldPersistenceMapper studyFieldPersistenceMapper;

@Override
public void checkById(Long id) {
if (!studyFieldRepository.existsById(id)) {
throw new StudyFieldNotExistsException(id);
}
}

@Override
public StudyField getById(Long id) {
StudyFieldJpaEntity entity = studyFieldRepository.findById(id)
.orElseThrow(() -> new StudyFieldNotExistsException(id));

return studyFieldPersistenceMapper.toDomain(entity);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,38 @@
import com.stumeet.server.common.annotation.PersistenceAdapter;
import com.stumeet.server.study.adapter.out.persistance.entity.StudyJpaEntity;
import com.stumeet.server.study.adapter.out.persistance.mapper.StudyPersistenceMapper;
import com.stumeet.server.study.application.port.out.StudyCommandPort;
import com.stumeet.server.study.application.port.out.StudyQueryPort;
import com.stumeet.server.study.application.port.out.StudyValidationPort;
import com.stumeet.server.study.domain.Study;
import com.stumeet.server.study.domain.exception.StudyNotExistsException;

import lombok.RequiredArgsConstructor;

@PersistenceAdapter
@RequiredArgsConstructor
public class StudyPersistenceAdapter implements StudyQueryPort, StudyValidationPort {
public class StudyPersistenceAdapter implements StudyQueryPort, StudyValidationPort, StudyCommandPort {

private final StudyRepository studyRepository;
private final JpaStudyRepository studyRepository;
private final StudyPersistenceMapper studyPersistenceMapper;

@Override
public Study getById(Long id) {
StudyJpaEntity entity = studyRepository.findById(id);
StudyJpaEntity entity = studyRepository.findById(id)
.orElseThrow(() -> new StudyNotExistsException(id));

return studyPersistenceMapper.toDomain(entity);
}

@Override
public boolean existsById(Long id) {
return studyRepository.existsById(id);
}

@Override
public Study save(Study study) {
StudyJpaEntity entity = studyRepository.save(studyPersistenceMapper.toEntity(study));

return studyPersistenceMapper.toDomain(entity);
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.stumeet.server.study.adapter.out.persistance;

import java.util.List;

import com.stumeet.server.common.annotation.PersistenceAdapter;
import com.stumeet.server.study.adapter.out.persistance.entity.StudyTagJpaEntity;
import com.stumeet.server.study.adapter.out.persistance.mapper.StudyTagPersistenceMapper;
import com.stumeet.server.study.application.port.out.StudyTagCommandPort;
import com.stumeet.server.study.domain.StudyTag;

import lombok.RequiredArgsConstructor;

@PersistenceAdapter
@RequiredArgsConstructor
public class StudyTagPersistenceAdapter implements StudyTagCommandPort {

private final JpaStudyTagRepository studyTagRepository;
private final StudyTagPersistenceMapper studyTagPersistenceMapper;

@Override
public List<StudyTag> saveAll(List<StudyTag> studyTags, Long studyDomainId) {
List<StudyTagJpaEntity> entities =
studyTagRepository.saveAll(studyTagPersistenceMapper.toEntities(studyTags, studyDomainId));

return studyTagPersistenceMapper.toDomains(entities);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import org.hibernate.annotations.Comment;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
Expand Down Expand Up @@ -37,7 +36,7 @@ public class StudyDomainJpaEntity {
@Comment("분야")
private StudyFieldJpaEntity studyField;

@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@OneToMany(orphanRemoval = true)
@JoinColumn(name = "study_domain_id")
private List<StudyTagJpaEntity> studyTags;
}
Loading
Loading