Skip to content

Commit

Permalink
Merge pull request #17 from ecolink-JOIN/feature/study
Browse files Browse the repository at this point in the history
[Feat] 스터디 모집 API 추가
  • Loading branch information
wnsvy607 authored Sep 2, 2024
2 parents d3bf72c + aba50ca commit 49ac158
Show file tree
Hide file tree
Showing 24 changed files with 445 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/deploy-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ jobs:
key: ${{ secrets.EC2_KEY }}
script: |
cd ${{ env.APP_PATH }}
docker-compose down --volumes
docker compose down --rmi local
docker image prune -f
docker-compose up --build -d
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.join.core.address.repository;

import com.join.core.address.domain.Address;
import com.join.core.address.service.AddressReader;
import com.join.core.common.exception.ErrorCode;
import com.join.core.common.exception.impl.InvalidSelectionException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Component
public class AddressReaderImpl implements AddressReader {

private final AddressRepository addressRepository;

@Transactional(readOnly = true)
@Override
public Address getAddressByLocation(String province, String city) {
return addressRepository.findByProvinceAndCity(province, city)
.orElseThrow(() -> new InvalidSelectionException(ErrorCode.ADDRESS_SELECTION_REQUIRED));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.join.core.address.repository;

import com.join.core.address.domain.Address;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface AddressRepository extends JpaRepository<Address, Long> {
Optional<Address> findByProvinceAndCity(String province, String city);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.join.core.address.service;

import com.join.core.address.domain.Address;

public interface AddressReader {
Address getAddressByLocation(String province, String city);

}
24 changes: 24 additions & 0 deletions src/main/java/com/join/core/auth/repository/AvatarReaderImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.join.core.auth.repository;

import com.join.core.auth.service.AvatarReader;
import com.join.core.avatar.domain.Avatar;
import com.join.core.common.exception.ErrorCode;
import com.join.core.common.exception.impl.EntityNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Repository
public class AvatarReaderImpl implements AvatarReader {

private final AvatarRepository avatarRepository;

@Transactional(readOnly = true)
@Override
public Avatar getAvatarByToken(String avatarToken) {
return avatarRepository.findByAvatarToken(avatarToken)
.orElseThrow(() -> new EntityNotFoundException(ErrorCode.AVATAR_NOT_FOUND));
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.join.core.auth.repository;

import com.join.core.avatar.domain.Avatar;
import org.springframework.data.jpa.repository.JpaRepository;

import com.join.core.avatar.domain.Avatar;
import java.util.Optional;

public interface AvatarRepository extends JpaRepository<Avatar, Long> {
Optional<Avatar> findByAvatarToken(String avatarToken);

}
8 changes: 8 additions & 0 deletions src/main/java/com/join/core/auth/service/AvatarReader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.join.core.auth.service;

import com.join.core.avatar.domain.Avatar;

public interface AvatarReader {
Avatar getAvatarByToken(String avatarToken);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.join.core.category.repository;

import com.join.core.category.domain.Category;
import com.join.core.category.service.CategoryReader;
import com.join.core.common.exception.ErrorCode;
import com.join.core.common.exception.impl.InvalidSelectionException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Component
public class CategoryReaderImpl implements CategoryReader {

private final CategoryRepository categoryRepository;

@Transactional(readOnly = true)
@Override
public Category getCategoryByName(String categoryName) {
return categoryRepository.findByCategoryName(categoryName)
.orElseThrow(() -> new InvalidSelectionException(ErrorCode.CATEGORY_SELECTION_REQUIRED));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.join.core.category.repository;

import com.join.core.category.domain.Category;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface CategoryRepository extends JpaRepository<Category, Long> {
Optional<Category> findByCategoryName(String categoryName);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.join.core.category.service;

import com.join.core.category.domain.Category;

public interface CategoryReader {
Category getCategoryByName(String categoryName);

}
17 changes: 16 additions & 1 deletion src/main/java/com/join/core/common/constant/DayType.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
package com.join.core.common.constant;

import lombok.Getter;

@Getter
public enum DayType {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
SUN("일"),
MON("월"),
TUE("화"),
WED("수"),
THU("목"),
FRI("금"),
SAT("토");

private final String name;

DayType(String name) {
this.name = name;
}

}
13 changes: 12 additions & 1 deletion src/main/java/com/join/core/common/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,20 @@ public enum ErrorCode {
FAIL_TO_ANALYZE_FILE(HttpStatus.INTERNAL_SERVER_ERROR, "F-003", "이미지 파일을 분석하는데 실패했습니다."),
FAIL_TO_UPLOAD_FILE(HttpStatus.INTERNAL_SERVER_ERROR, "F-004", "파일 업로드에 실패했습니다."),
IMAGE_FILE_IS_NULL(HttpStatus.BAD_REQUEST, "F-005", "요청된 파일이 null입니다."),
IMAGE_EXTENSION_IS_NOT_ALLOWED(HttpStatus.BAD_REQUEST, "F-006", "요청된 파일의 확장자는 지원되지 않습니다.");
IMAGE_EXTENSION_IS_NOT_ALLOWED(HttpStatus.BAD_REQUEST, "F-006", "요청된 파일의 확장자는 지원되지 않습니다."),

/**
* 주소 선택 관련 오류
*/
ADDRESS_SELECTION_REQUIRED(HttpStatus.BAD_REQUEST, "AD-001", "주소 선택이 누락되었습니다."),

/**
* 카테고리 선택 관련 오류
*/
CATEGORY_SELECTION_REQUIRED(HttpStatus.BAD_REQUEST, "C-001", "카테고리 선택이 누락되었습니다.");

private final HttpStatus httpStatus;
private final String code;
private final String message;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.join.core.common.exception.impl;

import com.join.core.common.exception.ErrorCode;
import com.join.core.common.exception.GeneralException;

public class EntityNotFoundException extends GeneralException {

public EntityNotFoundException(ErrorCode errorCode) {
super(errorCode);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.join.core.common.exception.impl;

import com.join.core.common.exception.ErrorCode;
import com.join.core.common.exception.GeneralException;

public class InvalidSelectionException extends GeneralException {

public InvalidSelectionException(ErrorCode errorCode) {
super(errorCode);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

import java.time.LocalTime;

Expand All @@ -30,9 +31,16 @@ public class StudySchedule extends BaseTimeEntity {
@NotNull
private LocalTime endTime;

@Setter
@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "study_id", nullable = false)
private Study study;

public StudySchedule(DayType weekOfDay, LocalTime stTime, LocalTime endTime) {
this.weekOfDay = weekOfDay;
this.stTime = stTime;
this.endTime = endTime;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.join.core.schedule.dto.request;

import com.join.core.common.constant.DayType;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalTime;

@Getter
@NoArgsConstructor
public class StudyScheduleRequest {

@Schema(description = "요일", example = "MON")
@NotNull
private DayType weekOfDay;

@Schema(description = "시작 시간", example = "10:00")
@NotNull
private LocalTime stTime;

@Schema(description = "종료 시간", example = "12:00")
@NotNull
private LocalTime endTime;

}
37 changes: 37 additions & 0 deletions src/main/java/com/join/core/study/controller/StudyController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.join.core.study.controller;

import com.join.core.auth.domain.UserPrincipal;
import com.join.core.common.response.ApiResponse;
import com.join.core.study.dto.request.StudyRecruitRequest;
import com.join.core.study.service.StudyRecruitService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequiredArgsConstructor
@RestController
@RequestMapping("${api.prefix}/study")
public class StudyController {

private final StudyRecruitService studyRecruitService;

@Tag(name = "${swagger.tag.study}")
@Operation(summary = "스터디 모집 - 인증 필수",
description = "스터디 모집 - 인증 필수",
security = {@SecurityRequirement(name = "session-token")})
@PreAuthorize("hasRole('USER')")
@PostMapping("/recruit")
public ApiResponse<Void> createStudy(@AuthenticationPrincipal UserPrincipal principal,
@RequestBody StudyRecruitRequest recruitRequest) {
studyRecruitService.createStudy(principal.getAvatarToken(), recruitRequest);
return ApiResponse.ok();
}

}
47 changes: 40 additions & 7 deletions src/main/java/com/join/core/study/domain/Study.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package com.join.core.study.domain;

import com.join.core.enrollment.domain.Enrollment;
import com.join.core.address.domain.Address;
import com.join.core.avatar.domain.Avatar;
import com.join.core.category.domain.Category;
import com.join.core.rule.domain.Rule;
import com.join.core.schedule.domain.StudySchedule;
import com.join.core.study.constant.StudyEndReason;
import com.join.core.study.constant.StudyStatus;
import com.join.core.study.dto.request.StudyRecruitRequest;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand All @@ -25,6 +27,7 @@ public class Study {
private Long id;

@NotNull
@Size(min = 5, max = 25)
private String studyName;

@NotNull
Expand All @@ -45,13 +48,15 @@ public class Study {
@NotNull
private boolean isRegular;

@NotNull
private LocalDate recruitEndDate;

@NotNull
private LocalDate stDate;

@NotNull
private LocalDate endDate;

@NotNull
private LocalDate actualEndDate;

@NotNull
Expand All @@ -77,11 +82,39 @@ public class Study {
@JoinColumn(name = "category_id", nullable = false)
private Category category;

@OneToMany(mappedBy = "study", fetch = FetchType.LAZY)
private List<Enrollment> enrollments;
@NotNull
@JoinColumn(name = "writer_id")
@ManyToOne(fetch = FetchType.LAZY)
private Avatar writer;

@OneToMany(mappedBy = "study", cascade = CascadeType.ALL, orphanRemoval = true)
private List<StudySchedule> schedules;

public Study(StudyRecruitRequest recruitRequest, Avatar writer, Address address, Category category) {
this.capacity = recruitRequest.getCapacity();
this.isRegular = recruitRequest.isRegular();
this.recruitEndDate = recruitRequest.getRecruitEndDate();
this.stDate = recruitRequest.getStDate();
this.endDate = recruitRequest.getEndDate();
this.writer = writer;
this.viewCnt = 0;
this.bookmarkCnt = 0;
this.address = address;
this.category = category;
this.status = StudyStatus.RECRUITING;
this.studyName = recruitRequest.getStudyName();
this.introduction = recruitRequest.getIntroduction();
this.content = recruitRequest.getContent();
this.ruleExp = recruitRequest.getRuleExp();
this.qualificationExp = recruitRequest.getQualificationExp();
}

@OneToMany(mappedBy = "study", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Rule> rules;
public void addSchedules(List<StudySchedule> schedules) {
this.schedules = schedules;
for (StudySchedule schedule : schedules) {
schedule.setStudy(this);
}
}

public void addViewCount() {
this.viewCnt++;
Expand Down
Loading

0 comments on commit 49ac158

Please sign in to comment.