Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
be4d504
[#20] feat: crew 필요한 업데이트 함수 추가
choiseoji Jul 19, 2025
79e04c5
[#20] feat: crewInfoRequestDto 추가
choiseoji Jul 19, 2025
5017859
[#20] feat: crewProfileResponse DTO 추가
choiseoji Jul 19, 2025
f974bdc
[#20] fix: 리더가 join한 경우, 일반 멤버가 Join한 경우 메서드 분리해서 joinCrew 생성하도록 수정
choiseoji Jul 19, 2025
05e77cb
[#20] feat: member role 수정하는 메서드 추가
choiseoji Jul 19, 2025
aaf2377
[#20] feat: 크루 생성, 크루 정보 수정, 크루 초대 코드 조회, 코드로 크루 조회, 크루 가입 컨트롤러 추가
choiseoji Jul 19, 2025
161f21a
[#20] feat: 크루 Exception 추가
choiseoji Jul 19, 2025
cbb4c54
[#20] feat: crewInviteCodeDTO 추가
choiseoji Jul 19, 2025
577afbc
[#20] feat: crew, joinCrew 레포지토리 코드 추가
choiseoji Jul 19, 2025
d61c7bb
[#20] feat: crewService 코드 추가
choiseoji Jul 19, 2025
987a975
[#20] feat: crew에 필요한 인터페이스 추가
choiseoji Jul 19, 2025
02a032c
Merge branch 'develop' into feat/20-crew
choiseoji Jul 19, 2025
4d1b1e5
[#20] chore: ddl-auto update로 변경
choiseoji Jul 19, 2025
63e673d
[#20] feat: MemberCrew 어노테이션 적용
choiseoji Jul 19, 2025
66229bf
[#20] feat: dev 코드랑 머지
choiseoji Jul 19, 2025
517be23
[#20] feat: 예외 클래스 전역 예외 처리기에 추가
choiseoji Jul 19, 2025
b6fec8d
[#20] test: 서비스 테스트 코드 추가 (크루 생성, 크루 수정, 크루 가입 요청)
choiseoji Jul 19, 2025
a86a95f
[#20] refact: Operation 위치 수정
choiseoji Jul 19, 2025
e91602c
[#20] feat: 크루 정보 수정 API에 권한 추가
choiseoji Jul 19, 2025
bfb2e19
[#20] feat: SecurityConfig에 crews api 허용 경로에 추가
choiseoji Jul 19, 2025
9108a20
[#20] refact: CrewProfileResponse 내부에서 생성하도록 수정
choiseoji Jul 19, 2025
672e0ae
[#20] refact: CrewProfileResponse 내부에서 생성하도록 수정
choiseoji Jul 19, 2025
ab9ec57
[#20] test: 테스트 코드 제목 추가
choiseoji Jul 19, 2025
a45d32a
[#20] test: Crew, JoinCrew 도메인 테스트 추가
choiseoji Jul 19, 2025
dd4291b
[#20] feat: CrewBaseInfoResponse DTO 추가
choiseoji Jul 20, 2025
c5f6bb0
[#20] delete: 필요없는 DTO 삭제
choiseoji Jul 20, 2025
7777e8c
[#20] feat: 기간별 일정 조회하는 DTO 추가
choiseoji Jul 20, 2025
464870e
[#20] feat: 기간별 일정 조회하는 레포지토리 코드 추가
choiseoji Jul 20, 2025
3f5848e
[#20] delete: dto 내에 of 함수 삭제
choiseoji Jul 20, 2025
759ac96
[#20] refact: crewServiceImpl -> crewService 이름 변경
choiseoji Jul 20, 2025
65377d0
[#20] refact: crewServiceImpl -> crewService 이름 변경
choiseoji Jul 20, 2025
2537b77
[#20] feat: 크루 정보 조회하는 API 추가
choiseoji Jul 20, 2025
16c7326
[#20] feat: crew, event 매퍼 클래스 추가
choiseoji Jul 20, 2025
c6046b7
[#20] feat: date range 계산하는 유틸 추가
choiseoji Jul 20, 2025
82924d9
[#20] feat: week, month, upcoming 일정 조회하는 서비스 코드 추가
choiseoji Jul 20, 2025
356e21c
[#20] test: CrewEventService 테스트코드 작성
choiseoji Jul 20, 2025
851cab0
[#20] delete: 필요없는 import문 삭제
choiseoji Jul 20, 2025
8bcd1f3
[#20] test: EventStatusMapper 테스트코드 작성
choiseoji Jul 20, 2025
a5de59a
[#20] refact: fileService로 사진 처리 로직 이동
choiseoji Jul 20, 2025
206aab6
[#20] feat: CrewMapper에 toEntity 추가
choiseoji Jul 20, 2025
664d23c
[#20] test: CrewServiceTest 테스트 코드 작성
choiseoji Jul 20, 2025
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
123 changes: 123 additions & 0 deletions src/main/java/run/backend/domain/crew/controller/CrewController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package run.backend.domain.crew.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.*;
import org.springframework.web.multipart.MultipartFile;
import run.backend.domain.crew.dto.common.CrewInviteCodeDto;
import run.backend.domain.crew.dto.request.CrewInfoRequest;
import run.backend.domain.crew.dto.response.*;
import run.backend.domain.crew.entity.Crew;
import run.backend.domain.crew.service.CrewEventService;
import run.backend.domain.crew.service.CrewService;
import run.backend.domain.member.entity.Member;
import run.backend.global.annotation.member.Login;
import run.backend.global.annotation.member.MemberCrew;
import run.backend.global.common.response.CommonResponse;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/crews")
@Tag(name = "Crews", description = "Crew 관련 API")
public class CrewController {

private final CrewService crewService;
private final CrewEventService crewEventService;

@PostMapping
@Operation(summary = "크루 생성", description = "크루 생성하는 API 입니다.")
public CommonResponse<CrewInviteCodeDto> createCrew(
@Login Member member,
@RequestParam String imageStatus,
@RequestPart(value = "data")CrewInfoRequest data,
@RequestPart(value = "image", required = false) MultipartFile image
) {

CrewInviteCodeDto response = crewService.createCrew(member, imageStatus, image, data);
return new CommonResponse<>("크루 생성 성공", response);
}

@PatchMapping
@PreAuthorize("hasRole('MANAGER') or hasRole('LEADER')")
@Operation(summary = "크루 정보 수정", description = "크루 정보 수정하는 API 입니다.")
public CommonResponse<Void> updateCrewInfo(
@MemberCrew Crew crew,
@RequestParam String imageStatus,
@RequestPart(value = "data")CrewInfoRequest data,
@RequestPart(value = "image", required = false) MultipartFile image
) {

crewService.updateCrew(crew, imageStatus, image, data);
return new CommonResponse<>("크루 정보 수정 성공");
}

@GetMapping("/{crewId}/invite-code")
@Operation(summary = "크루의 초대 코드 조회", description = "크루의 초대 코드 조회하는 API 입니다.")
public CommonResponse<CrewInviteCodeDto> getCrewInviteCode(@MemberCrew Crew crew) {

CrewInviteCodeDto response = crewService.getCrewInviteCode(crew);
return new CommonResponse<>("크루 초대 코드 조회 성공", response);
}

@GetMapping("/invite-code/{inviteCode}")
@Operation(summary = "초대 코드로 크루 조회", description = "초대 코드로 크루를 조회하는 API 입니다.")
public CommonResponse<CrewProfileResponse> getCrewByInviteCode(@PathVariable String inviteCode) {

CrewProfileResponse response = crewService.getCrewByInviteCode(inviteCode);
return new CommonResponse<>("크루 조회 성공", response);
}

@PostMapping("/{crewId}/join")
@Operation(summary = "크루 가입", description = "크루 가입하는 API 입니다.")
public CommonResponse<Void> joinCrew(
@Login Member member,
@PathVariable Long crewId) {

crewService.joinCrew(member, crewId);
return new CommonResponse<>("크루 가입 성공");
}

@GetMapping
@Operation(summary = "크루 기본 정보 조회", description = "크루 기본 정보를 조회하는 API 입니다.")
public CommonResponse<CrewBaseInfoResponse> getCrewBaseInfo(@MemberCrew Crew crew) {

CrewBaseInfoResponse response = crewService.getCrewBaseInfo(crew);
return new CommonResponse<>("크루 기본 정보 조회 성공", response);
}

@GetMapping("/events/weekly")
@Operation(summary = "weekly 기록 조회", description = "크루의 weekly 기록 조회하는 API 입니다.")
public CommonResponse<CrewWeeklyEventResponse> getWeeklyRecord(@MemberCrew Crew crew) {

CrewWeeklyEventResponse response = crewEventService.getCrewWeeklyEvent(crew);
return new CommonResponse<>("크루 주간 기록 조회 성공", response);
}

@GetMapping("/events/monthly")
@Operation(summary = "monthly 일정 조회", description = "크루의 monthly 일정 조회하는 API 입니다.")
public CommonResponse<CrewMonthlyCanlendarResponse> getMonthlyEvent(
@MemberCrew Crew crew,
@RequestParam int year,
@RequestParam int month) {

CrewMonthlyCanlendarResponse response = crewEventService.getCrewMonthlyCalendar(crew, year, month);
return new CommonResponse<>("크루 월간 기록 조회 성공", response);
}

@GetMapping("/events/upcoming")
@Operation(summary = "upcoming 일정 조회", description = "크루의 upcoming 일정 조회하는 API 입니다.")
public CommonResponse<CrewUpcomingEventResponse> getUpcomingEvent(@MemberCrew Crew crew) {

CrewUpcomingEventResponse response = crewEventService.getCrewUpcomingEvent(crew);
return new CommonResponse<>("크루 다가오는 일정 조회 성공", response);
}

@GetMapping("/members")
@Operation(summary = "크루원 조회", description = "크루 내 모든 크루원을 조회하는 API 입니다.")
public CommonResponse<Void> getCrewMember(@MemberCrew Crew crew) {

return new CommonResponse<>("크루원 조회 성공");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package run.backend.domain.crew.dto.common;

public record CrewInviteCodeDto(
String inviteCode
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package run.backend.domain.crew.dto.common;

import run.backend.domain.event.enums.RunningStatus;

public record DayStatusDto(
RunningStatus status,
Long eventId
) {
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
package run.backend.domain.crew.dto.request;

public record CrewInfoRequest() {
public record CrewInfoRequest(
String name,
String description
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package run.backend.domain.crew.dto.response;

import lombok.Builder;

import java.math.BigDecimal;

@Builder
public record CrewBaseInfoResponse(

int rank,
String image,
String name,
String description,
Long memberCount,
BigDecimal monthlyDistanceTotal,
Long monthlyTimeTotal

) {
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package run.backend.domain.crew.dto.response;

import java.util.List;
import run.backend.domain.crew.dto.common.DayStatusDto;
import java.util.Map;

public record CrewMonthlyCanlendarResponse(
List<CrewRecordResponse> records,
List<CrewEventResponse> events

Map<Integer, DayStatusDto> monthlyRunningStatus
) {
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
package run.backend.domain.crew.dto.response;

public record CrewProfileResponse() {
import lombok.Builder;

@Builder
public record CrewProfileResponse(
String crewImage,
String crewName,
String crewDescription,
Long memberCount,
String leaderImage,
String leaderName
) {
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package run.backend.domain.crew.dto.response;

import run.backend.domain.crew.dto.common.DayStatusDto;

import java.time.DayOfWeek;
import java.util.Map;

public record CrewWeeklyEventResponse(
int currentDay,
Map<DayOfWeek, DayStatusDto> weeklyRunningStatus

) {
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
package run.backend.domain.crew.dto.response;

public record EventProfileResponse() {
import lombok.Builder;

import java.time.LocalDate;
import java.time.LocalTime;

@Builder
public record EventProfileResponse(

Long eventId,
String title,
LocalDate date,
LocalTime startTime,
LocalTime endTime,
Long participants
) {
}
16 changes: 16 additions & 0 deletions src/main/java/run/backend/domain/crew/entity/Crew.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,22 @@ public class Crew extends BaseEntity {
@Column(name = "monthly_score_total")
private BigDecimal monthlyScoreTotal;

public void incrementMemberCount() {
this.memberCount++;
}

public void updateName(String newName) {
this.name = newName;
}

public void updateDescription(String newDescription) {
this.description = newDescription;
}

public void updateImage(String newImageName) {
this.image = newImageName;
}

@Builder
public Crew (
String name,
Expand Down
31 changes: 28 additions & 3 deletions src/main/java/run/backend/domain/crew/entity/JoinCrew.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,38 @@ public void approveJoin() {
this.joinStatus = JoinStatus.APPROVED;
}

public static JoinCrew createLeaderJoin(Member member, Crew crew) {
return JoinCrew.builder()
.crew(crew)
.member(member)
.role(Role.LEADER)
.joinStatus(JoinStatus.APPROVED)
.joinedDate(LocalDate.now())
.build();
}

public static JoinCrew createAppliedJoin(Member member, Crew crew) {
return JoinCrew.builder()
.crew(crew)
.member(member)
.role(Role.MEMBER)
.joinStatus(JoinStatus.APPLIED)
.build();
}

@Builder
public JoinCrew(
private JoinCrew(
Member member,
Crew crew
Crew crew,
Role role,
JoinStatus joinStatus,
LocalDate joinedDate

) {
this.crew = crew;
this.member = member;
this.joinStatus = JoinStatus.APPLIED;
this.role = role;
this.joinStatus = joinStatus;
this.joinedDate = joinedDate;
}
}
17 changes: 17 additions & 0 deletions src/main/java/run/backend/domain/crew/exception/CrewErrorCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package run.backend.domain.crew.exception;

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

@Getter
@AllArgsConstructor
public enum CrewErrorCode implements ErrorCode {

ALREADY_JOINED_CREW(7001, "이미 가입한 크루가 있습니다."),
NOT_FOUND_CREW(7002, "해당 크루를 찾을 수 없습니다.");


private final int errorCode;
private final String errorMessage;
}
22 changes: 22 additions & 0 deletions src/main/java/run/backend/domain/crew/exception/CrewException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package run.backend.domain.crew.exception;

import run.backend.global.exception.CustomException;

public class CrewException extends CustomException {

public CrewException(final CrewErrorCode crewErrorCode) {
super(crewErrorCode);
}

public static class AlreadyJoinedCrew extends CrewException {
public AlreadyJoinedCrew() {
super(CrewErrorCode.ALREADY_JOINED_CREW);
}
}

public static class NotFoundCrew extends CrewException {
public NotFoundCrew() {
super(CrewErrorCode.NOT_FOUND_CREW);
}
}
}
42 changes: 42 additions & 0 deletions src/main/java/run/backend/domain/crew/mapper/CrewMapper.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package run.backend.domain.crew.mapper;

import org.springframework.stereotype.Component;
import run.backend.domain.crew.dto.response.CrewBaseInfoResponse;
import run.backend.domain.crew.dto.response.CrewProfileResponse;
import run.backend.domain.crew.entity.Crew;
import run.backend.domain.member.entity.Member;

@Component
public class CrewMapper {

public Crew toEntity(String imageName, String name, String description) {
return Crew.builder()
.image(imageName)
.name(name)
.description(description)
.build();
}

public CrewProfileResponse toCrewProfile(Crew crew, Member leader) {
return CrewProfileResponse.builder()
.crewImage(crew.getImage())
.crewName(crew.getName())
.crewDescription(crew.getDescription())
.memberCount(crew.getMemberCount())
.leaderImage(leader.getProfileImage())
.leaderName(leader.getNickname())
.build();
}

public CrewBaseInfoResponse toCrewBaseInfo(int rank, Crew crew) {
return CrewBaseInfoResponse.builder()
.rank(rank)
.image(crew.getImage())
.name(crew.getName())
.description(crew.getDescription())
.memberCount(crew.getMemberCount())
.monthlyDistanceTotal(crew.getMonthlyDistanceTotal())
.monthlyTimeTotal(crew.getMonthlyTimeTotal())
.build();
}
}
Loading