Skip to content

Commit

Permalink
✨ [STMT-261] 활동 상세 목록 조회 API 구현 (#128)
Browse files Browse the repository at this point in the history
* 🩹 [STMT-261] 활동 조회 응답에 location 추가

* ✅ [STMT-261] 테스트에 location 추가

* ✨ [STMT-261] 활동 상세 리스트 조회 API 메서드, DTO 정의

* ✨ [STMT-261] 활동 상세 리스트 조회 usecase 구현

* ✨ [STMT-261] 활동 상세 리스트 조회 persistence adapter 구현

* 🔧 [STMT-261] application-local 설정 파일에 로깅 debug 레벨 설정

* ✅ [STMT-261] 테스트용 활동 stub 데이터 추가

* 🩹 [STMT-261] 응답에 활동 유형 추가

* ✨ [STMT-261] 스터디 id 검증 로직 추가

* ✅ [STMT-261] 활동 상세 목록 조회 API 테스트 케이스 작성

* 📝 [STMT-261] API 명세서 작성

* 📝 [STMT-261] 가입 스터디 목록 조회 API: 명세서 누락된 부분 추가
  • Loading branch information
05AM authored Jun 10, 2024
1 parent 9019d00 commit 8c973d1
Show file tree
Hide file tree
Showing 31 changed files with 751 additions and 281 deletions.
27 changes: 27 additions & 0 deletions src/docs/asciidoc/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,7 @@ 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[]
Expand All @@ -480,6 +481,32 @@ include::{snippets}/create-activity/fail/not-admin/response-fields.adoc[]
include::{snippets}/create-activity/fail/not-exists-study/response-body.adoc[]
include::{snippets}/create-activity/fail/not-exists-study/response-fields.adoc[]


=== 스터디 활동 상세 목록 조회

스터디 활동 상세 목록을 조회하는 API입니다.

==== GET /api/v1/studies/activities/detail
===== 요청
include::{snippets}/get-activity-details-by-condition/success/http-request.adoc[]
include::{snippets}/get-activity-details-by-condition/success/request-headers.adoc[]
include::{snippets}/get-activity-details-by-condition/success/query-parameters.adoc[]

===== 응답 성공 (200)
include::{snippets}/get-activity-details-by-condition/success/response-body.adoc[]
include::{snippets}/get-activity-details-by-condition/success/response-fields.adoc[]

===== 응답 실패 (400)
.유효하지 않은 활동 유형을 요청한 경우
include::{snippets}/get-activity-details-by-condition/fail/activity-category-invalid/response-body.adoc[]
include::{snippets}/get-activity-details-by-condition/fail/activity-category-invalid/response-fields.adoc[]

===== 응답 실패 (404)
.존재하지 않는 스터디 ID를 요청한 경우
include::{snippets}/get-activity-details-by-condition/fail/study-not-found/response-body.adoc[]
include::{snippets}/get-activity-details-by-condition/fail/study-not-found/response-fields.adoc[]


=== 스터디 활동 단일 조회

스터디 활동을 단일 조회하는 API입니다.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,34 +1,56 @@
package com.stumeet.server.activity.adapter.in;

import com.stumeet.server.activity.adapter.in.response.ActivityDetailResponse;
import com.stumeet.server.activity.adapter.in.response.ActivityListDetailedPageResponses;
import com.stumeet.server.activity.application.port.in.ActivityQueryUseCase;
import com.stumeet.server.activity.application.port.in.query.ActivityListDetailedQuery;
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 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.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

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

private final ActivityQueryUseCase activityQueryUseCase;

@GetMapping("/studies/{studyId}/activities/{activityId}")
public ResponseEntity<ApiResponse<ActivityDetailResponse>> getById(
@PathVariable Long studyId,
@PathVariable Long activityId,
@AuthenticationPrincipal LoginMember member
) {
ActivityDetailResponse response = activityQueryUseCase.getById(studyId, activityId, member.getId());
return ResponseEntity.status(HttpStatus.OK)
.body(ApiResponse.success(SuccessCode.GET_SUCCESS, response));
}
private final ActivityQueryUseCase activityQueryUseCase;

@GetMapping("/studies/{studyId}/activities/{activityId}")
public ResponseEntity<ApiResponse<ActivityDetailResponse>> getById(
@PathVariable Long studyId,
@PathVariable Long activityId,
@AuthenticationPrincipal LoginMember member
) {
ActivityDetailResponse response = activityQueryUseCase.getById(studyId, activityId, member.getId());
return ResponseEntity.status(HttpStatus.OK)
.body(ApiResponse.success(SuccessCode.GET_SUCCESS, response));
}

@GetMapping("/studies/activities/detail")
public ResponseEntity<ApiResponse<ActivityListDetailedPageResponses>> getDetailsByCondition(
@AuthenticationPrincipal LoginMember member,
@RequestParam Integer size,
@RequestParam Integer page,
@RequestParam(required = false) Boolean isNotice,
@RequestParam(required = false) Long studyId,
@RequestParam(required = false) String category
) {
ActivityListDetailedQuery query =
ActivityListDetailedQuery.of(size, page, isNotice, member.getId(), studyId, category);
ActivityListDetailedPageResponses response = activityQueryUseCase.getDetails(query);

return ResponseEntity.status(HttpStatus.OK)
.body(ApiResponse.success(SuccessCode.GET_SUCCESS, response));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

public record ActivityDetailResponse(
Long id,
String category,
String title,
String content,
List<ActivityImageResponse> imageUrl,
Expand All @@ -16,6 +17,7 @@ public record ActivityDetailResponse(
String status,
LocalDateTime startDate,
LocalDateTime endDate,
String location,
LocalDateTime createdAt

) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.stumeet.server.activity.adapter.in.response;

import java.time.LocalDateTime;

import lombok.Builder;

@Builder
public record ActivityListDetailedPageResponse(
Long id,
String category,
String title,
String content,
LocalDateTime startDate,
LocalDateTime endDate,
String location,
ActivityParticipantSimpleResponse author,
LocalDateTime createdAt
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.stumeet.server.activity.adapter.in.response;

import java.util.List;

import lombok.Builder;

@Builder
public record ActivityListDetailedPageResponses(
List<ActivityListDetailedPageResponse> items,
PageInfoResponse pageInfo
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.stumeet.server.activity.adapter.in.response;

import lombok.Builder;

@Builder
public record PageInfoResponse(
int totalPages,
long totalElements,
int currentPage,
int pageSize
) {
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
package com.stumeet.server.activity.adapter.out.mapper;

import java.util.List;

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.service.model.ActivityCreateSource;
import com.stumeet.server.activity.domain.model.Activity;
import com.stumeet.server.activity.domain.model.Meet;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.stereotype.Component;

@Component
Expand All @@ -26,13 +31,22 @@ public Activity toDomain(ActivityJpaEntity entity) {
.isNotice(entity.isNotice())
.startDate(entity.getStartDate())
.endDate(entity.getEndDate())
.location(entity.getLocation())
.createdAt(entity.getCreatedAt())
.location(entity.getLocation())
.build();

return entity.getCategory().create(request);
}

public Page<Activity> toDomainPages(Page<ActivityJpaEntity> entities) {
List<Activity> domains = entities.stream()
.map(this::toDomain)
.toList();

return new PageImpl<>(domains, entities.getPageable(), entities.getTotalElements());
}

public ActivityJpaEntity toEntity(Activity domain) {
return ActivityJpaEntity.builder()
.id(domain.getId())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,47 @@
package com.stumeet.server.activity.adapter.out.persistence;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

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.application.port.out.ActivityQueryPort;
import com.stumeet.server.activity.domain.exception.NotExistsActivityException;
import com.stumeet.server.activity.domain.model.Activity;
import com.stumeet.server.activity.domain.model.ActivityCategory;
import com.stumeet.server.common.annotation.PersistenceAdapter;

import lombok.RequiredArgsConstructor;

@PersistenceAdapter
@RequiredArgsConstructor
public class ActivityPersistenceAdapter implements ActivityCreatePort, ActivityQueryPort {

private final JpaActivityRepository jpaActivityRepository;
private final ActivityPersistenceMapper activityPersistenceMapper;
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));
}

@Override
public Activity create(Activity activity) {
ActivityJpaEntity entity = activityPersistenceMapper.toEntity(activity);
@Override
public Activity getById(Long activityId) {
ActivityJpaEntity entity = jpaActivityRepository.findById(activityId)
.orElseThrow(() -> new NotExistsActivityException(activityId));

return activityPersistenceMapper.toDomain(jpaActivityRepository.save(entity));
}
return activityPersistenceMapper.toDomain(entity);
}

@Override
public Activity getById(Long activityId) {
ActivityJpaEntity activityJpaEntity = jpaActivityRepository.findById(activityId)
.orElseThrow(() -> new NotExistsActivityException(activityId));
@Override
public Page<Activity> getDetailPagesByCondition(
Pageable pageable, Boolean isNotice, Long studyId, ActivityCategory category) {
Page<ActivityJpaEntity> entities =
jpaActivityRepository.findDetailPagesByCondition(pageable, isNotice, studyId, category);

return activityPersistenceMapper.toDomain(activityJpaEntity);
}
return activityPersistenceMapper.toDomainPages(entities);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@
import com.stumeet.server.activity.adapter.out.model.ActivityJpaEntity;
import org.springframework.data.jpa.repository.JpaRepository;

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

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import com.stumeet.server.activity.adapter.out.model.ActivityJpaEntity;
import com.stumeet.server.activity.domain.model.ActivityCategory;

public interface JpaActivityRepositoryCustom {
Page<ActivityJpaEntity> findDetailPagesByCondition(Pageable pageable, Boolean isNotice, Long studyId, ActivityCategory category);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.stumeet.server.activity.adapter.out.persistence;

import static com.stumeet.server.activity.adapter.out.model.QActivityJpaEntity.*;

import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.support.PageableExecutionUtils;

import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.stumeet.server.activity.adapter.out.model.ActivityJpaEntity;
import com.stumeet.server.activity.domain.model.ActivityCategory;

import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
public class JpaActivityRepositoryCustomImpl implements JpaActivityRepositoryCustom{

private final JPAQueryFactory query;

@Override
public Page<ActivityJpaEntity> findDetailPagesByCondition(
Pageable pageable, Boolean isNotice, Long studyId, ActivityCategory category) {
List<ActivityJpaEntity> content = query
.selectFrom(activityJpaEntity)
.where(
isNotice != null ? activityJpaEntity.isNotice.eq(isNotice) : null,
studyId != null ? activityJpaEntity.study.id.eq(studyId) : null,
category != null ? activityJpaEntity.category.eq(category) : null)
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.orderBy(activityJpaEntity.createdAt.desc())
.fetch();

JPAQuery<Long> countQuery = query
.select(activityJpaEntity.count())
.from(activityJpaEntity)
.where(
isNotice != null ? activityJpaEntity.isNotice.eq(isNotice) : null,
studyId != null ? activityJpaEntity.study.id.eq(studyId) : null,
category != null ? activityJpaEntity.category.eq(category) : null);

return PageableExecutionUtils.getPage(content, pageable, countQuery::fetchOne);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package com.stumeet.server.activity.application.port.in;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import com.stumeet.server.activity.domain.model.Activity;
import com.stumeet.server.activity.domain.model.ActivityCategory;

public interface ActivityQuery {
Activity getById(Long activityId);

Page<Activity> getDetailsByCondition(Pageable pageable, Boolean isNotice, Long studyId, ActivityCategory category);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.stumeet.server.activity.application.port.in;

import com.stumeet.server.activity.adapter.in.response.ActivityDetailResponse;
import com.stumeet.server.activity.adapter.in.response.ActivityListDetailedPageResponses;
import com.stumeet.server.activity.application.port.in.query.ActivityListDetailedQuery;

public interface ActivityQueryUseCase {
ActivityDetailResponse getById(Long studyId, Long activityId, Long memberId);

ActivityListDetailedPageResponses getDetails(ActivityListDetailedQuery query);
}
Loading

0 comments on commit 8c973d1

Please sign in to comment.