Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
c535610
[Feat] #238 로드맵 엔티티 구현
jeong1112 Apr 15, 2026
a4b266e
[Refactor] #238 Phase가 member가 아닌 roadmap과 연관관계를 갖도록 설정
jeong1112 Apr 15, 2026
21cda67
[Refactor] #238 actionItem이 roadmap과 연관관계를 갖도록 설정
jeong1112 Apr 15, 2026
5122b95
[Refactor] #238 로드맵 조회 시 ACTIVE한 로드맵만 조회하도록 수정
jeong1112 Apr 15, 2026
c99b907
[Refactor] #238 로드맵 관련 API 엔드포인트 변경
jeong1112 Apr 15, 2026
0a7f817
[Chore] #238 로드맵 비동기 생성 관련 필드 및 로직 제거
jeong1112 Apr 15, 2026
f0c8c99
[Refactor] #238 TermController 어노테이션 인터페이스로 분리
jeong1112 Apr 15, 2026
faee369
[Refactor] #238 로드맵 관련 컨트롤러 어노테이션 인터페이스로 분리
jeong1112 Apr 15, 2026
fa544db
[Refactor] #238 MemberController 어노테이션 인터페이스로 분리
jeong1112 Apr 15, 2026
587ceb2
[Refactor] #238 JobPostingController 어노테이션 인터페이스로 분리
jeong1112 Apr 15, 2026
e5418b2
[Chore] #238 이전 버전의 온보딩 로직 삭제
jeong1112 Apr 15, 2026
89ee713
[Chore] #238 test용 application.yml 수정
jeong1112 Apr 15, 2026
69a2634
[Test] #238 Member 테이블 컬럼 변경에 따른 Fixture 변경
jeong1112 Apr 15, 2026
a9f6b43
[Test] #238 온보딩 로직 변경 및 비동기 처리 로직 제거에 따른 테스트 코드 수정
jeong1112 Apr 15, 2026
98b6ad2
[Test] #238 Roadmap 엔티티 생성에 따른 테스트 코드 수정
jeong1112 Apr 15, 2026
9080c72
[Chore] #239 컨트롤러 반환 메시지 오타 수정
jeong1112 Apr 15, 2026
cf0793c
[Refactor] #238 Phase 조회 시 상태 기준으로 조회하도록 수정
jeong1112 Apr 15, 2026
cf478b9
[Refactor] #238 PhaseAction 조회 시에도 Roadmap 상태 반영
jeong1112 Apr 15, 2026
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,45 @@
package org.sopt.kareer.domain.jobposting.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.sopt.kareer.domain.jobposting.dto.response.JobPostingCrawlListResponse;
import org.sopt.kareer.domain.jobposting.dto.response.JobPostingListResponse;
import org.sopt.kareer.global.annotation.CustomExceptionDescription;
import org.sopt.kareer.global.response.BaseResponse;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

import static org.sopt.kareer.global.config.swagger.SwaggerResponseDescription.*;

@Tag(name = "채용 공고 관련 API")
public interface JobPostingApi {

@GetMapping("crawl")
@Operation(summary = "채용 공고 크롤링 (Server Only)")
ResponseEntity<BaseResponse<JobPostingCrawlListResponse>> crawlJobPostings(@RequestParam(defaultValue = "5") int limit);

@PostMapping(value = "recommend", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "채용 공고 추천", description = "사용자가 업로드한 이력서/자소서, 사용자 정보 기반으로 채용 공고를 추천합니다.")
@CustomExceptionDescription(RECOMMEND_JOBPOSTING)
ResponseEntity<BaseResponse<JobPostingListResponse>> recommendJobPostings(
@AuthenticationPrincipal Long memberId,
@RequestPart(value = "files", required = false) List<MultipartFile> files,
@RequestParam(value = "includeCompletedTodo", defaultValue = "false") boolean includeCompletedTodos);

@PostMapping("{jobPostingId}/bookmarks")
@Operation(summary = "채용 공고 북마크 추가/삭제", description = "사용자가 추천된 채용 공고를 추가하거나 삭제합니다.")
@CustomExceptionDescription(CREATE_BOOKMARK)
ResponseEntity<BaseResponse<Void>> createJobPostingBookmark(
@AuthenticationPrincipal Long memberId,
@PathVariable Long jobPostingId);

@GetMapping("bookmarks")
@Operation(summary = "채용 공고 북마크 조회", description = "사용자가 북마크한 채용 공고를 조회합니다.")
@CustomExceptionDescription(GET_BOOKMARK)
ResponseEntity<BaseResponse<JobPostingListResponse>> getJobPostingBookmarks(@AuthenticationPrincipal Long memberId);
}
Original file line number Diff line number Diff line change
@@ -1,73 +1,54 @@
package org.sopt.kareer.domain.jobposting.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.sopt.kareer.domain.jobposting.dto.response.JobPostingCrawlListResponse;
import org.sopt.kareer.domain.jobposting.dto.response.JobPostingListResponse;
import org.sopt.kareer.domain.jobposting.service.JobPostingCrawler;
import org.sopt.kareer.domain.jobposting.service.JobPostingService;
import org.sopt.kareer.global.annotation.CustomExceptionDescription;
import org.sopt.kareer.global.response.BaseResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

