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: Apply 관련 기능 개발 #199

Merged
merged 15 commits into from
Sep 16, 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
@@ -0,0 +1,66 @@
package com.gongjakso.server.domain.apply.controller;

import com.gongjakso.server.domain.apply.service.ApplyService;
import com.gongjakso.server.domain.apply.dto.request.ApplyReq;
import com.gongjakso.server.domain.apply.dto.response.ApplyRes;
import com.gongjakso.server.domain.apply.dto.request.StatusReq;
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.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;


@RestController
@RequestMapping("/api/v2/apply")
@RequiredArgsConstructor
@Tag(name = "Apply", description = "지원 관련 API")
public class ApplyController {

private final ApplyService applyService;

@Operation(summary = "지원하기", description = "팀에 지원하는 API")
@PostMapping("/{team_id}")
public ApplicationResponse<ApplyRes> apply(@AuthenticationPrincipal PrincipalDetails principalDetails,
@PathVariable("team_id") Long teamId,
@Valid @RequestBody ApplyReq req) {
return ApplicationResponse.ok(applyService.apply(principalDetails.getMember(), teamId, req));
}

@Operation(summary = "내가 지원한 팀 조회", description = "내가 지원한 팀을 페이징 조회하는 API")
@GetMapping("/my")
public ApplicationResponse<Page<ApplyRes>> getMyApplies(@AuthenticationPrincipal PrincipalDetails principalDetails,
@RequestParam Long page) {
Pageable pageable = PageRequest.of(page.intValue(), 6);
return ApplicationResponse.ok(applyService.getMyApplies(principalDetails.getMember(), pageable));
}

@Operation(summary = "특정 지원자 지원서 열람하기", description = "특정 지원자의 지원서를 열람하는 API")
@GetMapping("/{apply_id}")
public ApplicationResponse<ApplyRes> getApply(@AuthenticationPrincipal PrincipalDetails principalDetails,
@PathVariable("apply_id") Long applyId) {
return ApplicationResponse.ok(applyService.getApply(principalDetails.getMember(), applyId));
}

@Operation(summary = "지원서 선발/미선발", description = "지원자를 선발/미선발하는 API")
@PatchMapping("/select/{apply_id}")
public ApplicationResponse<ApplyRes> selectApply(@AuthenticationPrincipal PrincipalDetails principalDetails,
@PathVariable("apply_id") Long applyId,
@Valid @RequestBody StatusReq req) {
return ApplicationResponse.ok(applyService.selectApply(principalDetails.getMember(), applyId, req));
}

@Operation(summary = "지원 취소", description = "지원자가 지원 취소하는 API")
@DeleteMapping("/{apply_id}")
public ApplicationResponse<Void> cancelApply(@AuthenticationPrincipal PrincipalDetails principalDetails,
@PathVariable("apply_id") Long applyId) {
applyService.cancelApply(principalDetails.getMember(), applyId);
return ApplicationResponse.ok();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.gongjakso.server.domain.apply.dto.request;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.gongjakso.server.domain.member.entity.Member;
import com.gongjakso.server.domain.portfolio.entity.Portfolio;
import com.gongjakso.server.domain.apply.entity.Apply;
import com.gongjakso.server.domain.apply.entity.PortfolioInfo;
import com.gongjakso.server.domain.team.entity.Team;
import com.gongjakso.server.domain.apply.enumerate.ApplyStatus;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Builder;

@JsonInclude(JsonInclude.Include.NON_NULL)
@Builder
public record ApplyReq(

@Nullable
@Schema(description = "포트폴리오 ID", example = "1")
Long portfolioId,

@NotNull
@Schema(description = "포트폴리오 공개 설정", example = "false")
Boolean isPrivate,

@Size(max = 500)
@Schema(description = "지원 이유", example = "저는 데이터 사이언스 과목을 수강하며 데이터에 관한 기본적인 내용들을 배우며 이런 데이터를 잘 활용하고, 이용하는 것이 중요한 역량이 될 것 같다고 판단했습니다. 그래서 관련된 역량을 쌓고자 공모전에 출품하고 싶다는 생각을 가지게 되었고, 공공데이터 공모전이 적합하다고 생각했습니다!")
String body,

@Size(max = 20)
@NotNull
@Schema(description = "지원 상태", example = "COMPLETED")
ApplyStatus status,

@Size(max = 20)
@NotNull
@Schema(description = "지원 파트", example = "기획")
String part
) {
public static Apply toEntity(ApplyReq req, Team team, Member member, @Nullable Portfolio portfolio) {
PortfolioInfo portfolioInfo = portfolio != null
? PortfolioInfo.ofPortfolio(portfolio)
: PortfolioInfo.ofPrivate();

return Apply.builder()
.team(team)
.member(member)
.portfolioInfo(portfolioInfo)
.body(req.body())
.status(req.status())
.part(req.part())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.gongjakso.server.domain.apply.dto.request;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.gongjakso.server.domain.apply.enumerate.ApplyStatus;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Builder;

@JsonInclude(JsonInclude.Include.NON_NULL)
@Builder
public record StatusReq(

@Size(max = 20)
@NotNull
@Schema(description = "지원 상태", example = "SELECTED")
ApplyStatus status
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.gongjakso.server.domain.apply.dto.response;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.gongjakso.server.domain.apply.entity.Apply;
import jakarta.annotation.Nullable;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Builder;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Period;

@JsonInclude(JsonInclude.Include.NON_NULL)
@Builder
public record ApplyRes(

@NotNull
Long applyId,

@NotNull
Long teamId,

@NotNull
Long memberId,

@Nullable
Long portfolioId,

@Nullable
String portfolioTitle,

@Size(max = 500)
String body,

@Size(max = 20)
@NotNull
String status,

@Size(max = 20)
@NotNull
String part,

int scrapCount,

int remainingDays,

LocalDate startedAt,

LocalDate finishedAt,

String teamName,

String leaderName,

Boolean isViewed,

LocalDateTime deleteAt

) {
public static ApplyRes of(Apply apply) {
return ApplyRes.builder()
.applyId(apply.getId())
.teamId(apply.getTeam().getId())
.teamName(apply.getTeam().getTitle())
.memberId(apply.getMember().getId())
.leaderName(apply.getTeam().getMember().getName())
.portfolioId(apply.getPortfolioInfo().getPortfolio() != null ? apply.getPortfolioInfo().getPortfolio().getId() : null)
.portfolioTitle(apply.getPortfolioInfo().getPortfolio() != null ? apply.getPortfolioInfo().getPortfolio().getTitle() : null)
.body(apply.getBody())
.status(apply.getStatus().getDescription())
.part(apply.getPart())
.isViewed(apply.isViewed())
.deleteAt(apply.getDeletedAt())
.scrapCount(apply.getTeam().getScrapCount())
.startedAt(apply.getTeam().getStartedAt())
.finishedAt(apply.getTeam().getFinishedAt())
.remainingDays(Period.between(LocalDate.now(), apply.getTeam().getFinishedAt()).getDays())
.build();
}
}
68 changes: 68 additions & 0 deletions src/main/java/com/gongjakso/server/domain/apply/entity/Apply.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.gongjakso.server.domain.apply.entity;

import com.gongjakso.server.domain.member.entity.Member;
import com.gongjakso.server.domain.team.entity.Team;
import com.gongjakso.server.domain.apply.enumerate.ApplyStatus;
import com.gongjakso.server.global.common.BaseTimeEntity;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.SQLDelete;

@Getter
@Entity
@Table(name = "apply")
@SQLDelete(sql = "UPDATE apply SET deleted_at = NOW() where id = ?")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Apply extends BaseTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id", nullable = false, columnDefinition = "bigint")
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id", nullable = false, columnDefinition = "bigint")
private Team team;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="member_id", nullable = false, columnDefinition = "bigint")
private Member member;

@Embedded
private PortfolioInfo portfolioInfo;

@Column(nullable = false, columnDefinition = "varchar(500)")
private String body;

@Column(nullable = false, columnDefinition = "varchar(20)")
@Enumerated(EnumType.STRING)
private ApplyStatus status;

@Column(nullable = false, columnDefinition = "varchar(20)")
private String part;

@Column(nullable = false, columnDefinition = "tinyint(1) default 0")
private boolean isViewed;

@Builder
public Apply(Team team, Member member, PortfolioInfo portfolioInfo, String body, ApplyStatus status, String part) {
this.team = team;
this.member = member;
this.portfolioInfo = portfolioInfo;
this.body = body;
this.status = status;
this.part = part;
this.isViewed = false;
}

public void select(ApplyStatus status) {
this.status = status;
}

public void setViewed() {
this.isViewed = true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.gongjakso.server.domain.apply.entity;

import com.gongjakso.server.domain.portfolio.entity.Portfolio;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Embeddable
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PortfolioInfo {

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "portfolio_id", columnDefinition = "bigint")
private Portfolio portfolio;

private boolean isPrivate;

public static PortfolioInfo ofPortfolio(Portfolio portfolio) {
return PortfolioInfo.builder()
.portfolio(portfolio)
.isPrivate(false)
.build();
}

public static PortfolioInfo ofPrivate() {
return PortfolioInfo.builder()
.portfolio(null)
.isPrivate(true)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.gongjakso.server.domain.apply.enumerate;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum ApplyStatus {
COMPLETED("지원 완료"),
ACCEPTED("합격"),
REJECTED("불합격");

private final String description;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.gongjakso.server.domain.apply.repository;

import com.gongjakso.server.domain.apply.entity.Apply;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface ApplyRepository extends JpaRepository<Apply, Long>, ApplyRepositoryCustom {
Boolean existsByMemberIdAndTeamIdAndDeletedAtIsNull(Long memberId, Long teamId);
Optional<Apply> findByIdAndDeletedAtIsNull(Long applyId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.gongjakso.server.domain.apply.repository;

import com.gongjakso.server.domain.member.entity.Member;
import com.gongjakso.server.domain.apply.dto.response.ApplyRes;
import com.gongjakso.server.domain.apply.entity.Apply;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import java.util.Optional;

public interface ApplyRepositoryCustom {
Page<ApplyRes> findByMemberAndPage(Member member, Pageable pageable);
Optional<Apply> findApplyDetails(Long applyId);
}
Loading
Loading