Skip to content
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,79 @@
package com.jobdri.jobdri_api.domain.applicationdraft.controller;

import com.jobdri.jobdri_api.domain.applicationdraft.dto.request.ApplicationDraftUpsertRequest;
import com.jobdri.jobdri_api.domain.applicationdraft.dto.response.ApplicationDraftResponse;
import com.jobdri.jobdri_api.domain.applicationdraft.dto.response.ApplicationDraftSaveResponse;
import com.jobdri.jobdri_api.domain.applicationdraft.service.ApplicationDraftService;
import com.jobdri.jobdri_api.domain.user.entity.User;
import com.jobdri.jobdri_api.global.apiPayload.ApiResponse;
import com.jobdri.jobdri_api.global.apiPayload.code.GeneralErrorCode;
import com.jobdri.jobdri_api.global.apiPayload.exception.GeneralException;
import com.jobdri.jobdri_api.global.security.UserDetailsImpl;
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.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/application-drafts")
@Tag(name = "Application Draft", description = "지원 플로우 임시저장 API")
public class ApplicationDraftController {

private final ApplicationDraftService applicationDraftService;

@Operation(
summary = "내 임시저장 생성/수정",
description = "로그인한 사용자의 지원 플로우 임시저장을 생성하거나 수정합니다. 사용자당 최근 임시저장 1개만 유지합니다."
)
@PutMapping("/me")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

임시저장 자체가 이미 개인화된거니까 굳이 url에 me를 쓸 필요 없을거 같기도,,,

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

me 넣는게 더 명확할 것 같긴 했는데.. 걍 짧게 ㄱㄱ 할게

public ApiResponse<ApplicationDraftSaveResponse> saveOrUpdateMyDraft(
@AuthenticationPrincipal UserDetailsImpl userDetails,
@Valid @RequestBody ApplicationDraftUpsertRequest request
) {
return ApiResponse.onSuccess(
"임시저장되었습니다.",
applicationDraftService.saveOrUpdate(getCurrentUser(userDetails), request)
);
}

@Operation(
summary = "내 임시저장 조회",
description = "로그인한 사용자의 최근 임시저장 1건을 조회합니다. 임시저장이 없으면 result를 null로 반환합니다."
)
@GetMapping("/me")
public ApiResponse<ApplicationDraftResponse> getMyDraft(
@AuthenticationPrincipal UserDetailsImpl userDetails
) {
return ApiResponse.onSuccess(
null,
applicationDraftService.getMyDraft(getCurrentUser(userDetails))
);
}

@Operation(
summary = "내 임시저장 삭제",
description = "로그인한 사용자의 임시저장을 삭제합니다. 임시저장이 없어도 성공 처리합니다."
)
@DeleteMapping("/me")
public ApiResponse<Void> deleteMyDraft(
@AuthenticationPrincipal UserDetailsImpl userDetails
) {
applicationDraftService.deleteMyDraft(getCurrentUser(userDetails));
return ApiResponse.onSuccess("임시저장이 삭제되었습니다.");
}

private User getCurrentUser(UserDetailsImpl userDetails) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기도 아까 리뷰 단거랑 마찬가지

if (userDetails == null) {
throw new GeneralException(GeneralErrorCode.MISSING_AUTH_INFO);
}
return userDetails.getUser();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.jobdri.jobdri_api.domain.applicationdraft.dto.request;

import com.jobdri.jobdri_api.domain.applicationdraft.entity.ApplicationDraftStep;
import com.jobdri.jobdri_api.domain.mockapply.entity.ApplyType;
import jakarta.validation.constraints.NotNull;

import java.util.List;

public record ApplicationDraftUpsertRequest(
@NotNull(message = "작성 단계는 필수입니다.")
ApplicationDraftStep step,

@NotNull(message = "지원 유형은 필수입니다.")
ApplyType type,

Long postingId,
Long middleCategoryId,
Long smallCategoryId,
List<Long> selectedQuestionIds
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.jobdri.jobdri_api.domain.applicationdraft.dto.response;

import com.jobdri.jobdri_api.domain.applicationdraft.entity.ApplicationDraftStep;
import com.jobdri.jobdri_api.domain.mockapply.entity.ApplyType;

import java.time.LocalDateTime;
import java.util.List;

public record ApplicationDraftResponse(
Long draftId,
ApplicationDraftStep step,
ApplyType type,
Long postingId,
Long middleCategoryId,
Long smallCategoryId,
List<Long> selectedQuestionIds,
LocalDateTime savedAt
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.jobdri.jobdri_api.domain.applicationdraft.dto.response;

import com.jobdri.jobdri_api.domain.applicationdraft.entity.ApplicationDraftStep;

import java.time.LocalDateTime;

public record ApplicationDraftSaveResponse(
Long draftId,
ApplicationDraftStep step,
LocalDateTime savedAt
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package com.jobdri.jobdri_api.domain.applicationdraft.entity;

import com.jobdri.jobdri_api.domain.mockapply.entity.ApplyType;
import com.jobdri.jobdri_api.domain.user.entity.User;
import jakarta.persistence.CollectionTable;
import jakarta.persistence.Column;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder(access = AccessLevel.PRIVATE)
@Table(name = "application_drafts")
public class ApplicationDraft {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ApplicationDraft가 step을 세분화해서 상태를 저장하고 있는데 현재 draft에서 실제 MockApply로 전환되는 흐름이 없는거 같은데 그러면 사실상 임시 수정값 저장 용도로만 쓰이는 거인듯..? draft 테이블을 두기보다 MockApply에 status enum을 두고 지원 공고 추출/지원서 생성 -> 질문 선택 -> 답변 작성 -> 완료 정도의 큰 단계만 관리하는 쪽이 더 나을듯

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

처음에는 최종 제출 전까지 mockapply를 안 만드는 구조를 생각해서 임시저장 테이블을 나눌라 했는데, #43 에서 문항 선택 단계 진입 전에 mockapply를 먼저 생성하고 mockApplyId 기준으로 이어지는 구조가 됐으니 너 말대로 enum을 두고 관리하는게 좋을듯

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

회의록에서도 물어볼라카는거 같으니 답변 미리 준비해놓을게


@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@OneToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "user_id", nullable = false, unique = true)
private User user;

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private ApplicationDraftStep step;

@Enumerated(EnumType.STRING)
@Column(nullable = false)
private ApplyType type;

@Column(name = "posting_id")
private Long postingId;

@Column(name = "middle_category_id")
private Long middleCategoryId;

@Column(name = "small_category_id")
private Long smallCategoryId;

@Builder.Default
@ElementCollection
@CollectionTable(
name = "application_draft_selected_questions",
joinColumns = @JoinColumn(name = "application_draft_id")
)
@Column(name = "question_id", nullable = false)
private List<Long> selectedQuestionIds = new ArrayList<>();

@Column(nullable = false, updatable = false)
private LocalDateTime createdAt;

@Column(nullable = false)
private LocalDateTime updatedAt;

public static ApplicationDraft create(
User user,
ApplicationDraftStep step,
ApplyType type,
Long postingId,
Long middleCategoryId,
Long smallCategoryId,
List<Long> selectedQuestionIds
) {
return ApplicationDraft.builder()
.user(user)
.step(step)
.type(type)
.postingId(postingId)
.middleCategoryId(middleCategoryId)
.smallCategoryId(smallCategoryId)
.selectedQuestionIds(normalizeQuestionIds(selectedQuestionIds))
.build();
}

public void update(
ApplicationDraftStep step,
ApplyType type,
Long postingId,
Long middleCategoryId,
Long smallCategoryId,
List<Long> selectedQuestionIds
) {
this.step = step;
this.type = type;
this.postingId = postingId;
this.middleCategoryId = middleCategoryId;
this.smallCategoryId = smallCategoryId;
this.selectedQuestionIds.clear();
this.selectedQuestionIds.addAll(normalizeQuestionIds(selectedQuestionIds));
this.updatedAt = LocalDateTime.now();
}

public LocalDateTime getSavedAt() {
return updatedAt;
}

@PrePersist
void onCreate() {
LocalDateTime now = LocalDateTime.now();
this.createdAt = now;
this.updatedAt = now;
}

@PreUpdate
void onUpdate() {
if (this.updatedAt == null) {
this.updatedAt = LocalDateTime.now();
}
}

private static List<Long> normalizeQuestionIds(List<Long> selectedQuestionIds) {
if (selectedQuestionIds == null) {
return new ArrayList<>();
}
return new ArrayList<>(selectedQuestionIds);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.jobdri.jobdri_api.domain.applicationdraft.entity;

public enum ApplicationDraftStep {
TYPE_SELECT,
COMPANY_SELECT,
JOB_SELECT,
QUESTION_SELECT,
ANSWER_WRITE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.jobdri.jobdri_api.domain.applicationdraft.repository;

import com.jobdri.jobdri_api.domain.applicationdraft.entity.ApplicationDraft;
import com.jobdri.jobdri_api.domain.user.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface ApplicationDraftRepository extends JpaRepository<ApplicationDraft, Long> {
Optional<ApplicationDraft> findByUser(User user);

void deleteByUser(User user);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.jobdri.jobdri_api.domain.applicationdraft.service;

import com.jobdri.jobdri_api.domain.applicationdraft.dto.request.ApplicationDraftUpsertRequest;
import com.jobdri.jobdri_api.domain.applicationdraft.dto.response.ApplicationDraftResponse;
import com.jobdri.jobdri_api.domain.applicationdraft.dto.response.ApplicationDraftSaveResponse;
import com.jobdri.jobdri_api.domain.applicationdraft.entity.ApplicationDraft;
import com.jobdri.jobdri_api.domain.applicationdraft.repository.ApplicationDraftRepository;
import com.jobdri.jobdri_api.domain.user.entity.User;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;

@Service
@RequiredArgsConstructor
public class ApplicationDraftService {

private final ApplicationDraftRepository applicationDraftRepository;

@Transactional
public ApplicationDraftSaveResponse saveOrUpdate(User user, ApplicationDraftUpsertRequest request) {
ApplicationDraft draft = applicationDraftRepository.findByUser(user)
.map(existingDraft -> {
existingDraft.update(
request.step(),
request.type(),
request.postingId(),
request.middleCategoryId(),
request.smallCategoryId(),
request.selectedQuestionIds()
);
return existingDraft;
})
.orElseGet(() -> applicationDraftRepository.save(ApplicationDraft.create(
user,
request.step(),
request.type(),
request.postingId(),
request.middleCategoryId(),
request.smallCategoryId(),
request.selectedQuestionIds()
)));

return new ApplicationDraftSaveResponse(
draft.getId(),
draft.getStep(),
draft.getSavedAt()
);
}

@Transactional(readOnly = true)
public ApplicationDraftResponse getMyDraft(User user) {
return applicationDraftRepository.findByUser(user)
.map(this::toResponse)
.orElse(null);
}

@Transactional
public void deleteMyDraft(User user) {
applicationDraftRepository.deleteByUser(user);
}

private ApplicationDraftResponse toResponse(ApplicationDraft draft) {
return new ApplicationDraftResponse(
draft.getId(),
draft.getStep(),
draft.getType(),
draft.getPostingId(),
draft.getMiddleCategoryId(),
draft.getSmallCategoryId(),
new ArrayList<>(draft.getSelectedQuestionIds()),
draft.getSavedAt()
);
}
}
Loading
Loading