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] 회차 삭제 API 개발 #58

Merged
merged 7 commits into from
Jan 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions src/main/java/com/join/core/attendance/domain/Attendance.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
Expand All @@ -20,6 +22,7 @@ public class Attendance extends BaseTimeEntity {

@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@OnDelete(action = OnDeleteAction.CASCADE)
@JoinColumn(name = "meeting_id", nullable = false)
private Meeting meeting;

Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/join/core/common/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ public enum ErrorCode {
DUPLICATE_APPLICATION(HttpStatus.BAD_REQUEST, "S-002", "이미 지원한 스터디입니다."),
APPLICATION_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "S-003", "주어진 식별자로 지원 정보를 찾을 수 없습니다."),

/**
* 회차 관련 오류
*/
MEETING_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "M-001", "주어진 식별자로 회차를 찾을 수 없습니다."),

/**
* 공지 관련 오류
*/
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/com/join/core/common/response/ApiResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,8 @@ public static <T> ApiResponse<T> created(T data) {
return new ApiResponse<>(data, 201, "Created");
}

public static ApiResponse<Void> noContent() {
return new ApiResponse<>(null, 204, "No Content");
}

}
3 changes: 3 additions & 0 deletions src/main/java/com/join/core/fine/domain/Fine.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;

import java.math.BigDecimal;
import java.time.LocalDate;
Expand Down Expand Up @@ -51,6 +53,7 @@ public class Fine extends BaseTimeEntity {

@NotNull
@ManyToOne(fetch = FetchType.LAZY)
@OnDelete(action = OnDeleteAction.CASCADE)
@JoinColumn(name = "meeting_id", nullable = false)
private Meeting meeting;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@

import com.join.core.auth.domain.UserPrincipal;
import com.join.core.common.response.ApiResponse;
import com.join.core.meeting.controller.specification.MeetingControllerSpecification;
import com.join.core.meeting.domain.MeetingAppendService;
import com.join.core.meeting.domain.MeetingDeleteService;
import com.join.core.meeting.domain.MeetingReadService;
import com.join.core.meeting.dto.request.MeetingAppendRequest;
import com.join.core.meeting.dto.response.MeetingResponse;
import com.join.core.meeting.service.MeetingAppendService;
import com.join.core.meeting.service.MeetingReadService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
Expand All @@ -20,30 +19,35 @@
@RequiredArgsConstructor
@RestController
@RequestMapping("${api.prefix}/study/{studyToken}/meetings")
public class MeetingController {
public class MeetingController implements MeetingControllerSpecification {

private final MeetingAppendService meetingAppendService;
private final MeetingReadService meetingReadService;
private final MeetingDeleteService meetingDeleteService;

@Tag(name = "${swagger.tag.meeting}")
@Operation(summary = "회차 추가 - 인증 필수",
description = "회차 추가 - 인증 필수",
security = {@SecurityRequirement(name = "session-token")})
@PreAuthorize("isAuthenticated()")
@PostMapping
public ApiResponse<Void> append(@AuthenticationPrincipal UserPrincipal principal,
@Valid @RequestBody MeetingAppendRequest request) {
@Valid @RequestBody MeetingAppendRequest request,
@PathVariable String studyToken
) {
Long avatarId = principal.getAvatarId();
meetingAppendService.appendMeetingToStudy(avatarId, request);
meetingAppendService.appendMeetingToStudy(avatarId, studyToken, request);
return ApiResponse.ok();
}

@Tag(name = "${swagger.tag.meeting}")
@Operation(summary = "회차 리스트 조회",
description = "회차 리스트 조회",
security = {@SecurityRequirement(name = "session-token")})
@GetMapping
public ApiResponse<List<MeetingResponse>> getMeetingDetails(@PathVariable String studyToken) {
return ApiResponse.ok(meetingReadService.getMeetings(studyToken));
}

@PreAuthorize("isAuthenticated()")
@DeleteMapping("/{meetingId}")
public ApiResponse<Void> delete(@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String studyToken,
@PathVariable Long meetingId) {
Long avatarId = principal.getAvatarId();
meetingDeleteService.delete(avatarId, studyToken, meetingId);
return ApiResponse.noContent();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.join.core.meeting.controller.specification;

import com.join.core.auth.domain.UserPrincipal;
import com.join.core.common.response.ApiResponse;
import com.join.core.meeting.dto.request.MeetingAppendRequest;
import com.join.core.meeting.dto.response.MeetingResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;

import java.util.List;

public interface MeetingControllerSpecification {

@Tag(name = "${swagger.tag.meeting}")
@Operation(summary = "회차 추가 - 인증 필수",
description = "회차 추가 - 인증 필수",
security = {@SecurityRequirement(name = "session-token")})
ApiResponse<Void> append(@AuthenticationPrincipal UserPrincipal principal,
@Valid @RequestBody MeetingAppendRequest request,
@PathVariable String studyToken
);

@Tag(name = "${swagger.tag.meeting}")
@Operation(summary = "회차 리스트 조회",
description = "회차 리스트 조회",
security = {@SecurityRequirement(name = "session-token")})
ApiResponse<List<MeetingResponse>> getMeetingDetails(@PathVariable String studyToken);

@Tag(name = "${swagger.tag.meeting}")
@Operation(summary = "회차 삭제 - 인증 필수",
description = "회차 삭제 - 인증 필수",
security = {@SecurityRequirement(name = "session-token")})
ApiResponse<Void> delete(@AuthenticationPrincipal UserPrincipal principal,
@PathVariable String studyToken,
@PathVariable Long meetingId);
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package com.join.core.meeting.service;
package com.join.core.meeting.domain;

import com.join.core.common.exception.ErrorCode;
import com.join.core.common.exception.impl.NoPermissionException;
import com.join.core.meeting.domain.Meeting;
import com.join.core.meeting.dto.request.MeetingAppendRequest;
import com.join.core.study.domain.Study;
import com.join.core.study.service.StudyReader;
Expand All @@ -24,9 +23,9 @@ public class MeetingAppendService {
private final MeetingReader meetingReader;

@Transactional
public void appendMeetingToStudy(Long avatarId, MeetingAppendRequest request) {
public void appendMeetingToStudy(Long avatarId, String studyToken, MeetingAppendRequest request) {

Study study = studyReader.getStudyByToken(request.getStudyToken());
Study study = studyReader.getStudyByToken(studyToken);

if (!study.getWriter().getId().equals(avatarId)) {
throw new NoPermissionException(ErrorCode.UNAUTHORIZED_ACCESS);
Expand All @@ -35,7 +34,7 @@ public void appendMeetingToStudy(Long avatarId, MeetingAppendRequest request) {

meetingStore.store(newMeeting);

List<Meeting> meetings = meetingReader.findByStudy(study);
List<Meeting> meetings = meetingReader.getMeetingsByStudy(study);
int meetingCount = meetings.size();

meetings.sort(Comparator.comparing(meeting ->
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.join.core.meeting.domain;

import com.join.core.common.exception.ErrorCode;
import com.join.core.common.exception.impl.NoPermissionException;
import com.join.core.study.domain.Study;
import com.join.core.study.service.StudyReader;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.List;
import java.util.stream.IntStream;

@Service
@RequiredArgsConstructor
public class MeetingDeleteService {

private final MeetingReader meetingReader;
private final StudyReader studyReader;
private final MeetingDeleter meetingDeleter;

@Transactional
public void delete(Long avatarId, String studyToken, Long meetingId) {

Study study = studyReader.getStudyByToken(studyToken);

if (!study.isWriter(avatarId)) {
throw new NoPermissionException(ErrorCode.UNAUTHORIZED_ACCESS);
}

Meeting meetingToDelete = meetingReader.getMeetingById(meetingId);

meetingDeleter.delete(meetingToDelete);

List<Meeting> remainingMeetings = meetingReader.getMeetingsByStudy(study)
.stream()
.filter(meeting -> !meeting.getId().equals(meetingId))
.sorted(Comparator.comparing(meeting ->
LocalDateTime.of(meeting.getStudyDate(), meeting.getStTime())))
.toList();

IntStream.range(0, remainingMeetings.size()).forEach(index -> {
Meeting meeting = remainingMeetings.get(index);
meeting.updateMeetingNo(index + 1);
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.join.core.meeting.domain;

public interface MeetingDeleter {
void delete(Meeting meeting);

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.join.core.meeting.service;
package com.join.core.meeting.domain;

import com.join.core.meeting.dto.response.MeetingResponse;
import com.join.core.study.domain.Study;
Expand All @@ -20,7 +20,7 @@ public class MeetingReadService {
public List<MeetingResponse> getMeetings(String studyToken) {
Study study = studyReader.getStudyByToken(studyToken);

return meetingReader.findByStudy(study)
return meetingReader.getMeetingsByStudy(study)
.stream()
.map(MeetingResponse::from)
.toList();
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/com/join/core/meeting/domain/MeetingReader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.join.core.meeting.domain;

import com.join.core.study.domain.Study;

import java.util.List;

public interface MeetingReader {
List<Meeting> getMeetingsByStudy(Study study);
Meeting getMeetingById(Long meetingId);

}
6 changes: 6 additions & 0 deletions src/main/java/com/join/core/meeting/domain/MeetingStore.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.join.core.meeting.domain;

public interface MeetingStore {
void store(Meeting meeting);

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import com.join.core.meeting.domain.Meeting;
import com.join.core.study.domain.Study;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand All @@ -14,11 +13,6 @@
@Getter
@NoArgsConstructor
public class MeetingAppendRequest {

@Schema(description = "회차를 추가하고자 하는 스터디 토큰", example = "std_abc123")
@NotBlank
private String studyToken;

@Schema(description = "스터디 회차 날짜", example = "2024-09-01")
@NotNull
private LocalDate studyDate;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
@Getter
@AllArgsConstructor
public class MeetingResponse {
@Schema(description = "회차 id", example = "1")
private Long id;

@Schema(description = "회차 순서", example = "1")
private int meetingNo;

Expand All @@ -28,6 +31,6 @@ public class MeetingResponse {
private MeetingStatus status;

public static MeetingResponse from(Meeting meeting) {
return new MeetingResponse(meeting.getMeetingNo(), meeting.getStudyDate(), meeting.getStTime(), meeting.getEndTime(), meeting.getStatus());
return new MeetingResponse(meeting.getId(), meeting.getMeetingNo(), meeting.getStudyDate(), meeting.getStTime(), meeting.getEndTime(), meeting.getStatus());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.join.core.meeting.repository;

import com.join.core.meeting.domain.Meeting;
import com.join.core.meeting.domain.MeetingDeleter;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@RequiredArgsConstructor
@Component
public class MeetingDeleterImpl implements MeetingDeleter {

private final MeetingRepository meetingRepository;

@Override
public void delete(Meeting meeting) {
meetingRepository.delete(meeting);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.join.core.meeting.repository;

import com.join.core.common.exception.ErrorCode;
import com.join.core.common.exception.impl.EntityNotFoundException;
import com.join.core.meeting.domain.Meeting;
import com.join.core.meeting.service.MeetingReader;
import com.join.core.meeting.domain.MeetingReader;
import com.join.core.study.domain.Study;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
Expand All @@ -15,7 +17,13 @@ public class MeetingReaderImpl implements MeetingReader {
private final MeetingRepository meetingRepository;

@Override
public List<Meeting> findByStudy(Study study) {
public List<Meeting> getMeetingsByStudy(Study study) {
return meetingRepository.findByStudy(study);
}

@Override
public Meeting getMeetingById(Long meetingId) {
return meetingRepository.findById(meetingId)
.orElseThrow(() -> new EntityNotFoundException(ErrorCode.MEETING_NOT_FOUND));
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.join.core.meeting.repository;

import com.join.core.meeting.domain.Meeting;
import com.join.core.meeting.service.MeetingStore;
import com.join.core.meeting.domain.MeetingStore;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

Expand Down
11 changes: 0 additions & 11 deletions src/main/java/com/join/core/meeting/service/MeetingReader.java

This file was deleted.

8 changes: 0 additions & 8 deletions src/main/java/com/join/core/meeting/service/MeetingStore.java

This file was deleted.

Loading
Loading