import static org.sopt.kareer.global.config.swagger.SwaggerResponseDescription.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/job-postings")
@Tag(name = "채용 공고 관련 API")
public class JobPostingController {
public class JobPostingController implements JobPostingApi {

private final JobPostingCrawler jobPostingCrawler;
private final JobPostingService jobPostingService;

@Operation(summary = "채용 공고 크롤링 (Server Only)")
@GetMapping("crawl")
@Override
public ResponseEntity<BaseResponse<JobPostingCrawlListResponse>> crawlJobPostings(@RequestParam(defaultValue = "5") int limit) {
return ResponseEntity.status(HttpStatus.OK)
.body(BaseResponse.ok(jobPostingCrawler.crawlJobPostingForTest(limit), "채용 공고 크롤링에 성공하였습니다."));
}

@Operation(summary = "채용 공고 추천", description = "사용자가 업로드한 이력서/자소서, 사용자 정보 기반으로 채용 공고를 추천합니다.")
@CustomExceptionDescription(RECOMMEND_JOBPOSTING)
@PostMapping(value = "recommend", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Override
public ResponseEntity<BaseResponse<JobPostingListResponse>> recommendJobPostings(
@AuthenticationPrincipal Long memberId,
@RequestPart(value = "files", required = false) List<MultipartFile> files,
@RequestParam(value = "includeCompletedTodo", defaultValue = "false") boolean includeCompletedTodos
){
@RequestParam(value = "includeCompletedTodo", defaultValue = "false") boolean includeCompletedTodos) {
return ResponseEntity.status(HttpStatus.OK)
.body(BaseResponse.ok(jobPostingService.recommend(memberId, files, includeCompletedTodos), "채용 공고 추천에 성공하였습니다."));
}

@Operation(summary = "채용 공고 북마크 추가/삭제", description = "사용자가 추천된 채용 공고를 추가하거나 삭제합니다.")
@CustomExceptionDescription(CREATE_BOOKMARK)
@PostMapping("{jobPostingId}/bookmarks")
@Override
public ResponseEntity<BaseResponse<Void>> createJobPostingBookmark(
@AuthenticationPrincipal Long memberId,
@PathVariable Long jobPostingId){
@PathVariable Long jobPostingId) {
jobPostingService.createOrDeleteBookmark(memberId, jobPostingId);

return ResponseEntity.status(HttpStatus.OK)
.body(BaseResponse.ok("채용 공고 북마크 추가 / 삭제에 성공했습니다."));
}

@Operation(summary = "채용 공고 북마크 조회", description = "사용자가 북마크한 채용 공고를 조회합니다.")
@CustomExceptionDescription(GET_BOOKMARK)
@GetMapping("bookmarks")
public ResponseEntity<BaseResponse<JobPostingListResponse>> getJobPostingBookmarks(
@AuthenticationPrincipal Long memberId
){
@Override
public ResponseEntity<BaseResponse<JobPostingListResponse>> getJobPostingBookmarks(@AuthenticationPrincipal Long memberId) {
return ResponseEntity.status(HttpStatus.OK)
.body(BaseResponse.ok(jobPostingService.getJobPostingBookmarks(memberId), "북마크 채용 공고 조회에 성공하였습니다."));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package org.sopt.kareer.domain.member.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.sopt.kareer.domain.member.dto.request.MemberTermsRequest;
import org.sopt.kareer.domain.member.dto.request.MypageRequest;
import org.sopt.kareer.domain.member.dto.response.*;
import org.sopt.kareer.global.annotation.CustomExceptionDescription;
import org.sopt.kareer.global.config.swagger.SwaggerResponseDescription;
import org.sopt.kareer.global.response.BaseResponse;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import static org.sopt.kareer.global.config.swagger.SwaggerResponseDescription.*;

@Tag(name = "Member API")
public interface MemberApi {

@GetMapping("/me")
@Operation(summary = "회원 정보 조회", description = "로그인한 회원의 정보를 조회합니다.")
@CustomExceptionDescription(SwaggerResponseDescription.MEMBER_INFO)
ResponseEntity<BaseResponse<MemberInfoResponse>> getMemberInfo(@AuthenticationPrincipal Long memberId);

@GetMapping("/onboard/universities")
@Operation(summary = "온보딩 대학교 목록 조회", description = "회원 온보딩 시 선택할 수 있는 대학교 목록을 조회합니다.")
ResponseEntity<BaseResponse<OnboardUniversitiesResponse>> getOnboardUniversities();

@GetMapping("/onboard/countries")
@Operation(summary = "온보딩 국가 목록 조회", description = "회원 온보딩 시 선택할 수 있는 국가 목록을 조회합니다.")
ResponseEntity<BaseResponse<OnboardCountriesResponse>> getOnboardCountries();

@GetMapping("/onboard/majors")
@Operation(summary = "온보딩 전공 목록 조회", description = "회원 온보딩 시 선택할 수 있는 전공 목록을 조회합니다.")
ResponseEntity<BaseResponse<OnboardMajorsResponse>> getOnboardMajors();

@GetMapping("/onboard/fields")
@Operation(summary = "온보딩 관심 분야 목록 조회", description = "회원 온보딩 시 선택할 수 있는 관심 분야 목록을 조회합니다.")
ResponseEntity<BaseResponse<OnboardFieldsResponse>> getOnboardFields();

@GetMapping("me/status")
@Operation(summary = "유저 상태 조회", description = "사용자의 비자 정보, 졸업 정보를 조회합니다.")
@CustomExceptionDescription(USER_STATUS)
ResponseEntity<BaseResponse<MemberStatusResponse>> getMemberStatus(@AuthenticationPrincipal Long memberId);

@GetMapping("mypage")
@Operation(summary = "마이페이지 조회", description = "마이페이지에서 유저 정보를 조회합니다.")
@CustomExceptionDescription(GET_MYPAGE)
ResponseEntity<BaseResponse<MypageResponse>> getMypage(@AuthenticationPrincipal Long memberId);

@PutMapping("mypage")
@Operation(summary = "마이페이지 수정", description = "마이페이지에서 유저 프로필을 수정합니다.")
@CustomExceptionDescription(UPDATE_MYPAGE)
ResponseEntity<BaseResponse<Void>> updateMypage(@AuthenticationPrincipal Long memberId,
@Valid @RequestBody MypageRequest request);

@DeleteMapping("/me")
@Operation(summary = "회원 탈퇴", description = "회원 탈퇴를 진행합니다.")
@CustomExceptionDescription(MEMBER_DELETE)
ResponseEntity<BaseResponse<Void>> deleteMember(@AuthenticationPrincipal Long memberId,
HttpServletRequest request,
HttpServletResponse response);

@PostMapping(value = "/onboard/ocr/visa", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "온보딩 비자 OCR API", description = "온보딩 과정에서 유저의 비자 문서를 분석하여 정보를 추출합니다.")
ResponseEntity<BaseResponse<OcrVisaResponse>> getVisaInfo(@RequestPart("file") MultipartFile file);

@PostMapping(value = "/onboard/ocr/passport", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@Operation(summary = "온보딩 여권 OCR API", description = "온보딩 과정에서 유저의 여권을 분석하여 정보를 추출합니다.")
ResponseEntity<BaseResponse<OcrPassportResponse>> getPassportInfo(@RequestPart("file") MultipartFile file);

@PostMapping("/term-agreements")
@Operation(summary = "약관 동의", description = "약관에 동의합니다.")
@CustomExceptionDescription(TERM_AGREE)
ResponseEntity<BaseResponse<Void>> agreeTerms(@AuthenticationPrincipal Long memberId,
@RequestBody @Valid MemberTermsRequest request);

@GetMapping("completion")
@Operation(summary = "온보딩, 약관동의 여부 조회")
@CustomExceptionDescription(GET_COMPLETION)
ResponseEntity<BaseResponse<MemberCompletionResponse>> getMemberCompletionStatus(@AuthenticationPrincipal Long memberId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.sopt.kareer.domain.member.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.sopt.kareer.domain.member.dto.request.MemberOnboardV2Request;
import org.sopt.kareer.global.annotation.CustomExceptionDescription;
import org.sopt.kareer.global.config.swagger.SwaggerResponseDescription;
import org.sopt.kareer.global.response.BaseResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;

@Tag(name = "Member API V2", description = "회원 API 버전 2")
public interface MemberApiV2 {

@PostMapping("/onboard")
@Operation(summary = "회원 온보딩 V2", description = "PENDING 상태의 회원의 온보딩 결과를 저장합니다.")
@CustomExceptionDescription(SwaggerResponseDescription.MEMBER_ONBOARD)
ResponseEntity<BaseResponse<Void>> onboardMember(@AuthenticationPrincipal Long memberId,
@Valid @RequestBody MemberOnboardV2Request request);
}
Loading
Loading