From e19b2023a69177407303648d9d3880e1a3473cb2 Mon Sep 17 00:00:00 2001 From: west_east Date: Tue, 22 Jul 2025 13:10:35 +0900 Subject: [PATCH 1/4] =?UTF-8?q?[#19]=20feat:=20service=20=EC=9D=B8?= =?UTF-8?q?=ED=84=B0=ED=8E=98=EC=9D=B4=EC=8A=A4=20=EC=A0=9C=EA=B1=B0=20&?= =?UTF-8?q?=20Mapper=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 5 ++ scripts/run_simple_dummy.sh | 61 +++++++++++++ scripts/simple_dummy_data.sql | 67 ++++++++++++++ .../event/controller/EventController.java | 4 +- .../domain/event/mapper/EventMapper.java | 29 ++++++ .../domain/event/service/EventService.java | 67 ++++++++++++-- .../event/service/EventServiceImpl.java | 89 ------------------- ...iceImplTest.java => EventServiceTest.java} | 69 +++++++++++++- 8 files changed, 287 insertions(+), 104 deletions(-) create mode 100755 scripts/run_simple_dummy.sh create mode 100644 scripts/simple_dummy_data.sql create mode 100644 src/main/java/run/backend/domain/event/mapper/EventMapper.java delete mode 100644 src/main/java/run/backend/domain/event/service/EventServiceImpl.java rename src/test/java/run/backend/domain/event/service/{EventServiceImplTest.java => EventServiceTest.java} (75%) diff --git a/build.gradle b/build.gradle index c08ea6f..0ecbe28 100644 --- a/build.gradle +++ b/build.gradle @@ -46,6 +46,11 @@ dependencies { // swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' + + // mapstruct + implementation 'org.mapstruct:mapstruct:1.5.5.Final' + annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final' + annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0' } tasks.named('test') { diff --git a/scripts/run_simple_dummy.sh b/scripts/run_simple_dummy.sh new file mode 100755 index 0000000..a7fa27a --- /dev/null +++ b/scripts/run_simple_dummy.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# μ„€μ • +CONTAINER_NAME="runners_db" +DB_NAME="runners" +DB_USER="root" +DB_PASSWORD="runners1234!" + +echo "πŸš€ κ°„λ‹¨ν•œ 더미데이터 생성 μ‹œμž‘..." + +# Docker μ»¨ν…Œμ΄λ„ˆ μƒνƒœ 확인 +if ! docker ps | grep -q $CONTAINER_NAME; then + echo "❌ $CONTAINER_NAME μ»¨ν…Œμ΄λ„ˆκ°€ μ‹€ν–‰λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€." + exit 1 +fi + +# λ°μ΄ν„°λ² μ΄μŠ€ μ—°κ²° ν…ŒμŠ€νŠΈ +if ! docker exec $CONTAINER_NAME mysql -u$DB_USER -p$DB_PASSWORD -e "USE $DB_NAME;" 2>/dev/null; then + echo "❌ λ°μ΄ν„°λ² μ΄μŠ€ 연결에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€." + exit 1 +fi + +echo "βœ… λ°μ΄ν„°λ² μ΄μŠ€ μ—°κ²° 성곡" + +# SQL μ‹€ν–‰ +echo "πŸ“ 더미데이터 생성 쀑..." +docker exec -i $CONTAINER_NAME mysql -u$DB_USER -p$DB_PASSWORD $DB_NAME < scripts/simple_dummy_data.sql + +if [ $? -eq 0 ]; then + echo "βœ… 더미데이터 생성 μ™„λ£Œ!" +else + echo "❌ 더미데이터 생성 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€." + exit 1 +fi + +# μƒμ„±λœ 데이터 확인 +echo "πŸ“Š μƒμ„±λœ 데이터 톡계:" +docker exec $CONTAINER_NAME mysql -u$DB_USER -p$DB_PASSWORD $DB_NAME -e " +SELECT 'Members' as Category, COUNT(*) as Total FROM members; +SELECT 'Crews' as Category, COUNT(*) as Total FROM crews; +SELECT 'JoinCrews' as Category, COUNT(*) as Total FROM join_crews; +" + +echo "πŸ“ 크루별 멀버 수 확인:" +docker exec $CONTAINER_NAME mysql -u$DB_USER -p$DB_PASSWORD $DB_NAME -e " +SELECT + c.name as crew_name, + COUNT(jc.member_id) as member_count, + COUNT(CASE WHEN jc.crew_role = 'MANAGER' THEN 1 END) as managers, + COUNT(CASE WHEN jc.crew_role = 'LEADER' THEN 1 END) as leaders +FROM crews c +LEFT JOIN join_crews jc ON c.id = jc.crew_id +WHERE c.name LIKE '크루_%' +GROUP BY c.id, c.name +ORDER BY c.name; +" + +echo "πŸŽ‰ 더미데이터 생성 μ™„λ£Œ!" +echo "πŸ’‘ ν…ŒμŠ€νŠΈμš© ID μ˜ˆμ‹œ:" +echo " 크루1 멀버: ID 4-2003 λ²”μœ„" +echo " 크루2 멀버: ID 2004-4003 λ²”μœ„" diff --git a/scripts/simple_dummy_data.sql b/scripts/simple_dummy_data.sql new file mode 100644 index 0000000..b795009 --- /dev/null +++ b/scripts/simple_dummy_data.sql @@ -0,0 +1,67 @@ +-- κ°„λ‹¨ν•œ 더미데이터 생성 (크루 5개, 멀버 10,000λͺ…) +SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; + +-- 1. 크루 5개 생성 +INSERT INTO crews (name, description, image, invite_code, member_count, monthly_distance_total, monthly_time_total, monthly_score_total, created_at, updated_at) VALUES +('크루_001', '첫 번째 λŸ¬λ‹ ν¬λ£¨μž…λ‹ˆλ‹€', 'crew1.jpg', 'INVITE_001', 2000, 1000.50, 50000, 500.25, NOW(), NOW()), +('크루_002', '두 번째 λŸ¬λ‹ ν¬λ£¨μž…λ‹ˆλ‹€', 'crew2.jpg', 'INVITE_002', 2000, 1200.75, 60000, 600.50, NOW(), NOW()), +('크루_003', 'μ„Έ 번째 λŸ¬λ‹ ν¬λ£¨μž…λ‹ˆλ‹€', 'crew3.jpg', 'INVITE_003', 2000, 1100.25, 55000, 550.75, NOW(), NOW()), +('크루_004', 'λ„€ 번째 λŸ¬λ‹ ν¬λ£¨μž…λ‹ˆλ‹€', 'crew4.jpg', 'INVITE_004', 2000, 1300.00, 65000, 650.00, NOW(), NOW()), +('크루_005', 'λ‹€μ„― 번째 λŸ¬λ‹ ν¬λ£¨μž…λ‹ˆλ‹€', 'crew5.jpg', 'INVITE_005', 2000, 1150.80, 57500, 575.60, NOW(), NOW()); + +-- 2. 멀버 10,000λͺ… 생성 +INSERT INTO members (username, nickname, gender, age, oauth_id, oauth_type, role, profile_image, push_enabled, created_at, updated_at) +SELECT + CONCAT('user_', LPAD(seq, 6, '0')) as username, + CONCAT('μœ μ €_', seq) as nickname, + CASE WHEN seq % 2 = 0 THEN 'MALE' ELSE 'FEMALE' END as gender, + 18 + (seq % 42) as age, + CONCAT('oauth_', seq) as oauth_id, + 'GOOGLE' as oauth_type, + CASE + WHEN seq % 100 = 1 THEN 'MANAGER' + WHEN seq % 200 = 1 THEN 'LEADER' + ELSE 'MEMBER' + END as role, + CONCAT('profile_', (seq % 20) + 1, '.jpg') as profile_image, + true as push_enabled, + NOW() as created_at, + NOW() as updated_at +FROM ( + SELECT (@row_number := @row_number + 1) AS seq + FROM information_schema.columns c1 + CROSS JOIN information_schema.columns c2 + CROSS JOIN (SELECT @row_number := 0) r + LIMIT 10000 +) AS numbers; + +-- 3. 크루 κ°€μž… 관계 생성 +-- 1~2000번: 1번 크루 +-- 2001~4000번: 2번 크루 +-- 4001~6000번: 3번 크루 +-- 6001~8000번: 4번 크루 +-- 8001~10000번: 5번 크루 + +INSERT INTO join_crews (join_status, crew_role, joined_date, member_id, crew_id, created_at, updated_at) +SELECT + 'APPROVED' as join_status, + CASE + WHEN m.id % 100 = 1 THEN 'MANAGER' + WHEN m.id % 200 = 1 THEN 'LEADER' + ELSE 'MEMBER' + END as crew_role, + DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND() * 365) DAY) as joined_date, + m.id as member_id, + CASE + WHEN m.id <= (SELECT MIN(id) + 1999 FROM members WHERE username LIKE 'user_%') THEN (SELECT MIN(id) FROM crews WHERE name LIKE '크루_%') + 0 + WHEN m.id <= (SELECT MIN(id) + 3999 FROM members WHERE username LIKE 'user_%') THEN (SELECT MIN(id) FROM crews WHERE name LIKE '크루_%') + 1 + WHEN m.id <= (SELECT MIN(id) + 5999 FROM members WHERE username LIKE 'user_%') THEN (SELECT MIN(id) FROM crews WHERE name LIKE '크루_%') + 2 + WHEN m.id <= (SELECT MIN(id) + 7999 FROM members WHERE username LIKE 'user_%') THEN (SELECT MIN(id) FROM crews WHERE name LIKE '크루_%') + 3 + ELSE (SELECT MIN(id) FROM crews WHERE name LIKE '크루_%') + 4 + END as crew_id, + NOW() as created_at, + NOW() as updated_at +FROM members m +WHERE m.username LIKE 'user_%'; + +SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS; diff --git a/src/main/java/run/backend/domain/event/controller/EventController.java b/src/main/java/run/backend/domain/event/controller/EventController.java index 9391ae3..f82fb92 100644 --- a/src/main/java/run/backend/domain/event/controller/EventController.java +++ b/src/main/java/run/backend/domain/event/controller/EventController.java @@ -9,7 +9,7 @@ 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.event.service.EventService; import run.backend.domain.member.entity.Member; import run.backend.global.annotation.member.Login; import run.backend.global.common.response.CommonResponse; @@ -20,7 +20,7 @@ @Tag(name = "Events", description = "일정 κ΄€λ ¨ API") public class EventController { - private final EventServiceImpl eventService; + private final EventService eventService; @PostMapping @PreAuthorize("hasRole('MANAGER') or hasRole('LEADER')") diff --git a/src/main/java/run/backend/domain/event/mapper/EventMapper.java b/src/main/java/run/backend/domain/event/mapper/EventMapper.java new file mode 100644 index 0000000..4c7c88f --- /dev/null +++ b/src/main/java/run/backend/domain/event/mapper/EventMapper.java @@ -0,0 +1,29 @@ +package run.backend.domain.event.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.ReportingPolicy; +import run.backend.domain.crew.entity.Crew; +import run.backend.domain.event.dto.request.EventInfoRequest; +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.member.entity.Member; + +@Mapper( + componentModel = "spring", + unmappedTargetPolicy = ReportingPolicy.IGNORE +) +public interface EventMapper { + + @Mapping(target = "date", source = "request.baseDate") + @Mapping(target = "member", source = "runningCaptain") + Event toEvent(EventInfoRequest request, Crew crew, Member runningCaptain); + + @Mapping(target = "member", source = "runningCaptain") + PeriodicEvent toPeriodicEvent(EventInfoRequest request, Crew crew, Member runningCaptain); + + @Mapping(target = "member", source = "runningCaptain") + @Mapping(target = "event", source = "event") + JoinEvent toJoinEvent(Event event, Member runningCaptain); +} diff --git a/src/main/java/run/backend/domain/event/service/EventService.java b/src/main/java/run/backend/domain/event/service/EventService.java index 78ca968..e73d3a7 100644 --- a/src/main/java/run/backend/domain/event/service/EventService.java +++ b/src/main/java/run/backend/domain/event/service/EventService.java @@ -1,17 +1,66 @@ 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.mapper.EventMapper; +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; -public interface EventService { +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class EventService { - void createEvent(EventInfoRequest eventInfoRequest, Member member); + private final EventRepository eventRepository; + private final PeriodicEventRepository periodicEventRepository; + private final JoinCrewRepository joinCrewRepository; + private final JoinEventRepository joinEventRepository; + private final EventMapper eventMapper; -// void updateEvent(EventInfoRequest eventInfoRequest); -// -// void joinEvent(Long eventId, Long memberId); -// -// void deleteEvent(Long eventId); -// -// EventInfoResponse getEventDetails(Long eventId); + @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 = eventMapper.toPeriodicEvent(request, crew, runningCaptain); + periodicEventRepository.save(periodicEvent); + } + + private void createSingleEvent(EventInfoRequest request, Crew crew, Member runningCaptain) { + Event event = eventMapper.toEvent(request, crew, runningCaptain); + Event savedEvent = eventRepository.save(event); + + JoinEvent joinEvent = eventMapper.toJoinEvent(savedEvent, runningCaptain); + joinEventRepository.save(joinEvent); + } } diff --git a/src/main/java/run/backend/domain/event/service/EventServiceImpl.java b/src/main/java/run/backend/domain/event/service/EventServiceImpl.java deleted file mode 100644 index 6911817..0000000 --- a/src/main/java/run/backend/domain/event/service/EventServiceImpl.java +++ /dev/null @@ -1,89 +0,0 @@ -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); - } -} diff --git a/src/test/java/run/backend/domain/event/service/EventServiceImplTest.java b/src/test/java/run/backend/domain/event/service/EventServiceTest.java similarity index 75% rename from src/test/java/run/backend/domain/event/service/EventServiceImplTest.java rename to src/test/java/run/backend/domain/event/service/EventServiceTest.java index 47b8731..9bc20c6 100644 --- a/src/test/java/run/backend/domain/event/service/EventServiceImplTest.java +++ b/src/test/java/run/backend/domain/event/service/EventServiceTest.java @@ -17,6 +17,8 @@ import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; import run.backend.domain.crew.entity.Crew; import run.backend.domain.crew.repository.JoinCrewRepository; import run.backend.domain.event.dto.request.EventInfoRequest; @@ -27,6 +29,7 @@ import run.backend.domain.event.enums.RepeatCycle; import run.backend.domain.event.enums.WeekDay; import run.backend.domain.event.exception.EventException.InvalidEventCreationRequest; +import run.backend.domain.event.mapper.EventMapper; import run.backend.domain.event.repository.EventRepository; import run.backend.domain.event.repository.JoinEventRepository; import run.backend.domain.event.repository.PeriodicEventRepository; @@ -35,8 +38,9 @@ import run.backend.domain.member.enums.OAuthType; @ExtendWith(MockitoExtension.class) -@DisplayName("EventServiceImpl") -class EventServiceImplTest { +@MockitoSettings(strictness = Strictness.LENIENT) +@DisplayName("EventService") +class EventServiceTest { @Mock private EventRepository eventRepository; @@ -50,13 +54,18 @@ class EventServiceImplTest { @Mock private JoinEventRepository joinEventRepository; + @Mock + private EventMapper eventMapper; + @InjectMocks - private EventServiceImpl sut; // System Under Test + private EventService sut; // System Under Test private Member requestMember; private Member runningCaptain; private Crew crew; private Event savedEvent; + private JoinEvent savedJoinEvent; + private PeriodicEvent savedPeriodicEvent; @BeforeEach void setUp() { @@ -64,6 +73,8 @@ void setUp() { runningCaptain = createMember("λŸ¬λ‹μΊ‘ν‹΄"); crew = createCrew("ν…ŒμŠ€νŠΈν¬λ£¨"); savedEvent = createEvent(); + savedJoinEvent = createJoinEvent(); + savedPeriodicEvent = createPeriodicEvent(); } @Nested @@ -80,9 +91,18 @@ void shouldCreateSingleEventSuccessfully() { given(joinCrewRepository.validateEventCreation(any(), any(), any())) .willReturn(Optional.of(validation)); + given(eventMapper.toEvent(any(EventInfoRequest.class), any(Crew.class), any(Member.class))) + .willReturn(savedEvent); + + given(eventMapper.toJoinEvent(any(Event.class), any(Member.class))) + .willReturn(savedJoinEvent); + given(eventRepository.save(any(Event.class))) .willReturn(savedEvent); + given(joinEventRepository.save(any(JoinEvent.class))) + .willReturn(savedJoinEvent); + // when sut.createEvent(request, requestMember); @@ -102,9 +122,24 @@ void shouldCreatePeriodicEventSuccessfully() { given(joinCrewRepository.validateEventCreation(any(), any(), any())) .willReturn(Optional.of(validation)); + given(eventMapper.toPeriodicEvent(any(EventInfoRequest.class), any(Crew.class), any(Member.class))) + .willReturn(savedPeriodicEvent); + + given(eventMapper.toEvent(any(EventInfoRequest.class), any(Crew.class), any(Member.class))) + .willReturn(savedEvent); + + given(eventMapper.toJoinEvent(any(Event.class), any(Member.class))) + .willReturn(savedJoinEvent); + + given(periodicEventRepository.save(any(PeriodicEvent.class))) + .willReturn(savedPeriodicEvent); + given(eventRepository.save(any(Event.class))) .willReturn(savedEvent); + given(joinEventRepository.save(any(JoinEvent.class))) + .willReturn(savedJoinEvent); + // when sut.createEvent(request, requestMember); @@ -142,9 +177,18 @@ void shouldAutoJoinRunningCaptainToEvent() { given(joinCrewRepository.validateEventCreation(any(), any(), any())) .willReturn(Optional.of(validation)); + given(eventMapper.toEvent(any(EventInfoRequest.class), any(Crew.class), any(Member.class))) + .willReturn(savedEvent); + + given(eventMapper.toJoinEvent(any(Event.class), any(Member.class))) + .willReturn(savedJoinEvent); + given(eventRepository.save(any(Event.class))) .willReturn(savedEvent); + given(joinEventRepository.save(any(JoinEvent.class))) + .willReturn(savedJoinEvent); + // when sut.createEvent(request, requestMember); @@ -185,6 +229,23 @@ private Event createEvent() { .build(); } + private JoinEvent createJoinEvent() { + return JoinEvent.builder() + .event(savedEvent) + .member(runningCaptain) + .build(); + } + + private PeriodicEvent createPeriodicEvent() { + return PeriodicEvent.builder() + .baseDate(LocalDate.of(2025, 7, 18)) + .repeatCycle(RepeatCycle.WEEKLY) + .repeatDays(WeekDay.MONDAY) + .crew(crew) + .member(runningCaptain) + .build(); + } + private EventInfoRequest createSingleEventRequest() { return new EventInfoRequest( "ν…ŒμŠ€νŠΈ 일정", @@ -210,4 +271,4 @@ private EventInfoRequest createPeriodicEventRequest() { 1L ); } -} +} \ No newline at end of file From 4f958272e57e11cbb8c983c6ed2e494216c187aa Mon Sep 17 00:00:00 2001 From: west_east Date: Tue, 22 Jul 2025 15:22:10 +0900 Subject: [PATCH 2/4] =?UTF-8?q?[#19]=20feat:=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crew/repository/JoinCrewRepository.java | 20 ++-- .../event/controller/EventController.java | 27 ++++-- .../event/dto/request/EventInfoRequest.java | 12 +++ .../backend/domain/event/entity/Event.java | 44 +++++++-- .../domain/event/entity/PeriodicEvent.java | 54 +++++++++-- .../event/exception/EventErrorCode.java | 3 +- .../event/exception/EventException.java | 6 ++ .../domain/event/mapper/EventMapper.java | 14 +++ .../event/repository/JoinEventRepository.java | 6 +- .../repository/PeriodicEventRepository.java | 21 ++++ .../domain/event/service/EventService.java | 96 +++++++++++++++++++ .../member/exception/MemberErrorCode.java | 3 +- .../member/exception/MemberException.java | 6 ++ .../exception/GlobalExceptionHandler.java | 53 +++++----- 14 files changed, 308 insertions(+), 57 deletions(-) diff --git a/src/main/java/run/backend/domain/crew/repository/JoinCrewRepository.java b/src/main/java/run/backend/domain/crew/repository/JoinCrewRepository.java index 28561dc..4655fd8 100644 --- a/src/main/java/run/backend/domain/crew/repository/JoinCrewRepository.java +++ b/src/main/java/run/backend/domain/crew/repository/JoinCrewRepository.java @@ -17,16 +17,16 @@ public interface JoinCrewRepository extends JpaRepository { boolean existsByMemberAndJoinStatus(Member member, JoinStatus joinStatus); @Query(""" - SELECT jc.member - FROM JoinCrew jc - JOIN Crew c ON jc.crew = c - WHERE jc.role = :role - """) + SELECT jc.member + FROM JoinCrew jc + JOIN Crew c ON jc.crew = c + WHERE jc.role = :role + """) Member findCrewLeader(@Param("role") Role role, Crew crew); @Query("SELECT jc FROM JoinCrew jc WHERE jc.member.id = :memberId AND jc.joinStatus = :status") Optional findByMemberIdAndJoinStatus(@Param("memberId") Long memberId, - @Param("status") JoinStatus status); + @Param("status") JoinStatus status); @Query(""" SELECT new run.backend.domain.event.dto.response.EventCreationValidationDto( @@ -41,8 +41,14 @@ Optional findByMemberIdAndJoinStatus(@Param("memberId") Long memberId, AND captainJoin.joinStatus = :status """) Optional validateEventCreation( - @Param("requesterId") Long requesterId, + @Param("requesterId") Long requesterId, @Param("runningCaptainId") Long runningCaptainId, @Param("status") JoinStatus status ); + + boolean existsByMemberIdAndCrewIdAndJoinStatus( + Long memberId, + Long crewId, + JoinStatus joinStatus + ); } diff --git a/src/main/java/run/backend/domain/event/controller/EventController.java b/src/main/java/run/backend/domain/event/controller/EventController.java index f82fb92..660f7a9 100644 --- a/src/main/java/run/backend/domain/event/controller/EventController.java +++ b/src/main/java/run/backend/domain/event/controller/EventController.java @@ -4,6 +4,8 @@ import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PatchMapping; +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; @@ -19,18 +21,31 @@ @RequestMapping("/api/v1/events") @Tag(name = "Events", description = "일정 κ΄€λ ¨ API") public class EventController { - + private final EventService eventService; @PostMapping @PreAuthorize("hasRole('MANAGER') or hasRole('LEADER')") - @Operation(summary = "일정 생성", description = "λŸ¬λ‹ 일정λ₯Ό μƒμ„±ν•©λ‹ˆλ‹€. LEADER λ˜λŠ” MANAGER κΆŒν•œμ΄ ν•„μš”ν•©λ‹ˆλ‹€.") + @Operation(summary = "일정 생성", description = "λŸ¬λ‹ 일정을 μƒμ„±ν•©λ‹ˆλ‹€. LEADER λ˜λŠ” MANAGER κΆŒν•œμ΄ ν•„μš”ν•©λ‹ˆλ‹€.") public CommonResponse createEvent( - @RequestBody EventInfoRequest eventInfoRequest, - @Login Member member + @RequestBody EventInfoRequest eventInfoRequest, + @Login Member member ) { - + eventService.createEvent(eventInfoRequest, member); - return new CommonResponse<>("일정 생성 성곡"); + return new CommonResponse<>("λŸ¬λ‹ 일정 생성 성곡"); + } + + @PatchMapping("/{eventId}") + @PreAuthorize("hasRole('MANAGER') or hasRole('LEADER')") + @Operation(summary = "일정 μˆ˜μ •", description = "λŸ¬λ‹ 일정을 μˆ˜μ •ν•©λ‹ˆλ‹€. LEADER λ˜λŠ” MANAGER κΆŒν•œμ΄ ν•„μš”ν•©λ‹ˆλ‹€.") + public CommonResponse updateEvent( + @PathVariable Long eventId, + @RequestBody EventInfoRequest eventUpdateRequest, + @Login Member member + ) { + + eventService.updateEvent(eventId, eventUpdateRequest, member); + return new CommonResponse<>("λŸ¬λ‹ 일정 μˆ˜μ • 성곡"); } } diff --git a/src/main/java/run/backend/domain/event/dto/request/EventInfoRequest.java b/src/main/java/run/backend/domain/event/dto/request/EventInfoRequest.java index b00b751..40bebd9 100644 --- a/src/main/java/run/backend/domain/event/dto/request/EventInfoRequest.java +++ b/src/main/java/run/backend/domain/event/dto/request/EventInfoRequest.java @@ -7,15 +7,27 @@ import run.backend.domain.event.enums.WeekDay; public record EventInfoRequest( + @Schema(description = "일정 제λͺ©") String title, + + @Schema(description = "일정 λ‚ μ§œ") LocalDate baseDate, + @Schema(description = "반볡 μ£ΌκΈ°", example = "NONE / WEEKLY") RepeatCycle repeatCycle, + @Schema(description = "반볡 μš”μΌ", example = "MONDAY / TUESDAY / WEDNESDAY / THURSDAY / FRIDAY / SATURDAY / SUNDAY", nullable = true) WeekDay repeatDays, + + @Schema(description = "μ‹œμž‘ μ‹œκ°„") LocalTime startTime, + + @Schema(description = "μ’…λ£Œ μ‹œκ°„") LocalTime endTime, + + @Schema(description = "μž₯μ†Œ") String place, + @Schema(description = "λŸ¬λ‹μΊ‘ν‹΄ ID", example = "1") Long runningCaptainId ) { diff --git a/src/main/java/run/backend/domain/event/entity/Event.java b/src/main/java/run/backend/domain/event/entity/Event.java index af07741..5173bbe 100644 --- a/src/main/java/run/backend/domain/event/entity/Event.java +++ b/src/main/java/run/backend/domain/event/entity/Event.java @@ -56,14 +56,14 @@ public class Event extends BaseEntity { @Builder public Event( - String title, - LocalDate date, - LocalTime startTime, - LocalTime endTime, - String place, - Crew crew, - CrewRecord record, - Member member + String title, + LocalDate date, + LocalTime startTime, + LocalTime endTime, + String place, + Crew crew, + CrewRecord record, + Member member ) { this.title = title; this.date = date; @@ -84,4 +84,32 @@ public void incrementExpectedParticipants() { public void incrementActualParticipants() { this.actualParticipants++; } + + public void updateEvent( + String title, + LocalDate date, + LocalTime startTime, + LocalTime endTime, + String place, + Member runningCaptain + ) { + if (title != null) { + this.title = title; + } + if (date != null) { + this.date = date; + } + if (startTime != null) { + this.startTime = startTime; + } + if (endTime != null) { + this.endTime = endTime; + } + if (place != null) { + this.place = place; + } + if (runningCaptain != null) { + this.member = runningCaptain; + } + } } diff --git a/src/main/java/run/backend/domain/event/entity/PeriodicEvent.java b/src/main/java/run/backend/domain/event/entity/PeriodicEvent.java index 07b02d3..79e995b 100644 --- a/src/main/java/run/backend/domain/event/entity/PeriodicEvent.java +++ b/src/main/java/run/backend/domain/event/entity/PeriodicEvent.java @@ -55,15 +55,15 @@ public class PeriodicEvent extends BaseEntity { @Builder public PeriodicEvent( - String title, - LocalDate baseDate, - RepeatCycle repeatCycle, - WeekDay repeatDays, - LocalTime startTime, - LocalTime endTime, - String place, - Crew crew, - Member member + String title, + LocalDate baseDate, + RepeatCycle repeatCycle, + WeekDay repeatDays, + LocalTime startTime, + LocalTime endTime, + String place, + Crew crew, + Member member ) { this.title = title; this.baseDate = baseDate; @@ -75,4 +75,40 @@ public PeriodicEvent( this.crew = crew; this.member = member; } + + public void updatePeriodicEvent( + String title, + LocalDate baseDate, + RepeatCycle repeatCycle, + WeekDay repeatDays, + LocalTime startTime, + LocalTime endTime, + String place, + Member runningCaptain + ) { + if (title != null) { + this.title = title; + } + if (baseDate != null) { + this.baseDate = baseDate; + } + if (repeatCycle != null) { + this.repeatCycle = repeatCycle; + } + if (repeatDays != null) { + this.repeatDays = repeatDays; + } + if (startTime != null) { + this.startTime = startTime; + } + if (endTime != null) { + this.endTime = endTime; + } + if (place != null) { + this.place = place; + } + if (runningCaptain != null) { + this.member = runningCaptain; + } + } } diff --git a/src/main/java/run/backend/domain/event/exception/EventErrorCode.java b/src/main/java/run/backend/domain/event/exception/EventErrorCode.java index ebbd7c2..cd3f607 100644 --- a/src/main/java/run/backend/domain/event/exception/EventErrorCode.java +++ b/src/main/java/run/backend/domain/event/exception/EventErrorCode.java @@ -8,7 +8,8 @@ @AllArgsConstructor public enum EventErrorCode implements ErrorCode { - RUNNING_CAPTAIN_NOT_CREW_MEMBER(6001, "λŸ¬λ‹μΊ‘μ΄ 크루원이 μ•„λ‹™λ‹ˆλ‹€."); + RUNNING_CAPTAIN_NOT_CREW_MEMBER(6001, "λŸ¬λ‹μΊ‘μ΄ 크루원이 μ•„λ‹™λ‹ˆλ‹€."), + EVENT_NOT_FOUND(6002, "일정을 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."); private final int errorCode; private final String errorMessage; diff --git a/src/main/java/run/backend/domain/event/exception/EventException.java b/src/main/java/run/backend/domain/event/exception/EventException.java index 8b12138..179f26d 100644 --- a/src/main/java/run/backend/domain/event/exception/EventException.java +++ b/src/main/java/run/backend/domain/event/exception/EventException.java @@ -13,4 +13,10 @@ public InvalidEventCreationRequest() { super(EventErrorCode.RUNNING_CAPTAIN_NOT_CREW_MEMBER); } } + + public static class EventNotFound extends EventException { + public EventNotFound() { + super(EventErrorCode.EVENT_NOT_FOUND); + } + } } diff --git a/src/main/java/run/backend/domain/event/mapper/EventMapper.java b/src/main/java/run/backend/domain/event/mapper/EventMapper.java index 0d843fa..29a7f3b 100644 --- a/src/main/java/run/backend/domain/event/mapper/EventMapper.java +++ b/src/main/java/run/backend/domain/event/mapper/EventMapper.java @@ -34,4 +34,18 @@ public interface EventMapper { EventProfileResponse toEventProfile(Event event); List toEventProfileList(List events); + + default EventInfoRequest toEventInfoRequest(EventInfoRequest updateRequest, Event event) { + return new EventInfoRequest( + updateRequest.title() != null ? updateRequest.title() : event.getTitle(), + updateRequest.baseDate() != null ? updateRequest.baseDate() : event.getDate(), + updateRequest.repeatCycle(), + updateRequest.repeatDays(), + updateRequest.startTime() != null ? updateRequest.startTime() : event.getStartTime(), + updateRequest.endTime() != null ? updateRequest.endTime() : event.getEndTime(), + updateRequest.place() != null ? updateRequest.place() : event.getPlace(), + updateRequest.runningCaptainId() != null ? updateRequest.runningCaptainId() + : event.getMember().getId() + ); + } } diff --git a/src/main/java/run/backend/domain/event/repository/JoinEventRepository.java b/src/main/java/run/backend/domain/event/repository/JoinEventRepository.java index bb974ea..2977acf 100644 --- a/src/main/java/run/backend/domain/event/repository/JoinEventRepository.java +++ b/src/main/java/run/backend/domain/event/repository/JoinEventRepository.java @@ -1,8 +1,12 @@ package run.backend.domain.event.repository; import org.springframework.data.jpa.repository.JpaRepository; +import run.backend.domain.event.entity.Event; import run.backend.domain.event.entity.JoinEvent; +import run.backend.domain.member.entity.Member; public interface JoinEventRepository extends JpaRepository { - + + void deleteByEventAndMember(Event event, Member member); + } diff --git a/src/main/java/run/backend/domain/event/repository/PeriodicEventRepository.java b/src/main/java/run/backend/domain/event/repository/PeriodicEventRepository.java index b3c0e23..f908f09 100644 --- a/src/main/java/run/backend/domain/event/repository/PeriodicEventRepository.java +++ b/src/main/java/run/backend/domain/event/repository/PeriodicEventRepository.java @@ -1,7 +1,28 @@ package run.backend.domain.event.repository; 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.Crew; import run.backend.domain.event.entity.PeriodicEvent; +import java.time.LocalTime; +import java.util.Optional; + public interface PeriodicEventRepository extends JpaRepository { + + @Query(""" + SELECT pe + FROM PeriodicEvent pe + WHERE pe.crew = :crew + AND pe.title = :title + AND pe.startTime = :startTime + AND pe.endTime = :endTime + """) + Optional findByCrewAndTitleAndTime( + @Param("crew") Crew crew, + @Param("title") String title, + @Param("startTime") LocalTime startTime, + @Param("endTime") LocalTime endTime + ); } diff --git a/src/main/java/run/backend/domain/event/service/EventService.java b/src/main/java/run/backend/domain/event/service/EventService.java index e73d3a7..30afcfd 100644 --- a/src/main/java/run/backend/domain/event/service/EventService.java +++ b/src/main/java/run/backend/domain/event/service/EventService.java @@ -1,5 +1,6 @@ package run.backend.domain.event.service; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -12,12 +13,15 @@ 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.EventNotFound; import run.backend.domain.event.exception.EventException.InvalidEventCreationRequest; import run.backend.domain.event.mapper.EventMapper; 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.domain.member.exception.MemberException.MemberNotFound; +import run.backend.domain.member.repository.MemberRepository; import run.backend.global.annotation.global.Logging; @Service @@ -29,6 +33,7 @@ public class EventService { private final PeriodicEventRepository periodicEventRepository; private final JoinCrewRepository joinCrewRepository; private final JoinEventRepository joinEventRepository; + private final MemberRepository memberRepository; private final EventMapper eventMapper; @Transactional @@ -51,6 +56,31 @@ public void createEvent(EventInfoRequest eventInfoRequest, Member member) { createSingleEvent(eventInfoRequest, crew, runningCaptain); } + @Transactional + @Logging + public void updateEvent(Long eventId, EventInfoRequest eventUpdateRequest, Member member) { + Event event = eventRepository.findById(eventId) + .orElseThrow(EventNotFound::new); + + Member newRunningCaptain = validateNewRunningCaptain(eventUpdateRequest, event); + + if (newRunningCaptain != null && !event.getMember().getId() + .equals(newRunningCaptain.getId())) { + updateRunningCaptain(event, newRunningCaptain); + } + + handlePeriodicEventUpdate(event, eventUpdateRequest, newRunningCaptain); + + event.updateEvent( + eventUpdateRequest.title(), + eventUpdateRequest.baseDate(), + eventUpdateRequest.startTime(), + eventUpdateRequest.endTime(), + eventUpdateRequest.place(), + newRunningCaptain + ); + } + private void createPeriodicEvent(EventInfoRequest request, Crew crew, Member runningCaptain) { PeriodicEvent periodicEvent = eventMapper.toPeriodicEvent(request, crew, runningCaptain); periodicEventRepository.save(periodicEvent); @@ -63,4 +93,70 @@ private void createSingleEvent(EventInfoRequest request, Crew crew, Member runni JoinEvent joinEvent = eventMapper.toJoinEvent(savedEvent, runningCaptain); joinEventRepository.save(joinEvent); } + + private Member validateNewRunningCaptain(EventInfoRequest request, Event event) { + if (request.runningCaptainId() == null) { + return null; + } + + boolean isCrewMember = joinCrewRepository + .existsByMemberIdAndCrewIdAndJoinStatus( + request.runningCaptainId(), + event.getCrew().getId(), + JoinStatus.APPROVED + ); + + if (!isCrewMember) { + throw new InvalidEventCreationRequest(); + } + + return memberRepository.findById(request.runningCaptainId()) + .orElseThrow(MemberNotFound::new); + } + + private void updateRunningCaptain(Event event, Member newRunningCaptain) { + joinEventRepository.deleteByEventAndMember(event, event.getMember()); + + JoinEvent newJoinEvent = eventMapper.toJoinEvent(event, newRunningCaptain); + joinEventRepository.save(newJoinEvent); + } + + private void handlePeriodicEventUpdate(Event event, EventInfoRequest request, + Member newRunningCaptain) { + Optional existingPeriodicEvent = periodicEventRepository + .findByCrewAndTitleAndTime( + event.getCrew(), + event.getTitle(), + event.getStartTime(), + event.getEndTime() + ); + + RepeatCycle requestedRepeatCycle = request.repeatCycle(); + + if (requestedRepeatCycle == null || requestedRepeatCycle == RepeatCycle.NONE) { + existingPeriodicEvent.ifPresent(periodicEventRepository::delete); + } else { + if (existingPeriodicEvent.isPresent()) { + PeriodicEvent periodicEvent = existingPeriodicEvent.get(); + periodicEvent.updatePeriodicEvent( + request.title(), + request.baseDate(), + requestedRepeatCycle, + request.repeatDays(), + request.startTime(), + request.endTime(), + request.place(), + newRunningCaptain + ); + } else { + EventInfoRequest eventInfoRequest = eventMapper.toEventInfoRequest(request, event); + PeriodicEvent newPeriodicEvent = eventMapper.toPeriodicEvent( + eventInfoRequest, + event.getCrew(), + newRunningCaptain != null ? newRunningCaptain : event.getMember() + ); + periodicEventRepository.save(newPeriodicEvent); + } + } + } } diff --git a/src/main/java/run/backend/domain/member/exception/MemberErrorCode.java b/src/main/java/run/backend/domain/member/exception/MemberErrorCode.java index 87b68ee..93baedd 100644 --- a/src/main/java/run/backend/domain/member/exception/MemberErrorCode.java +++ b/src/main/java/run/backend/domain/member/exception/MemberErrorCode.java @@ -8,7 +8,8 @@ @AllArgsConstructor public enum MemberErrorCode implements ErrorCode { - MEMBER_NOT_JOINED_CREW(5001, "κ°€μž…ν•œ 크루가 μ—†μŠ΅λ‹ˆλ‹€."); + MEMBER_NOT_JOINED_CREW(5001, "κ°€μž…ν•œ 크루가 μ—†μŠ΅λ‹ˆλ‹€."), + MEMBER_NOT_FOUND(5002, "ν•΄λ‹Ή μœ μ €λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."); private final int errorCode; private final String errorMessage; diff --git a/src/main/java/run/backend/domain/member/exception/MemberException.java b/src/main/java/run/backend/domain/member/exception/MemberException.java index fed1333..e107242 100644 --- a/src/main/java/run/backend/domain/member/exception/MemberException.java +++ b/src/main/java/run/backend/domain/member/exception/MemberException.java @@ -13,4 +13,10 @@ public MemberNotJoinedCrew() { super(MemberErrorCode.MEMBER_NOT_JOINED_CREW); } } + + public static class MemberNotFound extends MemberException { + public MemberNotFound() { + super(MemberErrorCode.MEMBER_NOT_FOUND); + } + } } diff --git a/src/main/java/run/backend/global/exception/GlobalExceptionHandler.java b/src/main/java/run/backend/global/exception/GlobalExceptionHandler.java index 0187086..c83fba7 100644 --- a/src/main/java/run/backend/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/run/backend/global/exception/GlobalExceptionHandler.java @@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import run.backend.domain.auth.exception.AuthException; import run.backend.domain.crew.exception.CrewException; +import run.backend.domain.event.exception.EventException; import run.backend.domain.file.exception.FileException; import run.backend.domain.member.exception.MemberException; import run.backend.global.common.response.CommonResponse; @@ -19,52 +20,55 @@ public class GlobalExceptionHandler { @ExceptionHandler({ - AuthException.RefreshTokenNotFound.class, - FileException.FileNotFound.class, - MemberException.MemberNotJoinedCrew.class, - CrewException.NotFoundCrew.class + AuthException.RefreshTokenNotFound.class, + FileException.FileNotFound.class, + MemberException.MemberNotJoinedCrew.class, + MemberException.MemberNotFound.class, + CrewException.NotFoundCrew.class, + EventException.EventNotFound.class }) public ResponseEntity> handleNotFound(final CustomException e) { log.warn("[NOT_FOUND_EXCEPTION] {}", e.toString()); return ResponseEntity - .status(HttpStatus.NOT_FOUND) - .body(new CommonResponse<>(e.getErrorCode(), e.getErrorMessage())); + .status(HttpStatus.NOT_FOUND) + .body(new CommonResponse<>(e.getErrorCode(), e.getErrorMessage())); } @ExceptionHandler({ - AuthException.UserAlreadyExists.class, - FileException.FileSizeExceeded.class, - FileException.InvalidFileName.class, - FileException.InvalidFileExtension.class, - FileException.InvalidFileType.class, - CrewException.AlreadyJoinedCrew.class + AuthException.UserAlreadyExists.class, + FileException.FileSizeExceeded.class, + FileException.InvalidFileName.class, + FileException.InvalidFileExtension.class, + FileException.InvalidFileType.class, + CrewException.AlreadyJoinedCrew.class }) public ResponseEntity> handleConflict(final CustomException e) { log.warn("[CONFLICT_EXCEPTION] {}", e.toString()); return ResponseEntity - .status(HttpStatus.CONFLICT) - .body(new CommonResponse<>(e.getErrorCode(), e.getErrorMessage())); + .status(HttpStatus.CONFLICT) + .body(new CommonResponse<>(e.getErrorCode(), e.getErrorMessage())); } @ExceptionHandler({ - AuthException.InvalidSignupToken.class, - AuthException.OauthRequestFailed.class, - AuthException.TokenMissingAuthority.class, - AuthException.InvalidRefreshToken.class, - AuthException.RefreshTokenExpired.class, - FileException.FileUploadFailed.class + AuthException.InvalidSignupToken.class, + AuthException.OauthRequestFailed.class, + AuthException.TokenMissingAuthority.class, + AuthException.InvalidRefreshToken.class, + AuthException.RefreshTokenExpired.class, + FileException.FileUploadFailed.class, + EventException.InvalidEventCreationRequest.class }) public ResponseEntity> handleBadRequest(final CustomException e) { log.warn("[BAD_REQUEST_EXCEPTION] {}", e.toString()); return ResponseEntity - .status(HttpStatus.BAD_REQUEST) - .body(new CommonResponse<>(e.getErrorCode(), e.getErrorMessage())); + .status(HttpStatus.BAD_REQUEST) + .body(new CommonResponse<>(e.getErrorCode(), e.getErrorMessage())); } @ExceptionHandler(Exception.class) @@ -73,7 +77,8 @@ public ResponseEntity> handleUnknownException(final Excepti log.error("[INTERNAL_SERVER_ERROR]", e); return ResponseEntity - .status(HttpStatus.INTERNAL_SERVER_ERROR) - .body(new CommonResponse<>(HttpErrorCode.INTERNAL_SERVER_ERROR.getErrorCode(), HttpErrorCode.INTERNAL_SERVER_ERROR.getErrorMessage())); + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new CommonResponse<>(HttpErrorCode.INTERNAL_SERVER_ERROR.getErrorCode(), + HttpErrorCode.INTERNAL_SERVER_ERROR.getErrorMessage())); } } From 143eab762d3f7840726e34f146d896696740fb5c Mon Sep 17 00:00:00 2001 From: west_east Date: Tue, 22 Jul 2025 16:51:28 +0900 Subject: [PATCH 3/4] =?UTF-8?q?[#19]=20test:=20=EC=9D=BC=EC=A0=95=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../crew/repository/JoinCrewRepository.java | 15 +- .../domain/event/service/EventService.java | 17 +- .../event/service/EventServiceTest.java | 221 ++++++++++++++++-- 3 files changed, 223 insertions(+), 30 deletions(-) diff --git a/src/main/java/run/backend/domain/crew/repository/JoinCrewRepository.java b/src/main/java/run/backend/domain/crew/repository/JoinCrewRepository.java index 4655fd8..a971cc8 100644 --- a/src/main/java/run/backend/domain/crew/repository/JoinCrewRepository.java +++ b/src/main/java/run/backend/domain/crew/repository/JoinCrewRepository.java @@ -46,9 +46,16 @@ Optional validateEventCreation( @Param("status") JoinStatus status ); - boolean existsByMemberIdAndCrewIdAndJoinStatus( - Long memberId, - Long crewId, - JoinStatus joinStatus + @Query(""" + SELECT captainJoin.member + FROM JoinCrew captainJoin + WHERE captainJoin.member.id = :runningCaptainId + AND captainJoin.crew.id = :crewId + AND captainJoin.joinStatus = :status + """) + Optional findCrewMemberById( + @Param("runningCaptainId") Long runningCaptainId, + @Param("crewId") Long crewId, + @Param("status") JoinStatus status ); } diff --git a/src/main/java/run/backend/domain/event/service/EventService.java b/src/main/java/run/backend/domain/event/service/EventService.java index 30afcfd..6c5ffbb 100644 --- a/src/main/java/run/backend/domain/event/service/EventService.java +++ b/src/main/java/run/backend/domain/event/service/EventService.java @@ -20,8 +20,6 @@ import run.backend.domain.event.repository.JoinEventRepository; import run.backend.domain.event.repository.PeriodicEventRepository; import run.backend.domain.member.entity.Member; -import run.backend.domain.member.exception.MemberException.MemberNotFound; -import run.backend.domain.member.repository.MemberRepository; import run.backend.global.annotation.global.Logging; @Service @@ -33,7 +31,6 @@ public class EventService { private final PeriodicEventRepository periodicEventRepository; private final JoinCrewRepository joinCrewRepository; private final JoinEventRepository joinEventRepository; - private final MemberRepository memberRepository; private final EventMapper eventMapper; @Transactional @@ -99,19 +96,13 @@ private Member validateNewRunningCaptain(EventInfoRequest request, Event event) return null; } - boolean isCrewMember = joinCrewRepository - .existsByMemberIdAndCrewIdAndJoinStatus( + return joinCrewRepository + .findCrewMemberById( request.runningCaptainId(), event.getCrew().getId(), JoinStatus.APPROVED - ); - - if (!isCrewMember) { - throw new InvalidEventCreationRequest(); - } - - return memberRepository.findById(request.runningCaptainId()) - .orElseThrow(MemberNotFound::new); + ) + .orElseThrow(InvalidEventCreationRequest::new); } private void updateRunningCaptain(Event event, Member newRunningCaptain) { diff --git a/src/test/java/run/backend/domain/event/service/EventServiceTest.java b/src/test/java/run/backend/domain/event/service/EventServiceTest.java index 9bc20c6..6b2ec09 100644 --- a/src/test/java/run/backend/domain/event/service/EventServiceTest.java +++ b/src/test/java/run/backend/domain/event/service/EventServiceTest.java @@ -1,5 +1,6 @@ package run.backend.domain.event.service; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; @@ -19,7 +20,9 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import org.springframework.test.util.ReflectionTestUtils; 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; @@ -28,6 +31,7 @@ import run.backend.domain.event.entity.PeriodicEvent; import run.backend.domain.event.enums.RepeatCycle; import run.backend.domain.event.enums.WeekDay; +import run.backend.domain.event.exception.EventException.EventNotFound; import run.backend.domain.event.exception.EventException.InvalidEventCreationRequest; import run.backend.domain.event.mapper.EventMapper; import run.backend.domain.event.repository.EventRepository; @@ -69,8 +73,8 @@ class EventServiceTest { @BeforeEach void setUp() { - requestMember = createMember("μš”μ²­μž"); - runningCaptain = createMember("λŸ¬λ‹μΊ‘ν‹΄"); + requestMember = createMemberWithId(1L, "μš”μ²­μž"); + runningCaptain = createMemberWithId(2L, "λŸ¬λ‹μΊ‘ν‹΄"); crew = createCrew("ν…ŒμŠ€νŠΈν¬λ£¨"); savedEvent = createEvent(); savedJoinEvent = createJoinEvent(); @@ -86,12 +90,14 @@ class CreateEventTest { void shouldCreateSingleEventSuccessfully() { // given EventInfoRequest request = createSingleEventRequest(); - EventCreationValidationDto validation = new EventCreationValidationDto(crew, runningCaptain); + EventCreationValidationDto validation = new EventCreationValidationDto(crew, + runningCaptain); given(joinCrewRepository.validateEventCreation(any(), any(), any())) .willReturn(Optional.of(validation)); - given(eventMapper.toEvent(any(EventInfoRequest.class), any(Crew.class), any(Member.class))) + given(eventMapper.toEvent(any(EventInfoRequest.class), any(Crew.class), + any(Member.class))) .willReturn(savedEvent); given(eventMapper.toJoinEvent(any(Event.class), any(Member.class))) @@ -117,15 +123,18 @@ void shouldCreateSingleEventSuccessfully() { void shouldCreatePeriodicEventSuccessfully() { // given EventInfoRequest request = createPeriodicEventRequest(); - EventCreationValidationDto validation = new EventCreationValidationDto(crew, runningCaptain); + EventCreationValidationDto validation = new EventCreationValidationDto(crew, + runningCaptain); given(joinCrewRepository.validateEventCreation(any(), any(), any())) .willReturn(Optional.of(validation)); - given(eventMapper.toPeriodicEvent(any(EventInfoRequest.class), any(Crew.class), any(Member.class))) + given(eventMapper.toPeriodicEvent(any(EventInfoRequest.class), any(Crew.class), + any(Member.class))) .willReturn(savedPeriodicEvent); - given(eventMapper.toEvent(any(EventInfoRequest.class), any(Crew.class), any(Member.class))) + given(eventMapper.toEvent(any(EventInfoRequest.class), any(Crew.class), + any(Member.class))) .willReturn(savedEvent); given(eventMapper.toJoinEvent(any(Event.class), any(Member.class))) @@ -172,12 +181,14 @@ void shouldThrowExceptionWhenValidationFails() { void shouldAutoJoinRunningCaptainToEvent() { // given EventInfoRequest request = createSingleEventRequest(); - EventCreationValidationDto validation = new EventCreationValidationDto(crew, runningCaptain); + EventCreationValidationDto validation = new EventCreationValidationDto(crew, + runningCaptain); given(joinCrewRepository.validateEventCreation(any(), any(), any())) .willReturn(Optional.of(validation)); - given(eventMapper.toEvent(any(EventInfoRequest.class), any(Crew.class), any(Member.class))) + given(eventMapper.toEvent(any(EventInfoRequest.class), any(Crew.class), + any(Member.class))) .willReturn(savedEvent); given(eventMapper.toJoinEvent(any(Event.class), any(Member.class))) @@ -197,24 +208,195 @@ void shouldAutoJoinRunningCaptainToEvent() { } } - private Member createMember(String nickname) { - return Member.builder() + @Nested + @DisplayName("updateEvent λ©”μ„œλ“œλŠ”") + class UpdateEventTest { + + @Test + @DisplayName("κΈ°λ³Έ μ •λ³΄λ§Œ μˆ˜μ •ν•  λ•Œ μ„±κ³΅ν•œλ‹€") + void shouldUpdateBasicInfoSuccessfully() { + // given + EventInfoRequest request = createUpdateEventRequest(null, RepeatCycle.NONE, null, "λ³€κ²½λœ 제λͺ©"); + + given(eventRepository.findById(1L)).willReturn(Optional.of(savedEvent)); + given(periodicEventRepository.findByCrewAndTitleAndTime(any(), any(), any(), any())) + .willReturn(Optional.empty()); + + // when + sut.updateEvent(1L, request, requestMember); + + // then + then(eventRepository).should().findById(1L); + then(joinEventRepository).should(never()).deleteByEventAndMember(any(), any()); + } + + @Test + @DisplayName("이벀트 ν•„λ“œκ°€ μ‹€μ œλ‘œ μ—…λ°μ΄νŠΈλ˜λŠ”μ§€ ν™•μΈν•œλ‹€") + void shouldActuallyUpdateEventFields() { + // given + EventInfoRequest request = new EventInfoRequest( + "λ³€κ²½λœ 제λͺ©", + LocalDate.of(2025, 7, 20), + RepeatCycle.NONE, + null, + LocalTime.of(14, 0), + LocalTime.of(15, 0), + "λ³€κ²½λœ μž₯μ†Œ", + null + ); + + given(eventRepository.findById(1L)).willReturn(Optional.of(savedEvent)); + given(periodicEventRepository.findByCrewAndTitleAndTime(any(), any(), any(), any())) + .willReturn(Optional.empty()); + + // when + sut.updateEvent(1L, request, requestMember); + + // then + assertThat(savedEvent.getTitle()).isEqualTo("λ³€κ²½λœ 제λͺ©"); + assertThat(savedEvent.getDate()).isEqualTo(LocalDate.of(2025, 7, 20)); + assertThat(savedEvent.getStartTime()).isEqualTo(LocalTime.of(14, 0)); + assertThat(savedEvent.getEndTime()).isEqualTo(LocalTime.of(15, 0)); + assertThat(savedEvent.getPlace()).isEqualTo("λ³€κ²½λœ μž₯μ†Œ"); + } + + @Test + @DisplayName("λŸ¬λ‹μΊ‘ν‹΄ λ³€κ²½ μ‹œ JoinEventλ₯Ό κ΅μ²΄ν•œλ‹€") + void shouldChangeRunningCaptainSuccessfully() { + // given + Member newRunningCaptain = createMemberWithId(3L, "μƒˆλŸ¬λ‹μΊ‘ν‹΄"); + + EventInfoRequest request = createUpdateEventRequest(3L, RepeatCycle.NONE, null, "λ³€κ²½λœ 제λͺ©"); + + given(eventRepository.findById(1L)).willReturn(Optional.of(savedEvent)); + given(joinCrewRepository.findCrewMemberById(3L, crew.getId(), JoinStatus.APPROVED)) + .willReturn(Optional.of(newRunningCaptain)); + given(eventMapper.toJoinEvent(savedEvent, newRunningCaptain)).willReturn( + savedJoinEvent); + given(periodicEventRepository.findByCrewAndTitleAndTime(any(), any(), any(), any())) + .willReturn(Optional.empty()); + + // when + sut.updateEvent(1L, request, requestMember); + + // then + then(joinEventRepository).should().deleteByEventAndMember(savedEvent, runningCaptain); + then(joinEventRepository).should().save(any(JoinEvent.class)); + } + + @Test + @DisplayName("λŸ¬λ‹μΊ‘ν‹΄μ΄ μ‹€μ œλ‘œ λ³€κ²½λ˜λŠ”μ§€ ν™•μΈν•œλ‹€") + void shouldActuallyChangeRunningCaptain() { + // given + Member newCaptain = createMemberWithId(3L, "μƒˆ λŸ¬λ‹μΊ‘ν‹΄"); + + EventInfoRequest request = createUpdateEventRequest(3L, RepeatCycle.NONE, null, "λ³€κ²½λœ 제λͺ©"); + + given(eventRepository.findById(1L)).willReturn(Optional.of(savedEvent)); + given(joinCrewRepository.findCrewMemberById(3L, crew.getId(), JoinStatus.APPROVED)) + .willReturn(Optional.of(newCaptain)); + given(eventMapper.toJoinEvent(savedEvent, newCaptain)).willReturn(savedJoinEvent); + given(periodicEventRepository.findByCrewAndTitleAndTime(any(), any(), any(), any())) + .willReturn(Optional.empty()); + + // when + sut.updateEvent(1L, request, requestMember); + + // then + assertThat(savedEvent.getMember()).isEqualTo(newCaptain); + } + + @Test + @DisplayName("반볡 섀정을 μΆ”κ°€ν•  λ•Œ PeriodicEventλ₯Ό μƒμ„±ν•œλ‹€") + void shouldAddPeriodicEventSuccessfully() { + // given + EventInfoRequest request = createUpdateEventRequest(null, RepeatCycle.WEEKLY, WeekDay.TUESDAY, "반볡 μΌμ •μœΌλ‘œ λ³€κ²½"); + EventInfoRequest mappedRequest = createPeriodicEventRequest(); + + given(eventRepository.findById(1L)).willReturn(Optional.of(savedEvent)); + given(periodicEventRepository.findByCrewAndTitleAndTime(any(), any(), any(), any())) + .willReturn(Optional.empty()); + given(eventMapper.toEventInfoRequest(request, savedEvent)).willReturn(mappedRequest); + given(eventMapper.toPeriodicEvent(mappedRequest, crew, runningCaptain)).willReturn( + savedPeriodicEvent); + + // when + sut.updateEvent(1L, request, requestMember); + + // then + then(periodicEventRepository).should().save(any(PeriodicEvent.class)); + } + + @Test + @DisplayName("반볡 섀정을 μ œκ±°ν•  λ•Œ κΈ°μ‘΄ PeriodicEventλ₯Ό μ‚­μ œν•œλ‹€") + void shouldRemovePeriodicEventSuccessfully() { + // given + EventInfoRequest request = createUpdateEventRequest(null, RepeatCycle.NONE, null, "반볡 제거"); + + given(eventRepository.findById(1L)).willReturn(Optional.of(savedEvent)); + given(periodicEventRepository.findByCrewAndTitleAndTime(any(), any(), any(), any())) + .willReturn(Optional.of(savedPeriodicEvent)); + + // when + sut.updateEvent(1L, request, requestMember); + + // then + then(periodicEventRepository).should().delete(savedPeriodicEvent); + } + + @Test + @DisplayName("μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” 일정 μˆ˜μ • μ‹œ EventNotFound μ˜ˆμ™Έλ₯Ό λ°œμƒμ‹œν‚¨λ‹€") + void shouldThrowEventNotFoundWhenEventDoesNotExist() { + // given + EventInfoRequest request = createUpdateEventRequest(null, RepeatCycle.NONE, null, "λ³€κ²½λœ 제λͺ©"); + + given(eventRepository.findById(1L)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> sut.updateEvent(1L, request, requestMember)) + .isInstanceOf(EventNotFound.class); + } + + @Test + @DisplayName("μƒˆλ‘œμš΄ λŸ¬λ‹μΊ‘ν‹΄μ΄ 크루원이 아닐 λ•Œ InvalidEventCreationRequest μ˜ˆμ™Έλ₯Ό λ°œμƒμ‹œν‚¨λ‹€") + void shouldThrowExceptionWhenNewRunningCaptainIsNotCrewMember() { + // given + EventInfoRequest request = createUpdateEventRequest(3L, RepeatCycle.NONE, null, "λ³€κ²½λœ 제λͺ©"); + + given(eventRepository.findById(1L)).willReturn(Optional.of(savedEvent)); + given(joinCrewRepository.findCrewMemberById(3L, crew.getId(), JoinStatus.APPROVED)) + .willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> sut.updateEvent(1L, request, requestMember)) + .isInstanceOf(InvalidEventCreationRequest.class); + } + } + + private Member createMemberWithId(Long id, String nickname) { + Member member = Member.builder() .username("test_user") .nickname(nickname) .gender(Gender.MALE) .age(25) - .oauthId("oauth_id") + .oauthId("oauth_id_" + id) .oauthType(OAuthType.GOOGLE) .profileImage("profile.jpg") .build(); + + ReflectionTestUtils.setField(member, "id", id); + return member; } private Crew createCrew(String name) { - return Crew.builder() + Crew crew = Crew.builder() .name(name) .description("ν…ŒμŠ€νŠΈ 크루 μ„€λͺ…") .image("crew.jpg") .build(); + + ReflectionTestUtils.setField(crew, "id", 1L); + return crew; } private Event createEvent() { @@ -271,4 +453,17 @@ private EventInfoRequest createPeriodicEventRequest() { 1L ); } + + private EventInfoRequest createUpdateEventRequest(Long runningCaptainId, RepeatCycle repeatCycle, WeekDay weekDay, String title) { + return new EventInfoRequest( + title, + LocalDate.of(2025, 7, 19), + repeatCycle, + weekDay, + LocalTime.of(10, 0), + LocalTime.of(11, 0), + "μž₯μ†Œ", + runningCaptainId + ); + } } \ No newline at end of file From 879d381f890d0a398ab390e7b861f409ed1b1107 Mon Sep 17 00:00:00 2001 From: west_east Date: Tue, 22 Jul 2025 17:53:28 +0900 Subject: [PATCH 4/4] =?UTF-8?q?[#19]=20chore:=20=EB=8D=94=EB=AF=B8=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=83=9D=EC=84=B1=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=BD=ED=8A=B8=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- scripts/run_simple_dummy.sh | 61 ------------------------------- scripts/simple_dummy_data.sql | 67 ----------------------------------- 2 files changed, 128 deletions(-) delete mode 100755 scripts/run_simple_dummy.sh delete mode 100644 scripts/simple_dummy_data.sql diff --git a/scripts/run_simple_dummy.sh b/scripts/run_simple_dummy.sh deleted file mode 100755 index a7fa27a..0000000 --- a/scripts/run_simple_dummy.sh +++ /dev/null @@ -1,61 +0,0 @@ -#!/bin/bash - -# μ„€μ • -CONTAINER_NAME="runners_db" -DB_NAME="runners" -DB_USER="root" -DB_PASSWORD="runners1234!" - -echo "πŸš€ κ°„λ‹¨ν•œ 더미데이터 생성 μ‹œμž‘..." - -# Docker μ»¨ν…Œμ΄λ„ˆ μƒνƒœ 확인 -if ! docker ps | grep -q $CONTAINER_NAME; then - echo "❌ $CONTAINER_NAME μ»¨ν…Œμ΄λ„ˆκ°€ μ‹€ν–‰λ˜μ§€ μ•Šμ•˜μŠ΅λ‹ˆλ‹€." - exit 1 -fi - -# λ°μ΄ν„°λ² μ΄μŠ€ μ—°κ²° ν…ŒμŠ€νŠΈ -if ! docker exec $CONTAINER_NAME mysql -u$DB_USER -p$DB_PASSWORD -e "USE $DB_NAME;" 2>/dev/null; then - echo "❌ λ°μ΄ν„°λ² μ΄μŠ€ 연결에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€." - exit 1 -fi - -echo "βœ… λ°μ΄ν„°λ² μ΄μŠ€ μ—°κ²° 성곡" - -# SQL μ‹€ν–‰ -echo "πŸ“ 더미데이터 생성 쀑..." -docker exec -i $CONTAINER_NAME mysql -u$DB_USER -p$DB_PASSWORD $DB_NAME < scripts/simple_dummy_data.sql - -if [ $? -eq 0 ]; then - echo "βœ… 더미데이터 생성 μ™„λ£Œ!" -else - echo "❌ 더미데이터 생성 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€." - exit 1 -fi - -# μƒμ„±λœ 데이터 확인 -echo "πŸ“Š μƒμ„±λœ 데이터 톡계:" -docker exec $CONTAINER_NAME mysql -u$DB_USER -p$DB_PASSWORD $DB_NAME -e " -SELECT 'Members' as Category, COUNT(*) as Total FROM members; -SELECT 'Crews' as Category, COUNT(*) as Total FROM crews; -SELECT 'JoinCrews' as Category, COUNT(*) as Total FROM join_crews; -" - -echo "πŸ“ 크루별 멀버 수 확인:" -docker exec $CONTAINER_NAME mysql -u$DB_USER -p$DB_PASSWORD $DB_NAME -e " -SELECT - c.name as crew_name, - COUNT(jc.member_id) as member_count, - COUNT(CASE WHEN jc.crew_role = 'MANAGER' THEN 1 END) as managers, - COUNT(CASE WHEN jc.crew_role = 'LEADER' THEN 1 END) as leaders -FROM crews c -LEFT JOIN join_crews jc ON c.id = jc.crew_id -WHERE c.name LIKE '크루_%' -GROUP BY c.id, c.name -ORDER BY c.name; -" - -echo "πŸŽ‰ 더미데이터 생성 μ™„λ£Œ!" -echo "πŸ’‘ ν…ŒμŠ€νŠΈμš© ID μ˜ˆμ‹œ:" -echo " 크루1 멀버: ID 4-2003 λ²”μœ„" -echo " 크루2 멀버: ID 2004-4003 λ²”μœ„" diff --git a/scripts/simple_dummy_data.sql b/scripts/simple_dummy_data.sql deleted file mode 100644 index b795009..0000000 --- a/scripts/simple_dummy_data.sql +++ /dev/null @@ -1,67 +0,0 @@ --- κ°„λ‹¨ν•œ 더미데이터 생성 (크루 5개, 멀버 10,000λͺ…) -SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; - --- 1. 크루 5개 생성 -INSERT INTO crews (name, description, image, invite_code, member_count, monthly_distance_total, monthly_time_total, monthly_score_total, created_at, updated_at) VALUES -('크루_001', '첫 번째 λŸ¬λ‹ ν¬λ£¨μž…λ‹ˆλ‹€', 'crew1.jpg', 'INVITE_001', 2000, 1000.50, 50000, 500.25, NOW(), NOW()), -('크루_002', '두 번째 λŸ¬λ‹ ν¬λ£¨μž…λ‹ˆλ‹€', 'crew2.jpg', 'INVITE_002', 2000, 1200.75, 60000, 600.50, NOW(), NOW()), -('크루_003', 'μ„Έ 번째 λŸ¬λ‹ ν¬λ£¨μž…λ‹ˆλ‹€', 'crew3.jpg', 'INVITE_003', 2000, 1100.25, 55000, 550.75, NOW(), NOW()), -('크루_004', 'λ„€ 번째 λŸ¬λ‹ ν¬λ£¨μž…λ‹ˆλ‹€', 'crew4.jpg', 'INVITE_004', 2000, 1300.00, 65000, 650.00, NOW(), NOW()), -('크루_005', 'λ‹€μ„― 번째 λŸ¬λ‹ ν¬λ£¨μž…λ‹ˆλ‹€', 'crew5.jpg', 'INVITE_005', 2000, 1150.80, 57500, 575.60, NOW(), NOW()); - --- 2. 멀버 10,000λͺ… 생성 -INSERT INTO members (username, nickname, gender, age, oauth_id, oauth_type, role, profile_image, push_enabled, created_at, updated_at) -SELECT - CONCAT('user_', LPAD(seq, 6, '0')) as username, - CONCAT('μœ μ €_', seq) as nickname, - CASE WHEN seq % 2 = 0 THEN 'MALE' ELSE 'FEMALE' END as gender, - 18 + (seq % 42) as age, - CONCAT('oauth_', seq) as oauth_id, - 'GOOGLE' as oauth_type, - CASE - WHEN seq % 100 = 1 THEN 'MANAGER' - WHEN seq % 200 = 1 THEN 'LEADER' - ELSE 'MEMBER' - END as role, - CONCAT('profile_', (seq % 20) + 1, '.jpg') as profile_image, - true as push_enabled, - NOW() as created_at, - NOW() as updated_at -FROM ( - SELECT (@row_number := @row_number + 1) AS seq - FROM information_schema.columns c1 - CROSS JOIN information_schema.columns c2 - CROSS JOIN (SELECT @row_number := 0) r - LIMIT 10000 -) AS numbers; - --- 3. 크루 κ°€μž… 관계 생성 --- 1~2000번: 1번 크루 --- 2001~4000번: 2번 크루 --- 4001~6000번: 3번 크루 --- 6001~8000번: 4번 크루 --- 8001~10000번: 5번 크루 - -INSERT INTO join_crews (join_status, crew_role, joined_date, member_id, crew_id, created_at, updated_at) -SELECT - 'APPROVED' as join_status, - CASE - WHEN m.id % 100 = 1 THEN 'MANAGER' - WHEN m.id % 200 = 1 THEN 'LEADER' - ELSE 'MEMBER' - END as crew_role, - DATE_SUB(CURDATE(), INTERVAL FLOOR(RAND() * 365) DAY) as joined_date, - m.id as member_id, - CASE - WHEN m.id <= (SELECT MIN(id) + 1999 FROM members WHERE username LIKE 'user_%') THEN (SELECT MIN(id) FROM crews WHERE name LIKE '크루_%') + 0 - WHEN m.id <= (SELECT MIN(id) + 3999 FROM members WHERE username LIKE 'user_%') THEN (SELECT MIN(id) FROM crews WHERE name LIKE '크루_%') + 1 - WHEN m.id <= (SELECT MIN(id) + 5999 FROM members WHERE username LIKE 'user_%') THEN (SELECT MIN(id) FROM crews WHERE name LIKE '크루_%') + 2 - WHEN m.id <= (SELECT MIN(id) + 7999 FROM members WHERE username LIKE 'user_%') THEN (SELECT MIN(id) FROM crews WHERE name LIKE '크루_%') + 3 - ELSE (SELECT MIN(id) FROM crews WHERE name LIKE '크루_%') + 4 - END as crew_id, - NOW() as created_at, - NOW() as updated_at -FROM members m -WHERE m.username LIKE 'user_%'; - -SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;