diff --git a/src/main/java/com/chaineeproject/chainee/controller/JobPostController.java b/src/main/java/com/chaineeproject/chainee/controller/JobPostController.java index a14ba1e..3bd7dac 100644 --- a/src/main/java/com/chaineeproject/chainee/controller/JobPostController.java +++ b/src/main/java/com/chaineeproject/chainee/controller/JobPostController.java @@ -1,18 +1,22 @@ // src/main/java/com/chaineeproject/chainee/controller/JobPostController.java package com.chaineeproject.chainee.controller; +import com.chaineeproject.chainee.dto.AuthorHeader; +import com.chaineeproject.chainee.dto.JobPostDetailView; import com.chaineeproject.chainee.dto.JobPostRequest; import com.chaineeproject.chainee.dto.JobPostResponse; import com.chaineeproject.chainee.entity.JobPost; import com.chaineeproject.chainee.entity.User; import com.chaineeproject.chainee.exception.ApplicationException; import com.chaineeproject.chainee.exception.ErrorCode; +import com.chaineeproject.chainee.repository.FollowRepository; import com.chaineeproject.chainee.repository.JobPostRepository; import com.chaineeproject.chainee.repository.UserRepository; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.media.*; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -22,6 +26,7 @@ import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; @RestController @@ -34,6 +39,7 @@ public class JobPostController { private final UserRepository userRepository; private final JobPostRepository jobPostRepository; private final ObjectMapper objectMapper; + private final FollowRepository followRepository; @PostMapping @Operation( @@ -95,4 +101,84 @@ public ResponseEntity createJobPost( throw new ApplicationException(ErrorCode.INVALID_REQUEST); } } + + @GetMapping("/{postId}") + @Operation( + summary = "구인 공고 상세 조회", + description = "지정한 공고 ID의 상세 정보를 반환합니다.", + responses = @ApiResponse( + responseCode = "200", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = JobPostDetailView.class), + examples = @ExampleObject(value = """ + { + "id": 1, + "title": "백엔드 개발자 구합니다", + "description": "Spring Boot 기반 API 개발/운영, 코드리뷰 문화", + "payment": 3500000, + "requiredSkills": ["Java","Spring Boot","JPA","MySQL"], + "duration": "6m", + "deadline": "2025-12-31", + "createdAt": "2025-11-09T17:35:30.647140", + "applicantCount": 2, + "author": { + "id": 5, + "name": "홍길동", + "profileImageUrl": "https://cdn.example.com/u/5.png", + "positions": "Backend,DevOps", + "from": "Hongik Univ.", + "inLocation": "Seoul", + "website": "https://example.com", + "followers": 12, + "followings": 7 + } + } + """) + ) + ) + ) + public ResponseEntity getJobPost(@PathVariable Long postId) { + JobPost jp = jobPostRepository.findById(postId) + .orElseThrow(() -> new ApplicationException(ErrorCode.JOB_POST_NOT_FOUND)); + + var author = jp.getAuthor(); + long followers = followRepository.countByFolloweeId(author.getId()); + long followings = followRepository.countByFollowerId(author.getId()); + + var authorHeader = new AuthorHeader( + author.getId(), + author.getName(), + author.getProfileImageUrl(), + author.getPositions(), // 문자열(joined) 보관이라면 그대로 + author.getFrom(), + author.getInLocation(), + author.getWebsite(), + followers, + followings + ); + + return ResponseEntity.ok(new JobPostDetailView( + jp.getId(), + jp.getTitle(), + jp.getDescription(), + jp.getPayment(), + parseSkills(jp.getRequiredSkills()), + jp.getDuration(), + jp.getDeadline(), + jp.getCreatedAt(), + jp.getApplicantCount(), + authorHeader + )); + } + private java.util.List parseSkills(String json) { + if (json == null || json.isBlank()) return java.util.List.of(); + try { + return objectMapper.readValue(json, objectMapper.getTypeFactory() + .constructCollectionType(ArrayList.class, String.class)); + } catch (Exception e) { + log.warn("requiredSkills 파싱 실패, 빈 배열로 대체. json={}", json, e); + return java.util.List.of(); + } + } } diff --git a/src/main/java/com/chaineeproject/chainee/dto/AuthorHeader.java b/src/main/java/com/chaineeproject/chainee/dto/AuthorHeader.java index 5a2fdec..0316f73 100644 --- a/src/main/java/com/chaineeproject/chainee/dto/AuthorHeader.java +++ b/src/main/java/com/chaineeproject/chainee/dto/AuthorHeader.java @@ -1,17 +1,19 @@ +// src/main/java/com/chaineeproject/chainee/dto/AuthorHeader.java package com.chaineeproject.chainee.dto; import io.swagger.v3.oas.annotations.media.Schema; + import java.util.List; -@Schema(description = "공고 작성자 헤더 정보") +@Schema(description = "작성자 요약 정보") public record AuthorHeader( - Long id, - String name, - String profileImageUrl, - List positions, - String from, - String in, - String website, - @Schema(description = "팔로워 수") long followerCount, - @Schema(description = "팔로잉 수") long followingCount -) {} \ No newline at end of file + @Schema(description = "작성자 ID") Long id, + @Schema(description = "작성자 이름") String name, + @Schema(description = "프로필 이미지 URL") String profileImageUrl, + @Schema(description = "포지션(joined)") List positions, + @Schema(description = "학교/소속 등 from 필드") String from, + @Schema(description = "거주/위치 inLocation") String inLocation, + @Schema(description = "웹사이트") String website, + @Schema(description = "팔로워 수") long followers, + @Schema(description = "팔로잉 수") long followings +) {} diff --git a/src/main/java/com/chaineeproject/chainee/dto/JobPostDetail.java b/src/main/java/com/chaineeproject/chainee/dto/JobPostDetailView.java similarity index 74% rename from src/main/java/com/chaineeproject/chainee/dto/JobPostDetail.java rename to src/main/java/com/chaineeproject/chainee/dto/JobPostDetailView.java index cf0b6ee..f8df551 100644 --- a/src/main/java/com/chaineeproject/chainee/dto/JobPostDetail.java +++ b/src/main/java/com/chaineeproject/chainee/dto/JobPostDetailView.java @@ -1,13 +1,15 @@ +// src/main/java/com/chaineeproject/chainee/dto/JobPostDetailView.java package com.chaineeproject.chainee.dto; import io.swagger.v3.oas.annotations.media.Schema; + import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.List; -@Schema(description = "구인 공고 상세") -public record JobPostDetail( +@Schema(description = "구인 공고 상세 응답 뷰") +public record JobPostDetailView( Long id, String title, String description,