Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Team & Scrap API 개발 #196

Merged
merged 17 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@
import com.gongjakso.server.domain.contest.entity.Contest;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface ContestRepository extends JpaRepository<Contest, Long>, ContestRepositoryCustom {

/**
* 공모전 ID를 조건으로 하여 공모전을 조회한다. (논리적 삭제된 데이터는 조회되지 않음)
* @param contestId 공모전 ID
* @return Optional<Contest>
*/
Optional<Contest> findByIdAndDeletedAtIsNull(Long contestId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package com.gongjakso.server.domain.team.controller;

import com.gongjakso.server.domain.team.dto.request.TeamReq;
import com.gongjakso.server.domain.team.service.TeamService;
import com.gongjakso.server.global.common.ApplicationResponse;
import com.gongjakso.server.global.security.PrincipalDetails;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

import java.time.LocalDate;


@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v2")
@Tag(name = "Team", description = "팀 관련 API 리스트")
public class TeamController {

private final TeamService teamService;

@Operation(summary = "팀 생성 API", description = "특정 공모전에 해당하는 팀을 생성하는 API")
@PostMapping("/contest/{contest_id}/team/create")
public ApplicationResponse<?> createTeam(@AuthenticationPrincipal PrincipalDetails principalDetails,
@PathVariable(value = "contest_id") Long contestId, @Valid @RequestBody TeamReq teamReq) {
return ApplicationResponse.created(teamService.createTeam(principalDetails.getMember(), contestId, teamReq));
}

@Operation(summary = "팀 수정 API", description = "특정 공모전에 해당하는 팀을 수정하는 API")
@PutMapping("/contest/{contest_id}/team/{team_id}/update")
public ApplicationResponse<?> updateTeam(@AuthenticationPrincipal PrincipalDetails principalDetails,
@PathVariable(value = "contest_id") Long contestId,
@PathVariable(value = "team_id") Long teamId,
@Valid @RequestBody TeamReq teamReq) {
return ApplicationResponse.ok(teamService.updateTeam(principalDetails.getMember(), contestId, teamId, teamReq));
}

@Operation(summary = "팀 모집 연장하기 API", description = "특정 공모전에 해당하는 팀의 모집 마감일을 연장하는 API")
@PatchMapping("/contest/{contest_id}/team/{team_id}/extend-recruit")
public ApplicationResponse<?> extendRecruit(@AuthenticationPrincipal PrincipalDetails principalDetails,
@PathVariable(value = "contest_id") Long contestId,
@PathVariable(value = "team_id") Long teamId,
@RequestParam(value = "extend-date") @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate extendDate) {
return ApplicationResponse.ok(teamService.extendRecruit(principalDetails.getMember(), contestId, teamId, extendDate));
}

@Operation(summary = "팀 모집 마감하기 API", description = "특정 공모전에 해당하는 팀의 모집을 마감하는 API")
@PatchMapping("/contest/{contest_id}/team/{team_id}/close-recruit")
public ApplicationResponse<?> closeRecruit(@AuthenticationPrincipal PrincipalDetails principalDetails,
@PathVariable(value = "contest_id") Long contestId,
@PathVariable(value = "team_id") Long teamId) {
return ApplicationResponse.ok(teamService.closeRecruit(principalDetails.getMember(), contestId, teamId));
}

@Operation(summary = "팀 모집 취소하기 API", description = "특정 공모전에 해당하는 팀의 모집을 취소하는 API")
@PatchMapping("/contest/{contest_id}/team/{team_id}/cancel-recruit")
public ApplicationResponse<?> cancelRecruit(@AuthenticationPrincipal PrincipalDetails principalDetails,
@PathVariable(value = "contest_id") Long contestId,
@PathVariable(value = "team_id") Long teamId) {
return ApplicationResponse.ok(teamService.cancelRecruit(principalDetails.getMember(), contestId, teamId));
}

@Operation(summary = "팀 삭제 API", description = "특정 공모전에 해당하는 팀을 삭제하는 API (논리적 삭제)")
@DeleteMapping("/contest/{contest_id}/team/{team_id}/delete")
public ApplicationResponse<?> deleteTeam(@AuthenticationPrincipal PrincipalDetails principalDetails,
@PathVariable(value = "contest_id") Long contestId,
@PathVariable(value = "team_id") Long teamId) {
teamService.deleteTeam(principalDetails.getMember(), contestId, teamId);
return ApplicationResponse.ok();
}

@Operation(summary = "팀 조회 API", description = "특정 공모전에 해당하는 팀을 조회하는 API")
@GetMapping("/contest/{contest_id}/team/{team_id}")
public ApplicationResponse<?> getTeam(@PathVariable(value = "contest_id") Long contestId,
@PathVariable(value = "team_id") Long teamId) {
return ApplicationResponse.ok(teamService.getTeam(contestId, teamId));
}

@Operation(summary = "팀 리스트 조회 API", description = "특정 공모전에 해당하는 팀 리스트를 조회하는 API (오프셋 기반 페이지네이션)")
@GetMapping("/contest/{contest_id}/team/list")
public ApplicationResponse<?> getTeamList(@PathVariable(value = "contest_id") Long contestId,
@RequestParam(value = "province", required = false) String province,
@RequestParam(value = "district", required = false) String district,
@PageableDefault(size = 8) Pageable pageable) {
return ApplicationResponse.ok(teamService.getTeamListWithContest(contestId, province, district, pageable));
}

@Operation(summary = "팀 리스트 조회 API", description = "공모전에 상관없이 팀 리스트를 조회하는 API (검색 기능 존재 / 오프셋 기반 페이지네이션)")
@GetMapping("/team/list")
public ApplicationResponse<?> getTeamList(@RequestParam(value = "province", required = false) String province,
@RequestParam(value = "district", required = false) String district,
@RequestParam(value = "keyword", required = false) String keyword,
@PageableDefault(size = 8) Pageable pageable) {
return ApplicationResponse.ok(teamService.getTeamListWithoutContest(province, district, keyword, pageable));
}

@Operation(summary = "내가 모집 중인 팀 리스트 조회 API", description = "공모전에 상관없이 내가 모집 중인 팀 리스트를 조회하는 API (오프셋 기반 페이지네이션)")
@GetMapping("/team/my-recruit")
public ApplicationResponse<?> getMyRecruitTeamList(@AuthenticationPrincipal PrincipalDetails principalDetails,
@PageableDefault(size = 8) Pageable pageable) {
return ApplicationResponse.ok(teamService.getMyRecruitTeamList(principalDetails.getMember(), pageable));
}

@Operation(summary = "내가 참여한 팀 리스트 조회 API", description = "공모전에 상관없이 내가 참여한 팀 리스트를 조회하는 API (오프셋 기반 페이지네이션)")
@GetMapping("/team/my-apply")
public ApplicationResponse<?> getMyApplyTeamList(@AuthenticationPrincipal PrincipalDetails principalDetails,
@PageableDefault(size = 8) Pageable pageable) {
return ApplicationResponse.ok(teamService.getMyApplyTeamList(principalDetails.getMember(), pageable));
}

@Operation(summary = "특정 팀 스크랩하기", description = "특정 팀을 스크랩하는 API")
@PostMapping("/team/{team_id}/scrap")
public ApplicationResponse<?> scrapTeam(@AuthenticationPrincipal PrincipalDetails principalDetails,
@PathVariable(value = "team_id") Long teamId) {
teamService.scrapTeam(principalDetails.getMember(), teamId);
return ApplicationResponse.ok();
}

@Operation(summary = "특정 팀 스크랩 취소하기", description = "특정 팀 스크랩을 취소하는 API")
@DeleteMapping("/team/{team_id}/scrap")
public ApplicationResponse<?> cancelScrapTeam(@AuthenticationPrincipal PrincipalDetails principalDetails,
@PathVariable(value = "team_id") Long teamId) {
teamService.cancelScrapTeam(principalDetails.getMember(), teamId);
return ApplicationResponse.ok();
}

@Operation(summary = "스크랩한 팀 리스트 조회 API", description = "스크랩한 팀 리스트를 조회하는 API (오프셋 기반 페이지네이션)")
@GetMapping("/team/scrap/list")
public ApplicationResponse<?> getScrapTeamList(@AuthenticationPrincipal PrincipalDetails principalDetails,
@PageableDefault(size = 8) Pageable pageable) {
return ApplicationResponse.ok(teamService.getScrapTeamList(principalDetails.getMember(), pageable));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
package com.gongjakso.server.domain.team.dto.request;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
import com.fasterxml.jackson.databind.annotation.JsonNaming;
import com.gongjakso.server.domain.contest.entity.Contest;
import com.gongjakso.server.domain.member.entity.Member;
import com.gongjakso.server.domain.team.entity.Team;
import com.gongjakso.server.domain.team.enumerate.MeetingMethod;
import com.gongjakso.server.domain.team.vo.RecruitPart;
import com.gongjakso.server.global.common.Position;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Builder;

import java.time.LocalDate;
import java.util.List;

@Builder
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public record TeamReq(

@Schema(description = "팀 제목", example = "광화문광장 숏폼 공모전 참여자 모집")
@NotEmpty
@Size(min = 1, max = 50)
String title,

@Schema(description = "팀 내용", example = "광화문광장 숏폼 공모전 참여자 모집합니다.")
@Size(max = 1000)
String body,

@Schema(description = "총 인원", example = "5")
@NotEmpty
int totalCount,

@Schema(description = "모임 방식", example = "온라인 | 오프라인 | 하이브리드")
@NotEmpty
String meetingMethod,

@Schema(description = "시/도", example = "서울특별시")
String province,

@Schema(description = "시/군/구", example = "강남구")
String district,

@Schema(description = "지원 파트")
List<RecruitPartReq> recruitPart,

@Schema(description = "모집 마감일", example = "2024-12-31")
@JsonFormat(pattern = "yyyy-MM-dd")
LocalDate recruitFinishedAt,

@Schema(description = "활동 시작일", example = "2025-01-01")
@JsonFormat(pattern = "yyyy-MM-dd")
LocalDate startedAt,

@Schema(description = "활동 종료일", example = "2025-01-31")
@JsonFormat(pattern = "yyyy-MM-dd")
LocalDate finishedAt,

@Schema(description = "컨택 링크", example = "https://open.kakao.com/o/gongjakso")
String channelLink
) {

@Builder
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
public record RecruitPartReq(
@NotNull
@Schema(description = "파트 이름", example = "기획")
String position,

@NotNull
@Schema(description = "파트 인원", example = "1")
Integer recruitCount
) {

public RecruitPart from() {
Position position = Position.convert(this.position);
return new RecruitPart(position, recruitCount, 0);
}
}

public Team from(Member member, Contest contest) {
MeetingMethod method = MeetingMethod.valueOf(meetingMethod);

List<RecruitPart> recruitPartList = recruitPart.stream()
.map(RecruitPartReq::from)
.toList();

return new Team(
member,
contest,
title,
body,
totalCount,
method,
province,
district,
recruitPartList,
recruitFinishedAt,
startedAt,
finishedAt,
channelLink);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.gongjakso.server.domain.team.dto.response;

import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.gongjakso.server.domain.team.entity.Team;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;

import java.time.LocalDate;

@Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public record SimpleTeamRes(
@Schema(description = "팀 ID", example = "1")
Long id,

@Schema(description = "팀 제목", example = "광화문광장 숏폼 공모전 참여자 모집")
String title,

@Schema(description = "멤버 ID", example = "1")
Long memberId,

@Schema(description = "멤버 이름", example = "홍길동")
String memberName,

@Schema(description = "모집 마감일", example = "2024-12-31")
LocalDate recruitFinishedAt,

@Schema(description = "활동 시작일", example = "2025-01-01")
@JsonFormat(shape = JsonFormat.Shape.STRING)
LocalDate startedAt,

@Schema(description = "활동 종료일", example = "2025-01-31")
LocalDate finishedAt,

@Schema(description = "D-day", example = "10")
int dDay,

@Schema(description = "스크랩 수", example = "10")
int scrapCount,

@Schema(description = "조회 수", example = "10")
int viewCount
) {

public static SimpleTeamRes of(Team team) {
int dDay = 0;
if (team.getRecruitFinishedAt() != null) {
dDay = (int) java.time.temporal.ChronoUnit.DAYS.between(LocalDate.now(), team.getRecruitFinishedAt());
}

return SimpleTeamRes.builder()
.id(team.getId())
.title(team.getTitle())
.memberId(team.getMember().getId())
.memberName(team.getMember().getName())
.recruitFinishedAt(team.getRecruitFinishedAt())
.startedAt(team.getStartedAt())
.finishedAt(team.getFinishedAt())
.dDay(dDay)
.scrapCount(team.getScrapCount())
.viewCount(team.getViewCount())
.build();
}
}
Loading
Loading