Skip to content

Commit

Permalink
✨ [STMT-179] 스터디 활동 생성 기능 구현 (#121)
Browse files Browse the repository at this point in the history
* ✨ [STMT-179] 스터디 활동 기능 구현

* 🚚 [STMT-179] 잘못된 멤버 ID 스텁 객체 위치 수정

* ♻️ [STMT-179] 위치 정보는 모임에서만 받으므로 NULLABLE하게 변경

* ✅ [STMT-179] 스터디 활동 생성 API 테스트 코드 작성 및 문서화 진행

* ♻️ [STMT-179] 활동 생성시 응답으로 공통 응답 객체 추가

* ♻️ [STMT-179] 활동 생성 멤버 ID에 대한 변수명 수정

* ♻️ [STMT-179] 활동 도메인을 생성하기 위한 객체의 이름을 ~Source로 변경 및 패키지 위치 변경

* ♻️ [STMT-179] 활동 생성시 스터디 생성에 대한 검증 추가

* ♻️ [STMT-179] 활동 생성시 Enum이 아닌 String으로 반환하도록 변경
  • Loading branch information
zxcv9203 authored May 15, 2024
1 parent 9857e3b commit 402d36c
Show file tree
Hide file tree
Showing 36 changed files with 906 additions and 30 deletions.
38 changes: 37 additions & 1 deletion src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -421,4 +421,40 @@ include::{snippets}/presigned-url-generate/fail/invalid-file-name/response-field

.파일의 확장자가 유효하지 않은 경우
include::{snippets}/presigned-url-generate/fail/invalid-file-extension/response-body.adoc[]
include::{snippets}/presigned-url-generate/fail/invalid-file-extension/response-fields.adoc[]
include::{snippets}/presigned-url-generate/fail/invalid-file-extension/response-fields.adoc[]

== 스터디 활동 관리

=== 스터디 활동 생성

스터디 활동을 생성하는 API입니다.

==== POST /api/v1/studies/{studyId}/activities

===== 요청
include::{snippets}/create-activity/success/http-request.adoc[]
include::{snippets}/create-activity/success/path-parameters.adoc[]
include::{snippets}/create-activity/success/request-headers.adoc[]
include::{snippets}/create-activity/success/request-fields.adoc[]

===== 응답 성공 (201)
.응답 없음
include::{snippets}/create-activity/success/response-body.adoc[]

===== 응답 실패 (400)
.활동 생성 요청 값이 유효하지 않은 경우
include::{snippets}/create-activity/fail/invalid-request/response-body.adoc[]
include::{snippets}/create-activity/fail/invalid-request/response-fields.adoc[]

.존재하지 않는 활동 카테고리로 요청한 경우
include::{snippets}/create-activity/fail/not-exists-category/response-body.adoc[]
include::{snippets}/create-activity/fail/not-exists-category/response-fields.adoc[]
===== 응답 실패 (403)
.스터디의 관리자가 아닌 경우
include::{snippets}/create-activity/fail/not-admin/response-body.adoc[]
include::{snippets}/create-activity/fail/not-admin/response-fields.adoc[]

====== 응답 실패 (404)
.존재하지 않는 스터디 ID를 요청한 경우
include::{snippets}/create-activity/fail/not-exists-study/response-body.adoc[]
include::{snippets}/create-activity/fail/not-exists-study/response-fields.adoc[]
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.stumeet.server.activity.adapter.in;

import com.stumeet.server.activity.application.port.in.ActivityCreateUseCase;
import com.stumeet.server.activity.application.port.in.command.ActivityCreateCommand;
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.common.response.SuccessCode;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;

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

private final ActivityCreateUseCase activityCreateUseCase;

@PostMapping("/studies/{studyId}/activities")
public ResponseEntity<ApiResponse<Void>> create(
@PathVariable Long studyId,
@AuthenticationPrincipal LoginMember loginMember,
@RequestBody @Valid ActivityCreateCommand command
) {
activityCreateUseCase.create(studyId, command, loginMember.getMember().getId());

return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success(SuccessCode.ACTIVITY_CREATE_SUCCESS));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
@RequiredArgsConstructor
public class ActivityImagePersistenceMapper {
Expand All @@ -15,15 +17,21 @@ public ActivityImage toDomain(ActivityImageJpaEntity entity) {
return ActivityImage.builder()
.id(entity.getId())
.activity(activityPersistenceMapper.toDomain(entity.getActivity()))
.url(entity.getImage())
.url(entity.getUrl())
.build();
}

public ActivityImageJpaEntity toEntity(ActivityImage domain) {
return ActivityImageJpaEntity.builder()
.id(domain.getId())
.activity(activityPersistenceMapper.toEntity(domain.getActivity()))
.image(domain.getUrl())
.url(domain.getUrl())
.build();
}

public List<ActivityImageJpaEntity> toEntities(List<ActivityImage> images) {
return images.stream()
.map(this::toEntity)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
@RequiredArgsConstructor
public class ActivityParticipantPersistenceMapper {
Expand Down Expand Up @@ -37,4 +39,10 @@ public ActivityParticipantJpaEntity toEntity(ActivityParticipant domain) {
.status(domain.getStatus())
.build();
}

public List<ActivityParticipantJpaEntity> toEntities(List<ActivityParticipant> participants) {
return participants.stream()
.map(this::toEntity)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,18 @@
import com.stumeet.server.activity.adapter.out.model.ActivityJpaEntity;
import com.stumeet.server.activity.adapter.out.model.ActivityLinkedStudyJpaEntity;
import com.stumeet.server.activity.adapter.out.model.ActivityMemberJpaEntity;
import com.stumeet.server.activity.application.port.in.command.ActivityConstructCommand;
import com.stumeet.server.activity.application.service.model.ActivityCreateSource;
import com.stumeet.server.activity.domain.model.Activity;
import com.stumeet.server.activity.domain.model.ActivityCategory;
import com.stumeet.server.activity.domain.model.Meet;
import org.springframework.stereotype.Component;

@Component
public class ActivityPersistenceMapper {

public Activity toDomain(ActivityJpaEntity entity) {
ActivityConstructCommand request = ActivityConstructCommand.builder()
ActivityCreateSource request = ActivityCreateSource.builder()
.id(entity.getId())
.author(ActivityConstructCommand.ActivityMemberConstructCommand.builder()
.author(ActivityCreateSource.ActivityMemberCreateSource.builder()
.id(entity.getAuthor().getId())
.name(entity.getAuthor().getName())
.image(entity.getAuthor().getImage())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public class ActivityImageJpaEntity extends BaseTimeEntity {
@Comment("활동")
private ActivityJpaEntity activity;

@Column(name = "url", nullable = false, length = 500)
@Column(name = "image", nullable = false, length = 500)
@Comment("이미지 url")
private String image;
private String url;
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class ActivityJpaEntity extends BaseTimeEntity {
private ActivityLinkedStudyJpaEntity study;

@OneToOne
@JoinColumn(name = "member_id")
@JoinColumn(name = "author_id")
@Comment("활동을 생성한 멤버")
private ActivityMemberJpaEntity author;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.stumeet.server.activity.adapter.out.persistence;

import com.stumeet.server.activity.adapter.out.mapper.ActivityImagePersistenceMapper;
import com.stumeet.server.activity.adapter.out.model.ActivityImageJpaEntity;
import com.stumeet.server.activity.application.port.out.ActivityImageCreatePort;
import com.stumeet.server.activity.domain.model.ActivityImage;
import com.stumeet.server.common.annotation.PersistenceAdapter;
import lombok.RequiredArgsConstructor;

import java.util.List;

@PersistenceAdapter
@RequiredArgsConstructor
public class ActivityImagePersistenceAdapter implements ActivityImageCreatePort {

private final JpaActivityImageRepository jpaActivityImageRepository;
private final ActivityImagePersistenceMapper activityImagePersistenceMapper;

@Override
public void create(List<ActivityImage> images) {
List<ActivityImageJpaEntity> entities = activityImagePersistenceMapper.toEntities(images);

jpaActivityImageRepository.saveAll(entities);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.stumeet.server.activity.adapter.out.persistence;

import com.stumeet.server.activity.adapter.out.mapper.ActivityParticipantPersistenceMapper;
import com.stumeet.server.activity.adapter.out.model.ActivityParticipantJpaEntity;
import com.stumeet.server.activity.application.port.out.ActivityParticipantCreatePort;
import com.stumeet.server.activity.domain.model.ActivityParticipant;
import com.stumeet.server.common.annotation.PersistenceAdapter;
import lombok.RequiredArgsConstructor;

import java.util.List;

@PersistenceAdapter
@RequiredArgsConstructor
public class ActivityParticipantPersistenceAdapter implements ActivityParticipantCreatePort {

private final JpaActivityParticipantRepository jpaActivityParticipantRepository;
private final ActivityParticipantPersistenceMapper activityParticipantPersistenceMapper;


@Override
public void create(List<ActivityParticipant> participants) {
List<ActivityParticipantJpaEntity> entities = activityParticipantPersistenceMapper.toEntities(participants);

jpaActivityParticipantRepository.saveAll(entities);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.stumeet.server.activity.adapter.out.persistence;

import com.stumeet.server.activity.adapter.out.mapper.ActivityPersistenceMapper;
import com.stumeet.server.activity.adapter.out.model.ActivityJpaEntity;
import com.stumeet.server.activity.application.port.out.ActivityCreatePort;
import com.stumeet.server.activity.domain.model.Activity;
import com.stumeet.server.common.annotation.PersistenceAdapter;
import lombok.RequiredArgsConstructor;

@PersistenceAdapter
@RequiredArgsConstructor
public class ActivityPersistenceAdapter implements ActivityCreatePort {

private final JpaActivityRepository jpaActivityRepository;
private final ActivityPersistenceMapper activityPersistenceMapper;

@Override
public Activity create(Activity activity) {
ActivityJpaEntity entity = activityPersistenceMapper.toEntity(activity);

return activityPersistenceMapper.toDomain(jpaActivityRepository.save(entity));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.stumeet.server.activity.adapter.out.persistence;

import com.stumeet.server.activity.adapter.out.model.ActivityImageJpaEntity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface JpaActivityImageRepository extends JpaRepository<ActivityImageJpaEntity, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.stumeet.server.activity.adapter.out.persistence;

import com.stumeet.server.activity.adapter.out.model.ActivityParticipantJpaEntity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface JpaActivityParticipantRepository extends JpaRepository<ActivityParticipantJpaEntity, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.stumeet.server.activity.adapter.out.persistence;

import com.stumeet.server.activity.adapter.out.model.ActivityJpaEntity;
import org.springframework.data.jpa.repository.JpaRepository;

public interface JpaActivityRepository extends JpaRepository<ActivityJpaEntity, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.stumeet.server.activity.application.port.in;

import com.stumeet.server.activity.application.port.in.command.ActivityCreateCommand;

public interface ActivityCreateUseCase {
void create(Long studyId, ActivityCreateCommand command, Long memberId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package com.stumeet.server.activity.application.port.in.command;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Builder;
import org.springframework.format.annotation.DateTimeFormat;

import java.time.LocalDateTime;
import java.util.List;

@Builder
public record ActivityCreateCommand(
@NotBlank(message = "활동 카테고리를 입력해주세요")
String category,

@NotBlank(message = "활동 제목을 입력해주세요")
@Size(max = 100, message = "활동 제목은 100자 이하여야 합니다")
String title,

@NotBlank(message = "활동 내용을 입력해주세요")
@Size(max = 500, message = "활동 내용은 500자 이하여야 합니다")
String content,

@NotNull(message = "이미지 리스트를 전달해주세요")
@Size(max = 5, message = "이미지는 5개 이하로 등록할 수 있습니다")
List<String> images,

boolean isNotice,

@DateTimeFormat(pattern = "yyyy-MM-dd''HH:mm:ss")
LocalDateTime startDate,

@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
LocalDateTime endDate,

String location,

@NotNull(message = "참여 멤버 리스트를 전달해주세요")
@Size(min = 1, message = "참여 멤버는 1명 이상이어야 합니다")
List<Long> participants

) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.stumeet.server.activity.application.port.in.mapper;

import com.stumeet.server.activity.domain.model.Activity;
import com.stumeet.server.activity.domain.model.ActivityImage;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class ActivityImageUseCaseMapper {

public ActivityImage toDomain(String image, Activity activity) {
return ActivityImage.builder()
.activity(activity)
.url(image)
.build();

}
public List<ActivityImage> toDomains(List<String> images, Activity activity) {
return images.stream()
.map(image -> toDomain(image, activity))
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.stumeet.server.activity.application.port.in.mapper;

import com.stumeet.server.activity.domain.model.Activity;
import com.stumeet.server.activity.domain.model.ActivityMember;
import com.stumeet.server.activity.domain.model.ActivityParticipant;
import org.springframework.stereotype.Component;

import java.util.List;

@Component
public class ActivityParticipantUseCaseMapper {

public ActivityParticipant toDomain(Long participant, Activity activity) {
return ActivityParticipant.builder()
.activity(activity)
.member(ActivityMember.builder()
.id(participant)
.build())
.activity(activity)
.status(activity.getCategory().getDefaultStatus())
.build();
}

public List<ActivityParticipant> toDomains(List<Long> participants, Activity activity) {
return participants.stream()
.map(participant -> toDomain(participant, activity))
.toList();
}
}
Loading

0 comments on commit 402d36c

Please sign in to comment.