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
@@ -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;
Expand All @@ -22,6 +26,7 @@
import org.springframework.web.bind.annotation.*;

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

@RestController
Expand All @@ -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(
Expand Down Expand Up @@ -95,4 +101,84 @@ public ResponseEntity<JobPostResponse> 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<JobPostDetailView> 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<String> 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();
}
}
}
24 changes: 13 additions & 11 deletions src/main/java/com/chaineeproject/chainee/dto/AuthorHeader.java
Original file line number Diff line number Diff line change
@@ -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<String> positions,
String from,
String in,
String website,
@Schema(description = "팔로워 수") long followerCount,
@Schema(description = "팔로잉 수") long followingCount
) {}
@Schema(description = "작성자 ID") Long id,
@Schema(description = "작성자 이름") String name,
@Schema(description = "프로필 이미지 URL") String profileImageUrl,
@Schema(description = "포지션(joined)") List<String> positions,
@Schema(description = "학교/소속 등 from 필드") String from,
@Schema(description = "거주/위치 inLocation") String inLocation,
@Schema(description = "웹사이트") String website,
@Schema(description = "팔로워 수") long followers,
@Schema(description = "팔로잉 수") long followings
) {}
Original file line number Diff line number Diff line change
@@ -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,
Expand Down