Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f8eec0a
[#26] feat: 크루 여부 확인하는 response dto 추가
choiseoji Jul 24, 2025
bdc52a4
[#26] delete: memberService interface 삭제
choiseoji Jul 24, 2025
bdb32aa
[#26] feat: 가입된 크루 있는지 확인하는 코드 추가
choiseoji Jul 24, 2025
4935b33
[#26] refact: operation 위치 수정
choiseoji Jul 24, 2025
a8d606b
[#26] feat: 공통 페이지 dto 추가
choiseoji Jul 24, 2025
3ceaab4
[#26] feat: CrewRankingResponse dto 추가
choiseoji Jul 24, 2025
284906f
[#26] feat: 크루 랭킹 조회하는 로직 추가
choiseoji Jul 24, 2025
29e7e5c
[#26] chore: validation 의존성 추가
choiseoji Jul 24, 2025
51b30be
Merge branch 'develop' into feat/26-main
choiseoji Jul 24, 2025
f8ec813
[#26] feat: Crew에 빼앗은 km 추가
choiseoji Jul 30, 2025
43964cf
[#26] feat: 땅따먹기 현황 조회 api 추가
choiseoji Jul 30, 2025
8ec74cc
[#26] fix: CrewMapper map-struct 사용하도록 수정
choiseoji Jul 30, 2025
e43fb3b
[#26] fix: 한달 누적 km가 아닌 계산된 score km 반환하는 걸로 수정
choiseoji Jul 30, 2025
14a6a10
[#26] feat: mapper 적용, 땅따멱기 현황 조회 서비스 코드 추가
choiseoji Jul 30, 2025
738b4d8
[#26] feat: PageResponse에 to메서드 추가
choiseoji Jul 30, 2025
23a6b1c
[#26] feat: CrewRankingStatusResponse 추가
choiseoji Jul 30, 2025
39a2b87
[#26] feat: Querydsl config 추가
choiseoji Jul 30, 2025
f6a15af
[#26] fix: pageResponse 필드명 수정
choiseoji Jul 30, 2025
bbb68a7
[#26] feat: CrewRankingServiceTest 코드 작성
choiseoji Jul 30, 2025
8952c38
[#26] feat: CrewMapper 테스트 추가
choiseoji Jul 30, 2025
f14b190
[#26] feat: Crew 엔티티에 전체 생성자 추가
choiseoji Jul 30, 2025
466a675
[#26] delete: 필요없는 Import문 삭제
choiseoji Jul 30, 2025
00f9d3d
[#26] fix: updateMemberInfo에 file handle Image Update 적용
choiseoji Jul 30, 2025
a4315a0
[#26] test: 멤버 가입된 크루 체크 여부 확인해는 테스트 코드 작성
choiseoji Jul 30, 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
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ dependencies {
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation 'org.springframework.boot:spring-boot-starter-validation'

// mysql
runtimeOnly 'com.mysql:mysql-connector-j'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@
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.CrewRankingService;
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;
import run.backend.global.common.response.PageResponse;

@RestController
@RequiredArgsConstructor
Expand All @@ -25,6 +27,7 @@ public class CrewController {

private final CrewService crewService;
private final CrewEventService crewEventService;
private final CrewRankingService crewRankingService;

@PostMapping
@Operation(summary = "크루 생성", description = "크루 생성하는 API 입니다.")
Expand Down Expand Up @@ -114,10 +117,19 @@ public CommonResponse<CrewUpcomingEventResponse> getUpcomingEvent(@MemberCrew Cr
return new CommonResponse<>("크루 다가오는 일정 조회 성공", response);
}

@GetMapping("/members")
@Operation(summary = "크루원 조회", description = "크루 내 모든 크루원을 조회하는 API 입니다.")
public CommonResponse<Void> getCrewMember(@MemberCrew Crew crew) {
@GetMapping("/rankings")
public CommonResponse<PageResponse<CrewRankingResponse>> getCrewRankings(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "5") int size) {

return new CommonResponse<>("크루원 조회 성공");
PageResponse<CrewRankingResponse> response = crewRankingService.getCrewRanking(page, size);
return new CommonResponse<>("크루 랭킹 조회 성공", response);
}

@GetMapping("/rankings/status")
public CommonResponse<CrewRankingStatusResponse> getCrewRankingsStatus(@MemberCrew Crew crew) {

CrewRankingStatusResponse response = crewRankingService.getCrewRankingStatus(crew);
return new CommonResponse<>("크루 땅따먹기 현황 조회 성공", response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package run.backend.domain.crew.dto.response;

public record CrewRankingResponse(
Long crewId,
String name,
String image,
int monthlyScoreTotal
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package run.backend.domain.crew.dto.response;

public record CrewRankingStatusResponse(
int ranking,
int totalDistanceKm,
int capturedDistanceKm
) {
}
12 changes: 7 additions & 5 deletions src/main/java/run/backend/domain/crew/entity/Crew.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package run.backend.domain.crew.entity;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.*;
import run.backend.global.common.BaseEntity;

import java.math.BigDecimal;
Expand All @@ -13,6 +10,7 @@
@Entity
@Getter
@Table(name = "crews")
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Crew extends BaseEntity {

Expand All @@ -35,11 +33,14 @@ public class Crew extends BaseEntity {
@Column(name = "monthly_distance_total")
private BigDecimal monthlyDistanceTotal;

@Column(name = "captured_distance_total")
private BigDecimal capturedDistanceTotal;

@Column(name = "monthly_time_total")
private Long monthlyTimeTotal;

@Column(name = "monthly_score_total")
private BigDecimal monthlyScoreTotal;
private BigDecimal monthlyScoreTotal; // monthlyDistanceTotal(70%) + capturedDistanceTotal(30%)

public void incrementMemberCount() {
this.memberCount++;
Expand Down Expand Up @@ -69,6 +70,7 @@ public Crew (
this.inviteCode = UUID.randomUUID().toString();
this.memberCount = 1L;
this.monthlyDistanceTotal = BigDecimal.ZERO;
this.capturedDistanceTotal = BigDecimal.ZERO;
this.monthlyTimeTotal = 0L;
this.monthlyScoreTotal = BigDecimal.ZERO;
}
Expand Down
46 changes: 31 additions & 15 deletions src/main/java/run/backend/domain/crew/mapper/CrewMapper.java
Original file line number Diff line number Diff line change
@@ -1,23 +1,40 @@
package run.backend.domain.crew.mapper;

import org.springframework.stereotype.Component;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.ReportingPolicy;
import run.backend.domain.crew.dto.response.CrewBaseInfoResponse;
import run.backend.domain.crew.dto.response.CrewProfileResponse;
import run.backend.domain.crew.dto.response.CrewRankingResponse;
import run.backend.domain.crew.dto.response.CrewRankingStatusResponse;
import run.backend.domain.crew.entity.Crew;
import run.backend.domain.member.entity.Member;

@Component
public class CrewMapper {
import java.util.List;

public Crew toEntity(String imageName, String name, String description) {
@Mapper(
componentModel = "spring",
unmappedTargetPolicy = ReportingPolicy.IGNORE
)
public interface CrewMapper {

@Mapping(target = "rank", source = "rank")
CrewBaseInfoResponse toCrewBaseInfo(int rank, Crew crew);

@Mapping(target = "monthlyScoreTotal", expression = "java(crew.getMonthlyScoreTotal().intValue())")
CrewRankingResponse toCrewRankingResponse(Crew crew);

List<CrewRankingResponse> toCrewRankingResponseList(List<Crew> crews);

default 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) {
default CrewProfileResponse toCrewProfile(Crew crew, Member leader) {
return CrewProfileResponse.builder()
.crewImage(crew.getImage())
.crewName(crew.getName())
Expand All @@ -28,15 +45,14 @@ public CrewProfileResponse toCrewProfile(Crew crew, Member leader) {
.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();
default CrewRankingStatusResponse toCrewRankingStatusResponse(
int rank,
Crew crew
) {
return new CrewRankingStatusResponse(
rank,
crew.getMonthlyDistanceTotal().intValue(),
crew.getCapturedDistanceTotal().intValue()
);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package run.backend.domain.crew.repository;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import run.backend.domain.crew.entity.Crew;

Expand All @@ -8,4 +10,7 @@
public interface CrewRepository extends JpaRepository<Crew, Long> {

Optional<Crew> findByInviteCode(String inviteCode);


Page<Crew> findAllByOrderByMonthlyScoreTotalDesc(Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public interface JoinCrewRepository extends JpaRepository<JoinCrew, Long> {

boolean existsByMemberAndJoinStatus(Member member, JoinStatus joinStatus);

Optional<JoinCrew> findByMember(Member member);

@Query("""
SELECT jc.member
FROM JoinCrew jc
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package run.backend.domain.crew.service;

import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import run.backend.domain.crew.dto.response.CrewRankingResponse;
import run.backend.domain.crew.dto.response.CrewRankingStatusResponse;
import run.backend.domain.crew.entity.Crew;
import run.backend.domain.crew.mapper.CrewMapper;
import run.backend.domain.crew.repository.CrewRepository;
import run.backend.global.common.response.PageResponse;

import java.util.List;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class CrewRankingService {

private final CrewMapper crewMapper;
private final CrewRepository crewRepository;

public PageResponse<CrewRankingResponse> getCrewRanking(int page, int size) {

Page<Crew> pageResult = crewRepository.findAllByOrderByMonthlyScoreTotalDesc(PageRequest.of(page, size));
List<CrewRankingResponse> content = crewMapper.toCrewRankingResponseList(pageResult.getContent());

return PageResponse.toPageResponse(pageResult, content);
}

public CrewRankingStatusResponse getCrewRankingStatus(Crew crew) {

int rank = 0; // [TODO] : 스케줄링 rank 계산 구현 수정 예정

return crewMapper.toCrewRankingStatusResponse(rank, crew);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import run.backend.domain.member.dto.request.MemberInfoRequest;
import run.backend.domain.member.dto.response.MemberCrewStatusResponse;
import run.backend.domain.member.dto.response.MemberInfoResponse;
import run.backend.domain.member.entity.Member;
import run.backend.domain.member.service.MemberServiceImpl;
import run.backend.domain.member.service.MemberService;
import run.backend.global.annotation.member.Login;
import run.backend.global.common.response.CommonResponse;

Expand All @@ -18,18 +19,18 @@
@Tag(name = "Members", description = "Member 관련 API")
public class MemberController {

private final MemberServiceImpl memberService;
private final MemberService memberService;

@Operation(summary = "유저 정보 조회", description = "마이페이지 상단 유저 정보를 조회하는 API 입니다.")
@GetMapping
@Operation(summary = "유저 정보 조회", description = "마이페이지 상단 유저 정보를 조회하는 API 입니다.")
public CommonResponse<MemberInfoResponse> getMemberInfo(@Login Member member) {

MemberInfoResponse response = memberService.getMemberInfo(member);
return new CommonResponse<>("유저 정보 조회 성공", response);
}

@Operation(summary = "유저 정보 수정", description = "마이페이지에서 유저 정보를 수정하는 API 입니다.")
@PostMapping
@Operation(summary = "유저 정보 수정", description = "마이페이지에서 유저 정보를 수정하는 API 입니다.")
public CommonResponse<Void> updateMemberInfo(
@Login Member member,
@RequestParam String imageStatus,
Expand All @@ -39,4 +40,12 @@ public CommonResponse<Void> updateMemberInfo(
memberService.updateMemberInfo(member, imageStatus, image, data);
return new CommonResponse<>("유저 정보 수정 성공");
}

@GetMapping("/me/crews/exists")
@Operation(summary = "가입된 크루가 있는지 확인", description = "유저가 가입된 크루가 있는지 확인하는 API 입니다.")
public CommonResponse<MemberCrewStatusResponse> getMembersCrewExists(@Login Member member) {

MemberCrewStatusResponse response = memberService.getMembersCrewExists(member);
return new CommonResponse<>("유저 크루 가입 여부 조회 완료", response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package run.backend.domain.member.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;

public record MemberCrewStatusResponse(

@Schema(description = "유저의 크루 가입 상태 (NONE: 가입된 크루 없음, APPLIED: 가입 대기 중, APPROVED: 가입 완료")
String status
) {
}
59 changes: 50 additions & 9 deletions src/main/java/run/backend/domain/member/service/MemberService.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,62 @@
package run.backend.domain.member.service;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import run.backend.domain.crew.entity.Crew;
import run.backend.domain.crew.entity.JoinCrew;
import run.backend.domain.crew.enums.JoinStatus;
import run.backend.domain.crew.repository.JoinCrewRepository;
import run.backend.domain.file.service.FileService;
import run.backend.domain.member.dto.request.MemberInfoRequest;
import run.backend.domain.member.dto.response.MemberCrewStatusResponse;
import run.backend.domain.member.dto.response.MemberInfoResponse;
import run.backend.domain.member.entity.Member;
import run.backend.domain.member.repository.MemberRepository;

public interface MemberService {
import java.util.Optional;

MemberInfoResponse getMemberInfo(Member member);
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class MemberService {

void updateMemberInfo(Member member, String imageStatus, MultipartFile image, MemberInfoRequest data);
private final FileService fileService;
private final MemberRepository memberRepository;
private final JoinCrewRepository joinCrewRepository;

// void updateMember();
public MemberInfoResponse getMemberInfo(Member member) {

// void deleteMember(Member member);
//
// void leaveCrew(Member member, Long crewId);
//
// void joinCrew(String crewCode);
String crewName = memberRepository.findCrewByMemberIdAndStatus(member.getId(), JoinStatus.APPROVED)
.map(Crew::getName)
.orElse("N/A");
return new MemberInfoResponse(member.getProfileImage(), member.getNickname(), crewName);
}

@Transactional
public void updateMemberInfo(Member member, String imageStatus, MultipartFile image, MemberInfoRequest data) {

String newImageName = fileService.handleImageUpdate(imageStatus, member.getProfileImage(), image);
member.updateImage(newImageName);

if (data.gender() != null)
member.updateGender(data.gender());
if (data.age() != null)
member.updateAge(data.age());
if (data.nickname() != null)
member.updateNickname(data.nickname());

memberRepository.save(member);
}

public MemberCrewStatusResponse getMembersCrewExists(Member member) {

Optional<JoinCrew> joinCrew = joinCrewRepository.findByMember(member);

if (joinCrew.isEmpty()) {
return new MemberCrewStatusResponse("NONE");
}
return new MemberCrewStatusResponse(joinCrew.get().getJoinStatus().toString());
}
}
Loading