Skip to content
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: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@ secrets.yml
.idea
.env
src/test/resources/application.yml
/uploads
/uploads
./scripts
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-aop'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package run.backend.domain.crew.repository;

import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import run.backend.domain.crew.entity.JoinCrew;
import run.backend.domain.crew.enums.JoinStatus;
import run.backend.domain.event.dto.response.EventCreationValidationDto;

public interface JoinCrewRepository extends JpaRepository<JoinCrew, Long> {

@Query("SELECT jc FROM JoinCrew jc WHERE jc.member.id = :memberId AND jc.joinStatus = :status")
Optional<JoinCrew> findByMemberIdAndJoinStatus(@Param("memberId") Long memberId,
@Param("status") JoinStatus status);

@Query("""
SELECT new run.backend.domain.event.dto.response.EventCreationValidationDto(
requesterJoin.crew,
captainJoin.member
)
FROM JoinCrew requesterJoin
INNER JOIN JoinCrew captainJoin ON requesterJoin.crew.id = captainJoin.crew.id
WHERE requesterJoin.member.id = :requesterId
AND requesterJoin.joinStatus = :status
AND captainJoin.member.id = :runningCaptainId
AND captainJoin.joinStatus = :status
""")
Optional<EventCreationValidationDto> validateEventCreation(
@Param("requesterId") Long requesterId,
@Param("runningCaptainId") Long runningCaptainId,
@Param("status") JoinStatus status
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package run.backend.domain.event.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
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;
import run.backend.domain.event.dto.request.EventInfoRequest;
import run.backend.domain.event.service.EventServiceImpl;
import run.backend.domain.member.entity.Member;
import run.backend.global.annotation.member.Login;
import run.backend.global.common.response.CommonResponse;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/events")
@Tag(name = "Events", description = "일정 관련 API")
public class EventController {

private final EventServiceImpl eventService;

@PostMapping
@PreAuthorize("hasRole('MANAGER') or hasRole('LEADER')")
Copy link
Contributor

@choiseoji choiseoji Jul 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오!!! 이거 완전 좋네요!!
이거 하면 시큐리티 파일에서 따로 API 경로로 권한 검사 안해줘도 되는거죠??

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞아요~

@Operation(summary = "일정 생성", description = "러닝 일정를 생성합니다. LEADER 또는 MANAGER 권한이 필요합니다.")
public CommonResponse<Void> createEvent(
@RequestBody EventInfoRequest eventInfoRequest,
@Login Member member
) {

eventService.createEvent(eventInfoRequest, member);
return new CommonResponse<>("일정 생성 성공");
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
package run.backend.domain.event.dto.request;

public record EventInfoRequest() {
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDate;
import java.time.LocalTime;
import run.backend.domain.event.enums.RepeatCycle;
import run.backend.domain.event.enums.WeekDay;

public record EventInfoRequest(
String title,
LocalDate baseDate,
@Schema(description = "반복 주기", example = "NONE / WEEKLY")
RepeatCycle repeatCycle,
@Schema(description = "반복 요일", example = "MONDAY / TUESDAY / WEDNESDAY / THURSDAY / FRIDAY / SATURDAY / SUNDAY", nullable = true)
WeekDay repeatDays,
LocalTime startTime,
LocalTime endTime,
String place,
@Schema(description = "러닝캡틴 ID", example = "1")
Long runningCaptainId
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package run.backend.domain.event.dto.response;

import run.backend.domain.crew.entity.Crew;
import run.backend.domain.member.entity.Member;

public record EventCreationValidationDto(Crew crew, Member runningCaptain) {

}
12 changes: 10 additions & 2 deletions src/main/java/run/backend/domain/event/entity/Event.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public class Event extends BaseEntity {
@JoinColumn(name = "record_id")
private CrewRecord record;

@OneToOne(fetch = FetchType.LAZY)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "running_captain")
private Member member;

Expand All @@ -70,10 +70,18 @@ public Event(
this.startTime = startTime;
this.endTime = endTime;
this.place = place;
this.expectedParticipants = 1L;
this.expectedParticipants = 0L;
this.actualParticipants = 0L;
this.crew = crew;
this.record = record;
this.member = member;
}

public void incrementExpectedParticipants() {
this.expectedParticipants++;
}

public void incrementActualParticipants() {
this.actualParticipants++;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ public JoinEvent(
this.isRunning = false;
this.member = member;
this.event = event;
this.event.incrementExpectedParticipants();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import run.backend.domain.crew.entity.Crew;
import run.backend.domain.event.enums.RepeatCycle;
import run.backend.domain.event.enums.WeekDay;
import run.backend.domain.member.entity.Member;
import run.backend.global.common.BaseEntity;

import java.time.LocalDate;
Expand Down Expand Up @@ -48,6 +49,10 @@ public class PeriodicEvent extends BaseEntity {
@JoinColumn(name = "crew_id")
private Crew crew;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "running_captain")
private Member member;

@Builder
public PeriodicEvent(
String title,
Expand All @@ -57,7 +62,8 @@ public PeriodicEvent(
LocalTime startTime,
LocalTime endTime,
String place,
Crew crew
Crew crew,
Member member
) {
this.title = title;
this.baseDate = baseDate;
Expand All @@ -67,5 +73,6 @@ public PeriodicEvent(
this.endTime = endTime;
this.place = place;
this.crew = crew;
this.member = member;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package run.backend.domain.event.exception;

import lombok.AllArgsConstructor;
import lombok.Getter;
import run.backend.global.exception.ErrorCode;

@Getter
@AllArgsConstructor
public enum EventErrorCode implements ErrorCode {

RUNNING_CAPTAIN_NOT_CREW_MEMBER(6001, "러닝캡이 크루원이 아닙니다.");

private final int errorCode;
private final String errorMessage;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package run.backend.domain.event.exception;

import run.backend.global.exception.CustomException;

public class EventException extends CustomException {

public EventException(final EventErrorCode eventErrorCode) {
super(eventErrorCode);
}

public static class InvalidEventCreationRequest extends EventException {
public InvalidEventCreationRequest() {
super(EventErrorCode.RUNNING_CAPTAIN_NOT_CREW_MEMBER);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package run.backend.domain.event.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import run.backend.domain.event.entity.Event;

public interface EventRepository extends JpaRepository<Event, Long> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package run.backend.domain.event.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import run.backend.domain.event.entity.JoinEvent;

public interface JoinEventRepository extends JpaRepository<JoinEvent, Long> {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package run.backend.domain.event.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import run.backend.domain.event.entity.PeriodicEvent;

public interface PeriodicEventRepository extends JpaRepository<PeriodicEvent, Long> {
}
17 changes: 9 additions & 8 deletions src/main/java/run/backend/domain/event/service/EventService.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package run.backend.domain.event.service;


import run.backend.domain.event.dto.request.EventInfoRequest;
import run.backend.domain.event.dto.response.EventInfoResponse;
import run.backend.domain.member.entity.Member;

public interface EventService {

void updateEvent(EventInfoRequest eventInfoRequest);

void joinEvent(Long eventId, Long memberId);

void deleteEvent(Long eventId);
void createEvent(EventInfoRequest eventInfoRequest, Member member);

EventInfoResponse getEventDetails(Long eventId);
// void updateEvent(EventInfoRequest eventInfoRequest);
//
// void joinEvent(Long eventId, Long memberId);
//
// void deleteEvent(Long eventId);
//
// EventInfoResponse getEventDetails(Long eventId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package run.backend.domain.event.service;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import run.backend.domain.crew.entity.Crew;
import run.backend.domain.crew.enums.JoinStatus;
import run.backend.domain.crew.repository.JoinCrewRepository;
import run.backend.domain.event.dto.request.EventInfoRequest;
import run.backend.domain.event.dto.response.EventCreationValidationDto;
import run.backend.domain.event.entity.Event;
import run.backend.domain.event.entity.JoinEvent;
import run.backend.domain.event.entity.PeriodicEvent;
import run.backend.domain.event.enums.RepeatCycle;
import run.backend.domain.event.exception.EventException.InvalidEventCreationRequest;
import run.backend.domain.event.repository.EventRepository;
import run.backend.domain.event.repository.JoinEventRepository;
import run.backend.domain.event.repository.PeriodicEventRepository;
import run.backend.domain.member.entity.Member;
import run.backend.global.annotation.global.Logging;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class EventServiceImpl implements EventService {

private final EventRepository eventRepository;
private final PeriodicEventRepository periodicEventRepository;
private final JoinCrewRepository joinCrewRepository;
private final JoinEventRepository joinEventRepository;

@Override
@Transactional
@Logging
public void createEvent(EventInfoRequest eventInfoRequest, Member member) {
EventCreationValidationDto validation = joinCrewRepository
.validateEventCreation(
member.getId(),
eventInfoRequest.runningCaptainId(),
JoinStatus.APPROVED
)
.orElseThrow(InvalidEventCreationRequest::new);

Crew crew = validation.crew();
Member runningCaptain = validation.runningCaptain();

if (eventInfoRequest.repeatCycle() != RepeatCycle.NONE) {
createPeriodicEvent(eventInfoRequest, crew, runningCaptain);
}
createSingleEvent(eventInfoRequest, crew, runningCaptain);
}

private void createPeriodicEvent(EventInfoRequest request, Crew crew, Member runningCaptain) {
PeriodicEvent periodicEvent = PeriodicEvent.builder()
.title(request.title())
.baseDate(request.baseDate())
.repeatCycle(request.repeatCycle())
.repeatDays(request.repeatDays())
.startTime(request.startTime())
.endTime(request.endTime())
.place(request.place())
.crew(crew)
.member(runningCaptain)
.build();

periodicEventRepository.save(periodicEvent);
}

private void createSingleEvent(EventInfoRequest request, Crew crew, Member runningCaptain) {
Event event = Event.builder()
.title(request.title())
.date(request.baseDate())
.startTime(request.startTime())
.endTime(request.endTime())
.place(request.place())
.crew(crew)
.member(runningCaptain)
.build();

Event savedEvent = eventRepository.save(event);

JoinEvent joinEvent = JoinEvent.builder()
.event(savedEvent)
.member(runningCaptain)
.build();

joinEventRepository.save(joinEvent);
}
}
4 changes: 4 additions & 0 deletions src/main/java/run/backend/domain/member/entity/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,8 @@ public Member(String username, String nickname, Gender gender, int age, String o
this.pushEnabled = true;
this.role = Role.NONE;
}

public String toString() {
return "[멤버] 닉네임: " + this.nickname;
}
}
12 changes: 12 additions & 0 deletions src/main/java/run/backend/global/annotation/global/Logging.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package run.backend.global.annotation.global;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Logging {
String description() default "";
}
Loading