[Feat] 카드(공고), 덱(공고 모음) 크롤링 적용 기능 구현#18
Conversation
📝 WalkthroughWalkthroughPostgreSQL 채용 데이터 스키마와 JSONL 적재 도구를 추가하고, 원문 공고를 Job으로 동기화하는 스케줄러와 추천 덱·카드·브리핑 API를 구현했습니다. 관련 엔티티, 저장소, 상태 코드, 테스트 및 애플리케이션 설정도 함께 변경했습니다. Changes채용 데이터 적재
원문 공고 동기화
추천 덱과 브리핑
애플리케이션 설정
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant BriefingController
participant BriefingService
participant JobRepository
participant DeckRepository
participant CardService
Client->>BriefingController: GET /api/briefings/today/{userId}
BriefingController->>BriefingService: getTodayBriefing(userId)
BriefingService->>JobRepository: countApplicable(...)
BriefingService->>DeckRepository: findByUserIdAndDeckDate(...)
BriefingService->>CardService: getDeckCards(deckId)
BriefingService-->>BriefingController: BriefingResponse
BriefingController-->>Client: ApiResponse 응답
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
Comment |
There was a problem hiding this comment.
Actionable comments posted: 11
🧹 Nitpick comments (1)
CLAUDE.md (1)
28-30: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win현재 패키지 구조에 맞게 아키텍처 설명을 갱신하거나 규칙을 명확히 해 주세요.
현재 PR에는
domain/deck의 controller/dto/repository/service와domain/job의 converter/repository/service가 추가됐지만, 문서는 대부분의 도메인이 entity/enums만 가진다고 설명합니다. 이 문서가 현재 구조 설명인지, entity/enums만 허용하는 규칙인지 확인한 뒤 한쪽 기준으로 통일해야 합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@CLAUDE.md` around lines 28 - 30, Update the architecture description in CLAUDE.md to match the current package structure, including the added controller, dto, repository, service, and converter packages under domain/deck and domain/job. Alternatively, explicitly state that entity/enums-only is an intentional rule and clarify how these existing packages comply; ensure the documentation consistently reflects one of these interpretations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.gitignore:
- Around line 47-49: Update the product-name comment heading in .gitignore from
“Cluade” to “Claude,” leaving the .claude/ ignore rule unchanged.
In `@database/load.py`:
- Around line 89-95: Update the conflict-update assignments in database/load.py
lines 89-95, around POSTING_COLS and execute_values, to refresh collected_at
with the actual crawl timestamp or now() on every re-collected posting. Update
the collected_at definition in database/schema.sql lines 99-100 to explicitly
document and enforce the same final-collection-time semantics; both sites
require changes and must remain consistent.
In
`@src/main/java/com/leets7th/job_is_be/domain/deck/controller/BriefingController.java`:
- Around line 24-39: Update BriefingController methods getTodayBriefing and
getTodayBriefingStatus to obtain the user ID from the authenticated principal or
UserDetails instead of accepting it as a path variable. Remove userId from both
route mappings and method parameters, then pass the authenticated user ID to the
corresponding briefingService methods.
In
`@src/main/java/com/leets7th/job_is_be/domain/deck/controller/DeckController.java`:
- Around line 24-30: 경로 변수로 사용자를 지정해 타인의 덱에 접근할 수 있는 문제가 있습니다.
src/main/java/com/leets7th/job_is_be/domain/deck/controller/DeckController.java#L24-L30의
generateTodayDeck에서 userId 경로 변수를 제거하고 인증 주체에서 사용자 ID를 추출해
recommendationService.generateTodayDeck에 전달하세요.
src/main/java/com/leets7th/job_is_be/domain/deck/controller/CardController.java#L23-L29에서는
인증 주체를 서비스 계층으로 전달하고, 덱 소유자와 요청자의 ID가 일치하는지 검증한 뒤에만 카드를 조회하도록 수정하세요. 교차 사용자 덱
생성·카드 조회가 거부되는 보안 테스트도 추가하세요.
In
`@src/main/java/com/leets7th/job_is_be/domain/deck/service/BriefingService.java`:
- Around line 55-60: BriefingService의 현재 시각 조회를 단일 기준으로 고정하세요. `today`,
`resolveSlot` 및 `countApplicable` 호출 전에 `LocalDateTime now`를 한 번만 생성하고, 해당 값에서
날짜와 시간을 추출해 기존 흐름에 재사용하세요.
In
`@src/main/java/com/leets7th/job_is_be/domain/deck/service/RecommendationService.java`:
- Around line 49-50: Update the deck lookup/creation flow in
RecommendationService to handle concurrent first requests: if saving the new
Deck fails due to the uk_decks_user_date unique constraint, catch that conflict
and re-query deckRepository.findByUserIdAndDeckDate(userId, today) to use the
existing Deck. Preserve the current lookup and creation behavior when no
concurrent conflict occurs.
In `@src/main/java/com/leets7th/job_is_be/domain/job/entity/Company.java`:
- Around line 23-27: Update the Company entity to extend BaseEntity, remove its
duplicate id field and associated identifier mapping, and retain the existing
entity behavior while inheriting the shared identifier and audit fields from
BaseEntity.
In `@src/main/java/com/leets7th/job_is_be/domain/job/entity/JobPosting.java`:
- Around line 103-107: JobPosting의 dueTime, confirmTime, collectedAt 시간 타입을 스키마의
timestamptz에 맞춰 LocalDateTime에서 OffsetDateTime 또는 Instant로 변경하고, 해당 값을
생성·저장·조회하는 동기화 경로 전체도 동일한 타입으로 통일하세요. 관련 매핑과 변환 로직에서 JVM 기본 시간대에 의존하지 않도록 명시적
오프셋 또는 UTC를 사용하세요.
In
`@src/main/java/com/leets7th/job_is_be/domain/job/repository/JobPostingRepository.java`:
- Line 12: Update JobPostingRepository.findAll() and its
JobPostingSyncService.sync() caller so synchronization no longer materializes
every JSON-containing posting at once. Replace the repository contract with
paged retrieval or retrieval of only postings changed since the last
synchronization, and update sync() to process results incrementally while
preserving synchronization coverage.
In
`@src/main/java/com/leets7th/job_is_be/domain/job/service/JobPostingSyncService.java`:
- Around line 23-31: Update JobPostingSyncService.sync to run once when the
application starts in addition to the existing scheduled execution, using the
appropriate startup lifecycle annotation or hook. Preserve the hourly `@Scheduled`
behavior and add a test verifying that starting with a newly loaded database
creates Job records immediately.
In `@src/main/java/com/leets7th/job_is_be/global/config/SecurityConfig.java`:
- Around line 71-74: 인증 및 CSRF 정책이 SecurityConfig와 CLAUDE.md에서 불일치합니다.
src/main/java/com/leets7th/job_is_be/global/config/SecurityConfig.java의 csrf 설정과
인증 규칙을 의도한 단일 정책으로 정리하고, CLAUDE.md 36행의 설명도 동일한 정책을 반영하도록 갱신하세요. SecurityConfig의
JWT 인증, refresh 엔드포인트 CSRF 적용, stateless 설정과 문서의 CSRF disabled 및 permitAll 설명 중
실제 요구사항에 맞는 동작을 확정해 양쪽이 일치하게 유지하세요.
---
Nitpick comments:
In `@CLAUDE.md`:
- Around line 28-30: Update the architecture description in CLAUDE.md to match
the current package structure, including the added controller, dto, repository,
service, and converter packages under domain/deck and domain/job. Alternatively,
explicitly state that entity/enums-only is an intentional rule and clarify how
these existing packages comply; ensure the documentation consistently reflects
one of these interpretations.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 17190f2f-a223-449e-897c-25b3836bcbab
📒 Files selected for processing (37)
.gitignoreCLAUDE.mddatabase/DATA_DICTIONARY.mddatabase/README.mddatabase/data/companies.jsonldatabase/data/job_postings.jsonldatabase/load.pydatabase/requirements.txtdatabase/schema.sqlsrc/main/java/com/leets7th/job_is_be/JobIsBeApplication.javasrc/main/java/com/leets7th/job_is_be/domain/deck/controller/BriefingController.javasrc/main/java/com/leets7th/job_is_be/domain/deck/controller/CardController.javasrc/main/java/com/leets7th/job_is_be/domain/deck/controller/DeckController.javasrc/main/java/com/leets7th/job_is_be/domain/deck/dto/BriefingResponse.javasrc/main/java/com/leets7th/job_is_be/domain/deck/dto/CardResponse.javasrc/main/java/com/leets7th/job_is_be/domain/deck/entity/Deck.javasrc/main/java/com/leets7th/job_is_be/domain/deck/repository/CardRepository.javasrc/main/java/com/leets7th/job_is_be/domain/deck/repository/DeckRepository.javasrc/main/java/com/leets7th/job_is_be/domain/deck/service/BriefingService.javasrc/main/java/com/leets7th/job_is_be/domain/deck/service/CardService.javasrc/main/java/com/leets7th/job_is_be/domain/deck/service/RecommendationService.javasrc/main/java/com/leets7th/job_is_be/domain/job/converter/JobPostingConverter.javasrc/main/java/com/leets7th/job_is_be/domain/job/entity/Company.javasrc/main/java/com/leets7th/job_is_be/domain/job/entity/Job.javasrc/main/java/com/leets7th/job_is_be/domain/job/entity/JobPosting.javasrc/main/java/com/leets7th/job_is_be/domain/job/repository/JobPostingRepository.javasrc/main/java/com/leets7th/job_is_be/domain/job/repository/JobRepository.javasrc/main/java/com/leets7th/job_is_be/domain/job/service/JobPostingSyncService.javasrc/main/java/com/leets7th/job_is_be/global/config/SecurityConfig.javasrc/main/java/com/leets7th/job_is_be/global/config/WebConfig.javasrc/main/java/com/leets7th/job_is_be/global/status/ErrorStatus.javasrc/main/java/com/leets7th/job_is_be/global/status/SuccessStatus.javasrc/test/java/com/leets7th/job_is_be/domain/deck/service/BriefingServiceTest.javasrc/test/java/com/leets7th/job_is_be/domain/deck/service/CardServiceTest.javasrc/test/java/com/leets7th/job_is_be/domain/deck/service/RecommendationServiceTest.javasrc/test/java/com/leets7th/job_is_be/domain/job/converter/JobPostingConverterTest.javasrc/test/java/com/leets7th/job_is_be/domain/job/service/JobPostingSyncServiceTest.java
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/test/java/com/leets7th/job_is_be/domain/job/service/JobPostingSyncServiceTest.java (1)
44-104: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueLGTM!
다만, 모든 테스트가 단일 슬라이스(
hasNext() = false)만 검증하므로, 다중 페이지 순회(chunk.hasNext() == true시나리오)에 대한 테스트 추가를 권장합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/test/java/com/leets7th/job_is_be/domain/job/service/JobPostingSyncServiceTest.java` around lines 44 - 104, JobPostingSyncServiceTest의 단일 슬라이스 테스트만으로는 다음 페이지 순회가 검증되지 않는다. sync() 또는 syncOnStartup() 호출 시 jobPostingRepository.findAll(any())가 첫 번째 Slice에서는 hasNext()가 true이고 이후 Slice에서는 false를 반환하도록 설정하는 테스트를 추가하고, 모든 페이지의 JobPosting이 처리·저장 또는 갱신되는지 검증하라.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@src/main/java/com/leets7th/job_is_be/domain/job/service/JobPostingSyncService.java`:
- Around line 18-49: Remove the class-level transaction covering
JobPostingSyncService.sync and move each page’s upsert work into a separately
proxied transactional operation, such as a dedicated bean method or
TransactionTemplate. Update sync to invoke that operation once per fetched page,
preserving PAGE_SIZE pagination while ensuring each chunk commits or rolls back
independently and avoiding self-invocation of a transactional method.
---
Nitpick comments:
In
`@src/test/java/com/leets7th/job_is_be/domain/job/service/JobPostingSyncServiceTest.java`:
- Around line 44-104: JobPostingSyncServiceTest의 단일 슬라이스 테스트만으로는 다음 페이지 순회가 검증되지
않는다. sync() 또는 syncOnStartup() 호출 시 jobPostingRepository.findAll(any())가 첫 번째
Slice에서는 hasNext()가 true이고 이후 Slice에서는 false를 반환하도록 설정하는 테스트를 추가하고, 모든 페이지의
JobPosting이 처리·저장 또는 갱신되는지 검증하라.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 7adfb171-0aaa-49e1-a310-e69e1cca5503
📒 Files selected for processing (14)
.gitignoresrc/main/java/com/leets7th/job_is_be/domain/deck/dto/CardResponse.javasrc/main/java/com/leets7th/job_is_be/domain/deck/service/BriefingService.javasrc/main/java/com/leets7th/job_is_be/domain/deck/service/RecommendationService.javasrc/main/java/com/leets7th/job_is_be/domain/job/converter/JobPostingConverter.javasrc/main/java/com/leets7th/job_is_be/domain/job/entity/Company.javasrc/main/java/com/leets7th/job_is_be/domain/job/entity/Job.javasrc/main/java/com/leets7th/job_is_be/domain/job/entity/JobPosting.javasrc/main/java/com/leets7th/job_is_be/domain/job/repository/JobPostingRepository.javasrc/main/java/com/leets7th/job_is_be/domain/job/repository/JobRepository.javasrc/main/java/com/leets7th/job_is_be/domain/job/service/JobPostingSyncService.javasrc/main/java/com/leets7th/job_is_be/global/base/BaseEntity.javasrc/test/java/com/leets7th/job_is_be/domain/job/converter/JobPostingConverterTest.javasrc/test/java/com/leets7th/job_is_be/domain/job/service/JobPostingSyncServiceTest.java
🚧 Files skipped from review as they are similar to previous changes (9)
- src/main/java/com/leets7th/job_is_be/domain/job/repository/JobRepository.java
- src/main/java/com/leets7th/job_is_be/domain/deck/service/RecommendationService.java
- src/main/java/com/leets7th/job_is_be/domain/job/entity/Job.java
- src/main/java/com/leets7th/job_is_be/domain/deck/dto/CardResponse.java
- src/main/java/com/leets7th/job_is_be/domain/job/converter/JobPostingConverter.java
- src/main/java/com/leets7th/job_is_be/domain/job/entity/JobPosting.java
- src/test/java/com/leets7th/job_is_be/domain/job/converter/JobPostingConverterTest.java
- src/main/java/com/leets7th/job_is_be/domain/job/entity/Company.java
- src/main/java/com/leets7th/job_is_be/domain/deck/service/BriefingService.java
yukyoungs
left a comment
There was a problem hiding this comment.
기능구현 파이프라인부터 단위 테스트까지 작성해주셨네용 좋습니다!! 화이팅
The merge-base changed after approval.
The merge-base changed after approval.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/main/java/com/leets7th/job_is_be/domain/deck/entity/Deck.java`:
- Around line 49-53: Deck의 `@Builder가` user와 deckDate만 받지 않도록 all-args 생성자를 기준으로
변경하세요. emptyReason, firstOpenedAt, completedAt을 포함한 모든 엔티티 상태가 빌더에서 설정되게 하되, 기존
필드와 생성자 동작은 유지하세요.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 00e5b8e9-941d-47e5-9392-86b192a80c1a
📒 Files selected for processing (4)
src/main/java/com/leets7th/job_is_be/domain/deck/entity/Deck.javasrc/main/java/com/leets7th/job_is_be/domain/deck/service/BriefingService.javasrc/main/java/com/leets7th/job_is_be/domain/deck/service/RecommendationService.javasrc/main/java/com/leets7th/job_is_be/domain/personality/entity/PersonalityTest.java
🚧 Files skipped from review as they are similar to previous changes (2)
- src/main/java/com/leets7th/job_is_be/domain/deck/service/BriefingService.java
- src/main/java/com/leets7th/job_is_be/domain/deck/service/RecommendationService.java
The merge-base changed after approval.
#️⃣ 관련 이슈
closed #17
크롤링으로 수집한 공고 데이터를 카드 덱 형태로 변환·추천·제공하는 기능을 구현했습니다.
PR 유형
PR Checklist
🧩 작업 내용
@EnableScheduling) 설정RecommendationService— 사용자에게 노출할 카드(공고)를 추천하는 로직 구현JobPostingSyncService— 크롤링된 공고 데이터를 동기화하는 서비스 구현JobPostingConverter— 크롤링 원본 데이터를JobPosting엔티티로 변환DeckController— 카드 덱 조회 API 엔드포인트 추가RecommendationServiceTest,JobPostingConverterTest,JobPostingSyncServiceTest) 작성📸 스크린샷(선택)
📣 To Reviewers
Summary by CodeRabbit