Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
108 commits
Select commit Hold shift + click to select a range
804d485
chore: 이슈 및 PR 템플릿 추가 (#3)
sjungwon03 Jul 2, 2026
82216b9
chore: Spring Boot 3.5.16 다운그레이드 및 에러 구조 분리, AI 컨벤션 문서 추가 (#5)
myh7754 Jul 3, 2026
820acc3
feat: BaseEntity 및 도메인 엔티티, 스키마 SQL 추가 (#10)
myh7754 Jul 8, 2026
684b389
chore: CodeRabbit 리뷰 설정 파일 추가 (#8)
sjungwon03 Jul 8, 2026
48d4648
chore: Claude Code 하네스 재구성 (.claude/rules + 스킬/커맨드/에이전트/훅) (#12)
sjungwon03 Jul 8, 2026
514a025
feat: 온보딩 프로필 제출 API 추가 (#17)
jowoojin030 Jul 8, 2026
81349a9
chore: 백엔드 CI 워크플로 추가 (GitHub Actions) (#18)
sjungwon03 Jul 8, 2026
6178ec4
feat: Flyway 도입 및 로컬 MySQL 전환 (V1 초기 스키마 + V2 missing_count) (#14)
myh7754 Jul 9, 2026
c5a9701
chore: IntelliJ 코드 스타일 XML 추가 (google-java-format AOSP) (#23)
sjungwon03 Jul 10, 2026
f710724
refac: ApiResponse meta를 Map에서 PageMeta record로 변경 (#30)
myh7754 Jul 10, 2026
c730cf0
docs: API 경로를 엔티티 리소스 중심으로 재설계 (#32)
sjungwon03 Jul 10, 2026
4a59c2b
feat: 카카오, 구글, 네이버 소셜 로그인 인증 기능 추가 (#22)
sjungwon03 Jul 13, 2026
b1f4505
docs: Claude Code 코드 포맷(Google AOSP Style) 규칙 문서화 (#34)
sjungwon03 Jul 13, 2026
b17cdd1
feat: 앱 로그를 Log4j2 + JDBC로 app_logs 테이블에 적재 (#42)
sjungwon03 Jul 15, 2026
ca4fbe7
feat: 관리자 - 사용자 관리 API 추가 (#44)
sjungwon03 Jul 15, 2026
dad5519
feat: 관리자 - 정책/지역 관리 API 추가 (#47)
sjungwon03 Jul 15, 2026
9c69881
feat: 관리자 - 로그인 이력 API 추가 (#59)
sjungwon03 Jul 15, 2026
a166aee
feat: 관리자 - 검색 로그 조회 API 추가 (#53)
sjungwon03 Jul 15, 2026
977a64f
feat: 관리자 - 정책 신청 관리 API 추가 (#49)
sjungwon03 Jul 15, 2026
2822909
feat: 관리자 - 커뮤니티 관리 API 추가 (#51)
sjungwon03 Jul 15, 2026
ec04545
feat: 관리자 - 애플리케이션 로그 조회 API 추가 (#55)
sjungwon03 Jul 15, 2026
fc37de5
feat: 관리자 - 배치 작업 로그 조회 API 추가 (#57)
sjungwon03 Jul 15, 2026
402a020
chore: regions 지역 마스터 시드 마이그레이션 추가 (V3) (#38)
myh7754 Jul 15, 2026
58bacf7
fix: 관리자 애플리케이션 로그 API의 AppLog→ApplicationLog 리네임 누락 정리 (#62)
sjungwon03 Jul 15, 2026
8122597
fix: Flyway V4 마이그레이션 버전 충돌 해소 (정책 soft delete → V6) (#64)
sjungwon03 Jul 15, 2026
dd3b45f
fix: db/seed의 V3__seed_regions.sql과 마이그레이션 버전 충돌 해소 (V3→V4로 원복) (#66)
sjungwon03 Jul 15, 2026
9be0647
feat: 관리자 - 정책 수집 요약 조회 API 추가 (policy-sync-jobs/summary) (#72)
sjungwon03 Jul 15, 2026
989d7da
fix: 관리자 커뮤니티 게시글/댓글 API 경로를 다른 관리자 API와 일관되게 정리 (#70)
sjungwon03 Jul 15, 2026
92daab1
refac: refresh token 재발급(rotate) 제거, 발급 시점 TTL까지 그대로 재사용 (#68)
sjungwon03 Jul 15, 2026
8a72515
feat: 온통청년 정책 데이터 수집 배치(스케줄러) 구현 (#73)
myh7754 Jul 16, 2026
caec61b
feat: 최근 본 정책 기능 추가 (정책 상세 조회 시 기록 + 목록 API) (#75)
sjungwon03 Jul 16, 2026
d3ae95f
feat: 정책 상세 응답 지역을 코드→이름(RegionResponse)으로 보강 (#77)
myh7754 Jul 16, 2026
bda114d
feat: 게시판 CRUD API 추가 (#81)
seohyeon-tomato Jul 16, 2026
efbc24a
refac: 어드민 코드를 최상위 admin 도메인 패키지로 분리 (#83)
sjungwon03 Jul 19, 2026
38f471e
feat: 정책 목록 조회 공개 API (GET /api/v1/policies) (#79) (#84)
myh7754 Jul 20, 2026
d15fe44
feat: 정책 목록 keyword/region/age 검색 필터 추가 (#86) (#87)
myh7754 Jul 20, 2026
7d89e00
feat: 정책 관리(PolicyApplication) + 체크리스트(ApplicationChecklist) 구현 (#25)
canfactor Jul 20, 2026
8923fb6
feat: 정책별 STOMP 실시간 채팅 지원 (#89)
sjungwon03 Jul 20, 2026
74afca6
feat: 게시글 조회 시 조회수 증가 및 Redis 중복 방지 적용 (#97) (#99)
sjungwon03 Jul 20, 2026
d8e6d61
feat: MinIO 파일 업로드 및 다운로드 API 추가 (#96)
sjungwon03 Jul 20, 2026
d01ad85
feat: user_profiles 온보딩 항목 확장 및 지역 API 추가 (#101)
sjungwon03 Jul 20, 2026
f14e3c1
fix: 정책 카드 지역 라벨 조립을 백엔드에서 제거하고 원본 지역 목록으로 응답 (#100)
sjungwon03 Jul 20, 2026
1698596
feat: 마이페이지 프로필 조회 GET API 추가 (#104)
sjungwon03 Jul 20, 2026
acf49a0
fix: 정책 신청 재등록 시 soft delete 행 재활성화 (#107)
sjungwon03 Jul 20, 2026
4584415
feat: 게시글 이미지 첨부 저장 (#109)
sjungwon03 Jul 20, 2026
66ec075
refac: OnboardingController를 UserProfileController로 도메인 엔티티 기준 리네이밍 (…
sjungwon03 Jul 21, 2026
be8a5bc
feat: 정책 비교 API 추가 (policy-comparisons) (#40)
imi21123 Jul 21, 2026
4e82a60
feat: 게시글 목록 조회에 검색어/카테고리 필터와 정렬 지원 추가 (#114)
sjungwon03 Jul 21, 2026
f184f1f
fix: region=전국이 결과를 좁히던 동작 제거 (#93) (#94)
myh7754 Jul 21, 2026
fdc7c1b
feat: 온보딩 프로필 수정(PATCH) API 추가 (#121)
sjungwon03 Jul 21, 2026
4e92e69
fix: 지역 목록을 한글 가나다순으로 정렬 (#117)
sjungwon03 Jul 21, 2026
dc87ee9
feat: 지역 선택 시 전국 정책을 뒤로 미루는 정렬 (#120) (#122)
myh7754 Jul 21, 2026
2bd85da
refac: 정책 신청관리 register → create 네이밍 통일 및 Javadoc 주석 정리 (#91)
canfactor Jul 21, 2026
ccfcfa3
fix: 신청기간 정보 없는 정책이 사업기간 종료 후에도 상시로 노출되는 문제 수정 (#124)
sjungwon03 Jul 22, 2026
0c7b91d
feat: 정책 목록 조회에 취업상태(jobCode) 필터·정렬 추가 (#133) (#134)
myh7754 Jul 22, 2026
5cb8673
feat: 맞춤정책 조회 API 추가 (#132)
imi21123 Jul 22, 2026
a9f992e
chore: 로컬 개발 CORS 허용 origin에 5174 포트 추가 (#138)
sjungwon03 Jul 22, 2026
bfcd2c7
docs: 리드미 새로 작성 (#136)
sjungwon03 Jul 22, 2026
04530af
docs: 리드미에 구현 특징 섹션 추가 (#158)
sjungwon03 Jul 22, 2026
286372a
fix: 정책 채팅 STOMP 브로커에 하트비트 설정 추가 (#156)
sjungwon03 Jul 22, 2026
41b39ff
fix: 프레임워크 표준 4xx 예외를 catch-all 500 대신 정확한 상태코드로 처리
sjungwon03 Jul 22, 2026
46629b6
fix: 정책 채팅 구독 receipt 응답 추가 (#162)
sjungwon03 Jul 22, 2026
2b149e7
fix: 온보딩 프로필 제출 API 인증 필수화 및 본인 검증 추가
sjungwon03 Jul 22, 2026
0b9b809
fix: posts 공개 API 입력 검증 보강 (category 패턴·자유글 정책 거부·본문 길이)
sjungwon03 Jul 22, 2026
d16a0af
fix: auth 견고성 보강 — soft-delete 인증 차단, 제재 시 토큰 회수, state 소비 원자화
sjungwon03 Jul 22, 2026
84623b6
fix: 프로필 생성 API 미인증 접근·타인 userId 제출(IDOR) 차단
sjungwon03 Jul 22, 2026
7651100
fix: BoardErrorCode 중복 해소(COMMENT_NOT_FOUND B005 재배번) 및 board 패키지를 po…
sjungwon03 Jul 22, 2026
cea6178
fix: 삭제된 신청의 체크리스트 접근 시 500 대신 P005로 응답
sjungwon03 Jul 22, 2026
260a4f4
fix: 관리자 숨김 정책의 배치 재노출 방지 (#175)
sjungwon03 Jul 22, 2026
3065f73
fix: 비공개 정책의 비교와 신청 등록 차단 (#177)
sjungwon03 Jul 22, 2026
e26c2d3
fix: 목록 API 정렬과 페이지 입력을 방어 (#178)
sjungwon03 Jul 22, 2026
b2c63dc
fix: CORS 허용 Origin을 환경 설정으로 외부화 (#179)
sjungwon03 Jul 22, 2026
2ebdb71
fix: 파일 장애 로그와 정책 검색 로그를 추가 (#183)
sjungwon03 Jul 22, 2026
ef04bea
fix: 게시글 목록 category를 enum으로 검증 (#185)
sjungwon03 Jul 22, 2026
c1eb845
Merge branch 'fix/159-framework-4xx-handlers' into dev
sjungwon03 Jul 22, 2026
ad4fc47
Merge branch 'fix/163-profile-authz' into dev
sjungwon03 Jul 22, 2026
4999f8c
Merge branch 'fix/165-posts-input-validation' into dev
sjungwon03 Jul 22, 2026
e650e99
Merge branch 'fix/167-auth-robustness' into dev
sjungwon03 Jul 22, 2026
ffaa557
Merge branch 'fix/168-profile-idor' into dev
sjungwon03 Jul 22, 2026
7fea5af
Merge branch 'fix/169-checklist-orphan-500' into dev
sjungwon03 Jul 22, 2026
aed907d
Merge branch 'fix/170-post-domain-cleanup' into dev
sjungwon03 Jul 22, 2026
4cd2f71
Merge branch 'fix/175-admin-hidden-sync' into dev
sjungwon03 Jul 22, 2026
4590da1
Merge branch 'fix/177-policy-public-access' into dev
sjungwon03 Jul 22, 2026
fd32da5
Merge branch 'fix/178-pagination-input-guard' into dev
sjungwon03 Jul 22, 2026
efc7c46
Merge branch 'fix/179-configurable-origins' into dev
sjungwon03 Jul 22, 2026
068bf6c
Merge branch 'fix/183-file-search-logging' into dev
sjungwon03 Jul 22, 2026
acc6f97
Merge branch 'fix/185-post-category-validation' into dev
sjungwon03 Jul 22, 2026
48161b1
fix: 로그 비동기화 및 인프라 견고성 (#187) (#188)
sjungwon03 Jul 22, 2026
f999c18
fix: 프로필 용어 리팩토링 및 값 검증 (#189) (#190)
sjungwon03 Jul 22, 2026
485bce4
fix: 정책 신청 API 리팩토링 및 쿼리 최적화 (#191) (#192)
sjungwon03 Jul 22, 2026
892d317
fix: 조회수 IP 스푸핑 방지 및 Redis 캐시 오버헤드 차단 (#193) (#194)
sjungwon03 Jul 22, 2026
9b0e007
fix: 동기화 배치 안정성 강화 (백오프 & 실패 임계치) (#195) (#196)
sjungwon03 Jul 22, 2026
d6fb1d1
fix: API 명세 갱신 및 로버스트니스 강화 (#197) (#198)
sjungwon03 Jul 22, 2026
d9a0eb0
fix: 정책 키워드 검색에 지역명 부분일치 포함 (#200)
myh7754 Jul 23, 2026
83ebe70
feat: 게시글 목록 응답에 본문 요약 필드 추가 (#202)
sjungwon03 Jul 23, 2026
ec6b7ac
fix: 정책 수집 실패율 초과 메시지의 포맷 문자열 이스케이프 누락 수정 (#204)
myh7754 Jul 23, 2026
d9c883e
feat: 게시글 댓글 CRUD API 추가 (#212)
sjungwon03 Jul 24, 2026
4c15b62
fix: 정책 수집을 매 회차 실패시키던 컬럼 길이 상한 확대 (#209)
myh7754 Jul 24, 2026
6a948b4
chore: docker compose로 프론트+백엔드 통합 기동 및 배포 도메인 CORS 대응 (#211)
sjungwon03 Jul 24, 2026
6fa6b08
docs: Swagger(OpenAPI) API 문서 도입 (#214)
sjungwon03 Jul 24, 2026
85837a6
docs: 공통 에러 응답(ErrorResponse) 스키마와 4XX/5XX 기본 응답 추가 (#216)
sjungwon03 Jul 24, 2026
53471de
docs: PageMeta에 example 명시해 Swagger 의미없는 샘플값 제거 (#218)
sjungwon03 Jul 24, 2026
d1d5d57
fix: PolicyMapper에 컬럼 길이 상한 절단 가드 추가 (#221)
sjungwon03 Jul 24, 2026
1b4c43d
feat: 정책 목록/상세 응답에 자격조건 코드 필드 추가 (#220)
sjungwon03 Jul 24, 2026
7ed93e2
fix: 실패율 초과 FAILED 이력에 신규/변경/누락/실패 건수를 기록한다 (#219)
sjungwon03 Jul 24, 2026
6ab54cc
fix: PolicySyncServiceTest 전량실패 테스트가 길이절단 가드와 충돌해 CI 실패 (#224)
sjungwon03 Jul 24, 2026
d742237
feat: 검색엔진 등록용 동적 sitemap.xml 엔드포인트 추가 (#226)
sjungwon03 Jul 24, 2026
0f47087
merge: main을 dev에 병합하여 #227 PR 컨플릭트 해결
sjungwon03 Jul 24, 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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ MINIO_READ_TIMEOUT=30s
# 예) ALLOWED_ORIGINS=https://youthpick.samchon.cloud
ALLOWED_ORIGINS=http://localhost:3000,http://localhost:5173,http://localhost:5174

# sitemap.xml의 <loc> 절대 URL을 만들 때 쓰는 프론트엔드 배포 도메인. 로컬은 기본값(운영 도메인)을 그대로 둬도 무방하다.
FRONTEND_URL=https://youthpick.samchon.cloud

# ===== 온통청년 정책 API (배치 수집용 — 비우면 배치만 실패하고 앱은 뜬다) =====
# 발급: https://www.youthcenter.go.kr 회원가입 후 open api 인증키 신청
YOUTH_API_KEY=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
"/api/v1/policies/**",
"/api/v1/health")
.permitAll()
// 검색엔진 크롤러용 sitemap — 인증 없이 항상 열려 있어야 한다.
.requestMatchers("/sitemap.xml")
.permitAll()
// 게시글 조회는 공개, 생성·수정·삭제는 회원 전용
.requestMatchers(HttpMethod.GET, "/api/v1/posts/**")
.permitAll()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.bop.youthpick.global.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

/** 프론트엔드 배포 도메인. sitemap.xml처럼 절대 URL을 만들어야 하는 곳에서 쓴다. */
@ConfigurationProperties(prefix = "youthpick.site")
public record SiteProperties(String frontendUrl) {}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.bop.youthpick.post.repository;

import com.bop.youthpick.post.entity.Post;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
Expand All @@ -17,6 +19,15 @@ public interface PostRepository extends JpaRepository<Post, Long>, JpaSpecificat
@EntityGraph(attributePaths = {"user", "policy"}) // jpql을 써서 구현하는 방법도 있음
Optional<Post> findByIdAndDeletedAtIsNull(Long id); // 쿼리메서드

// sitemap.xml 생성 전용 — 목록/상세 API처럼 user/policy까지 끌고 올 필요가 없어 id/updatedAt만 projection한다.
List<PostSitemapView> findAllByDeletedAtIsNullOrderByUpdatedAtDesc();

interface PostSitemapView {
Long getId();

LocalDateTime getUpdatedAt();
}

// JpaSpecificationExecutor의 기본 findAll(Specification, Pageable)을 재선언해
// @EntityGraph로 목록 조회 시 user/policy N+1을 막는다(PostSummaryResponse가 둘 다 참조).
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.bop.youthpick.sitemap.controller;

import com.bop.youthpick.sitemap.service.SitemapService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

/**
* sitemap.xml은 URL 프로토콜 특성상 사이트 루트("/sitemap.xml")에서 서빙해야 하므로 다른 컨트롤러처럼 {@code /api/v1} 아래 두지 않는다.
* nginx가 이 경로를 백엔드로 프록시한다(front-end/nginx.conf 참고).
*/
@Tag(name = "SEO")
@RestController
@RequiredArgsConstructor
public class SitemapController {

private final SitemapService sitemapService;

@Operation(
summary = "sitemap.xml 조회",
description = "정적 페이지 + 공개 커뮤니티 게시글 URL을 담은 sitemap을 반환한다.")
@GetMapping(value = "/sitemap.xml", produces = MediaType.APPLICATION_XML_VALUE)
public ResponseEntity<String> sitemap() {
return ResponseEntity.ok(sitemapService.generate());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.bop.youthpick.sitemap.service;

import com.bop.youthpick.global.config.SiteProperties;
import com.bop.youthpick.post.repository.PostRepository;
import java.time.format.DateTimeFormatter;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
* 검색엔진(Search Console)에 등록할 sitemap.xml을 요청마다 생성한다. 프론트는 SSR 없는 Vite SPA라 빌드 시점에는 게시글처럼 동적인 URL을 알
* 수 없으므로, 백엔드가 DB를 조회해 매 요청 최신 목록으로 만들어 낸다.
*/
@Service
@RequiredArgsConstructor
public class SitemapService {

private static final DateTimeFormatter LASTMOD_FORMATTER = DateTimeFormatter.ISO_LOCAL_DATE;

private final PostRepository postRepository;
private final SiteProperties siteProperties;

@Transactional(readOnly = true)
public String generate() {
String baseUrl = siteProperties.frontendUrl();
StringBuilder xml = new StringBuilder();
xml.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
xml.append("<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n");

appendUrl(xml, baseUrl + "/", null, "daily", "1.0");
appendUrl(xml, baseUrl + "/search", null, "daily", "0.8");
appendUrl(xml, baseUrl + "/community", null, "hourly", "0.8");

for (PostRepository.PostSitemapView post :

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: sitemap.xml URL 수 제한 누락

sitemap.xml은 파일당 최대 50,000개 URL 허용(Google/Bing 공통 규칙)인데, 현재 모든 게시글을 제한 없이 하나의 sitemap에 넣는다. 게시글이 5만 개를 넘어가면 검색엔진이 sitemap을 거부한다. findAllByDeletedAtIsNullOrderByUpdatedAtDesc() 대신 페이지네이션 + sitemap index(<sitemapindex>)를 도입하거나, 상한을 둬서 5만 개 이하로 자른다.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

postRepository.findAllByDeletedAtIsNullOrderByUpdatedAtDesc()) {
String lastmod = LASTMOD_FORMATTER.format(post.getUpdatedAt());
appendUrl(xml, baseUrl + "/community/" + post.getId(), lastmod, "weekly", "0.6");
}

xml.append("</urlset>\n");
return xml.toString();
}

private void appendUrl(
StringBuilder xml, String loc, String lastmod, String changefreq, String priority) {
xml.append(" <url>\n");
xml.append(" <loc>").append(loc).append("</loc>\n");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: XML 특수문자 이스케이프 누락

appendUrl()에서 <loc>, <lastmod> 등에 값을 그대로 붙여넣고 있다. 현재는 baseUrl이 설정값이고 post.getId()가 숫자라 즉각적 위험이 낮지만, 향후 URL 구성이 바뀌면 & 등 XML 파싱을 깨뜨릴 수 있다. 값을 이스케이프하는 유틸이나 간단한 replace 체이닝으로 &&amp;, <&lt; 등을 처리하는 게 안전하다.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

if (lastmod != null) {
xml.append(" <lastmod>").append(lastmod).append("</lastmod>\n");
}
xml.append(" <changefreq>").append(changefreq).append("</changefreq>\n");
xml.append(" <priority>").append(priority).append("</priority>\n");
xml.append(" </url>\n");
}
}
3 changes: 3 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ spring:
youthpick:
cors:
allowed-origins: ${ALLOWED_ORIGINS:http://localhost:3000,http://localhost:5173,http://localhost:5174}
site:
# sitemap.xml에 절대 URL(<loc>)을 만들 때 쓰는 프론트엔드 배포 도메인.
frontend-url: ${FRONTEND_URL:https://youthpick.samchon.cloud}
file:
max-size: ${FILE_MAX_SIZE:10MB}
minio:
Expand Down
Loading