이슈 후속(S7) 마감 + 릴리스 버전 확정. 기능(S7): - 사이트맵 전체 재생성 완료/실패 시 실행 관리자에게 알림(수동 실행 한정). 스케줄러·증분·봇 재생성은 알림 없음(triggered_by 부재 시 skip). - 최종 실패 전용 알림 + 재생성 전용 큐 + WS 진행상황 payload 봉투 정정. - Reverb OFF 폴링 폴백 경로 수정(실브라우저 폴링에서만 드러난 결함). 버전(D28 — 정정, D20 번복): - 릴리스를 7.0.5→7.0.6(patch)로 확정. 7.1.0(minor)은 다른 브랜치에서 별도 진행. - config/app.php + .env + README + INSTALL + CHANGELOG + 모듈 3 requires 를 7.0.6 로. - D19 SEO 캐시 이행 업그레이드 스텝을 Upgrade_7_0_6 로 리네임. 이유: 스텝 버전 게이팅이 version<=toVersion 이라, 7.1.0 이름이면 7.0.5→7.0.6 업그레이드에서 이행이 실행되지 않는다. 검증: composer test-smoke OK(2·29) · 재생성 알림/업그레이드 이행/진행상황 테스트 green · version-sync/version-bump(new-bump)/extension-require green · audit 0 error.
15 KiB
Seo API 레퍼런스
소유: 코어 · 생성:
php artisan api:docgen(실측 기반). @generated 블록은 재생성 시 갱신되며, 사람이 작성한 설명은 보존됩니다.
TL;DR (5초 요약)
1. 이 문서는 실제 API 호출로 실측한 Seo 엔드포인트 레퍼런스입니다
2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 실측 응답 필드 표
3. 응답 필드의 예시값은 실제 호출 응답에서 관측된 값입니다
4. 갱신: 코드 변경 후 php artisan api:docgen 재실행
5. 설명(TODO) 칸은 사람이 채웁니다
GET /api/admin/seo/cached-urls
- 라우트명:
api.admin.seo.cached-urls - 컨트롤러:
App\Http\Controllers\Api\Admin\SeoCacheController@cachedUrls - 인증/권한:
auth:sanctum+permission:core.settings.read
요청 파라미터
요청 파라미터 없음.
요청 예시
GET /api/admin/seo/cached-urls HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}
응답 필드 (data 내부)
단건 응답: data 객체의 필드.
| 필드 | 타입 | 실측 예시값 | 용도/설명 |
|---|---|---|---|
| urls | array | [] |
현재 사전 렌더 캐시에 남아 있는 봇 대상 페이지 URL 목록 (SeoCacheManager 인덱스에서 조회). |
| count | integer | 0 |
캐시된 URL 개수 (urls 배열 길이). |
응답 예시
HTTP/1.1 200
{
"success": true,
"message": "messages.success",
"data": {
"urls": [],
"count": 0
}
}
에러 응답
| 상태코드 | 의미 | 발생 조건 |
|---|---|---|
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
| 403 | Forbidden | 요구 권한(core.settings.read)이 없는 경우 |
설명
SeoCacheManager 인덱스에서 현재 캐시된 SEO 페이지 URL 목록과 개수를 조회합니다. 어떤 봇 대상 페이지가 사전 렌더 캐시로 남아 있는지 확인하는 진단 용도로 사용합니다.
POST /api/admin/seo/clear-cache
- 라우트명:
api.admin.seo.clear-cache - 컨트롤러:
App\Http\Controllers\Api\Admin\SeoCacheController@clearCache - 인증/권한:
auth:sanctum+permission:core.settings.update
요청 파라미터
| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 |
|---|---|---|---|---|---|
| layout | body | string | 아니오 | — | 무효화 대상 레이아웃명. 지정 시 해당 레이아웃의 SEO 캐시만 삭제하고 무효화된 항목 수를 반환하며, 미지정 시 전체 SEO 캐시를 삭제한다. |
| module | body | string | 아니오 | — | 모듈 식별자 필터. 검증 규칙에는 정의되어 있으나 현재 컨트롤러 로직에서는 사용되지 않는다(향후 모듈 단위 캐시 무효화 확장 예약 필드). |
요청 예시
POST /api/admin/seo/clear-cache HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}
Content-Type: application/json
{
"layout": "예시값",
"module": "예시값"
}
응답 필드 (data 내부)
응답 예시
에러 응답
| 상태코드 | 의미 | 발생 조건 |
|---|---|---|
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
| 403 | Forbidden | 요구 권한(core.settings.update)이 없는 경우 |
| 422 | Unprocessable Entity | 요청 파라미터가 검증 규칙을 위반한 경우 (error.errors 에 필드별 메시지) |
설명
SEO 사전 렌더 캐시를 삭제합니다. layout 을 지정하면 해당 레이아웃 캐시만 무효화하고, 지정하지 않으면 전체 SEO 캐시를 삭제합니다. 응답의 data.cleared 는 layout 지정 시 무효화된 항목 수(정수), 미지정 시 문자열 "all" 입니다. 설정이나 콘텐츠 변경 후 오래된 봇 응답이 캐시로 남는 것을 방지할 때 사용합니다.
POST /api/admin/seo/sitemap/regenerate
- 라우트명:
api.admin.seo.sitemap.regenerate - 컨트롤러:
App\Http\Controllers\Api\Admin\SeoCacheController@regenerateSitemap - 인증/권한:
auth:sanctum+permission:core.settings.update
요청 파라미터
요청 파라미터 없음.
요청 예시
POST /api/admin/seo/sitemap/regenerate HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}
응답 필드 (data 내부)
단건 응답: data 객체의 필드 — GET /api/admin/seo/sitemap/status 와 동일한 스키마입니다.
| 필드 | 타입 | 실측 예시값 | 용도/설명 |
|---|---|---|---|
| last_updated_at | string|null | 2026-07-08T03:14:49+00:00 |
예약 시점 기준 마지막 생성 일시. 이번 재생성의 결과가 아니라 직전 생성 시각이며, 잡 완료 후 갱신됩니다 (아직 한 번도 생성되지 않았으면 null) |
| progress | object|null | {"status":"queued","mode":"full",...} |
재생성 진행상황. 예약 직후 status=queued, mode=full 로 즉시 기록됩니다. 필드 상세는 아래 상태 조회 API 참조 (진행 이력이 없으면 null) |
| realtime_enabled | boolean | false |
실시간 연결(WebSocket) 가능 여부. true 면 프론트가 진행상황 채널을 구독하고, false 면 상태 API 를 주기적으로 폴링합니다 |
응답 예시
HTTP/1.1 200
{
"success": true,
"message": "Sitemap 재생성을 시작했습니다. 완료까지 시간이 걸릴 수 있습니다.",
"data": {
"last_updated_at": "2026-07-08T03:14:49+00:00",
"progress": {
"status": "queued",
"mode": "full",
"started_at": "2026-07-08T05:20:00+00:00",
"finished_at": null,
"phase": null,
"urls": 0,
"url_count": null,
"child_count": null,
"message": null
},
"realtime_enabled": false
}
}
에러 응답
| 상태코드 | 의미 | 발생 조건 |
|---|---|---|
| 400 | Bad Request | SEO 설정에서 sitemap 생성이 비활성(seo.sitemap_enabled=false)인 경우 (seo.sitemap_disabled) |
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
| 403 | Forbidden | 요구 권한(core.settings.update)이 없는 경우 |
설명
sitemap.xml 재생성을 큐에 예약합니다. 관리자 수동 재생성은 현 생성 상태와 무관하게 항상 전체(full) 재생성입니다. 응답은 예약 접수 결과이며 생성 완료를 의미하지 않습니다 — 실제 생성은 큐 워커가 GenerateSitemapJob 을 처리할 때 수행되고, 완료 후 마지막 생성 시각이 갱신됩니다.
요청 스레드에서 동기 생성하지 않는 이유는 대용량(수백만 URL) 사이트에서 생성이 수 분~수십 분 걸려 관리자 요청이 메모리 초과/타임아웃으로 실패하기 때문입니다. 잡은 유니크 락(seo-sitemap)을 사용하므로 재생성을 연속으로 요청해도 동시에 여러 건이 실행되지 않고 한 건만 큐에 남습니다.
예약 직후 진행상황이 queued 로 기록되며, 이후 진행 단계는 GET /api/admin/seo/sitemap/status 로 조회합니다.
이 엔드포인트로 접수된 관리자 수동 재생성은 실행한 관리자의 ID 를 잡에 실어, 생성 완료 시 sitemap_regenerated, 실패 시 sitemap_regenerate_failed 알림이 그 관리자에게만 발송됩니다(기본 채널: 앱 내 알림). 스케줄러·리소스 변경(증분)·봇 캐시 미스로 유발된 재생성은 실행 관리자가 없어 알림을 보내지 않습니다. 알림 정의는 config/core.php 의 notification_definitions 에 있으며 관리자 알림 설정에서 채널을 조정할 수 있습니다.
GET /api/admin/seo/sitemap/status
- 라우트명:
api.admin.seo.sitemap.status - 컨트롤러:
App\Http\Controllers\Api\Admin\SeoCacheController@sitemapStatus - 인증/권한:
auth:sanctum+permission:core.settings.read
요청 파라미터
요청 파라미터 없음.
요청 예시
GET /api/admin/seo/sitemap/status HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}
응답 필드 (data 내부)
단건 응답: data 객체의 필드.
| 필드 | 타입 | 실측 예시값 | 용도/설명 |
|---|---|---|---|
| last_updated_at | string|null | 2026-07-08T03:14:49+00:00 |
마지막 생성 일시 (아직 한 번도 생성되지 않았으면 null) |
| progress | object|null | {"status":"running",...} |
재생성 진행상황. 진행 이력이 없으면 null |
| progress.status | string | running |
queued(예약) / running(기여자 처리 중) / writing(파일 작성 중) / completed(완료) / failed(실패) |
| progress.mode | string|null | full |
재생성 모드 (full / auto / incremental) |
| progress.phase | string|null | sirsoft-board |
현재 처리 중인 기여자 식별자 (running 단계에서만) |
| progress.urls | integer | 12000 |
누적 처리 URL 수 |
| progress.url_count | integer|null | 5000 |
완료 시 총 URL 수 |
| progress.child_count | integer|null | 2 |
완료 시 분할 파일 수 |
| progress.started_at | string|null | 2026-07-08T05:20:00+00:00 |
예약 시각 |
| progress.finished_at | string|null | 2026-07-08T05:21:30+00:00 |
완료/실패 시각 |
| progress.message | string|null | null |
실패 시 오류 메시지 |
| realtime_enabled | boolean | false |
실시간 연결(WebSocket) 가능 여부. true 면 프론트가 core.admin.seo.sitemap 채널을 구독하고, false 면 이 API 를 주기적으로 폴링합니다 |
응답 예시
HTTP/1.1 200
{
"success": true,
"message": "성공적으로 처리되었습니다.",
"data": {
"last_updated_at": "2026-07-08T03:14:49+00:00",
"progress": {
"status": "running",
"mode": "full",
"started_at": "2026-07-08T05:20:00+00:00",
"finished_at": null,
"phase": "sirsoft-board",
"urls": 12000,
"url_count": null,
"child_count": null,
"message": null
},
"realtime_enabled": false
}
}
에러 응답
| 상태코드 | 의미 | 발생 조건 |
|---|---|---|
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
| 403 | Forbidden | 요구 권한(core.settings.read)이 없는 경우 |
설명
Sitemap 재생성 진행상황과 실시간 연결 가능 여부를 조회합니다. SEO 탭 진입 시 초기 로드에 사용되며, realtime_enabled 가 false(웹소켓 미사용)일 때는 프론트가 진행 중(queued/running/writing) 동안 이 API 를 3초 간격으로 폴링합니다. 실시간 연결이 켜져 있으면 폴링 대신 core.admin.seo.sitemap 채널의 sitemap.progress.updated 이벤트로 즉시 갱신됩니다.
진행상황은 캐시(TTL 3600초)에 저장되므로, 잡이 비정상 종료해도 시간이 지나면 만료됩니다. 잡 최종 실패 시에는 progress.status=failed 로 고정됩니다.
GET /api/admin/seo/stats
- 라우트명:
api.admin.seo.stats - 컨트롤러:
App\Http\Controllers\Api\Admin\SeoCacheController@stats - 인증/권한:
auth:sanctum+permission:core.settings.read
요청 파라미터
요청 파라미터 없음.
요청 예시
GET /api/admin/seo/stats HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}
응답 필드 (data 내부)
단건 응답: data 객체의 필드.
| 필드 | 타입 | 실측 예시값 | 용도/설명 |
|---|---|---|---|
| overall | object | {"total_entries":0,"hits":0,"misses":0,"hit_rate":0,"avg_… |
최근 7일 전체 캐시 통계 집계. total_entries(기록 총 건수), hits(적중 건수), misses(미적중 건수), hit_rate(적중률 %, hits/total×100), avg_response_time_ms(미적중 시 평균 렌더링 소요 시간 ms, 데이터 없으면 null). |
| by_layout | array | [] |
레이아웃별 통계 목록 (layout_name 으로 그룹핑). 각 원소는 layout_name·total·hits·misses·hit_rate·avg_response_time_ms 를 가지며, 레이아웃 단위로 캐시 효율을 비교하는 용도. |
| by_module | array | [] |
모듈별 통계 목록 (module_identifier 로 그룹핑). 각 원소는 module_identifier·total·hits·misses·hit_rate·avg_response_time_ms 를 가지며, 어느 확장 모듈의 SEO 페이지가 캐시로 재사용되는지 파악하는 용도. |
응답 예시
HTTP/1.1 200
{
"success": true,
"message": "messages.success",
"data": {
"overall": {
"total_entries": 0,
"hits": 0,
"misses": 0,
"hit_rate": 0,
"avg_response_time_ms": null
},
"by_layout": [],
"by_module": []
}
}
에러 응답
| 상태코드 | 의미 | 발생 조건 |
|---|---|---|
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
| 403 | Forbidden | 요구 권한(core.settings.read)이 없는 경우 |
설명
SEO 캐시 적중 현황을 최근 7일 기준으로 전체·레이아웃별·모듈별로 조회합니다. 봇 대상 사전 렌더 캐시가 얼마나 효과적으로 재사용되는지(적중률, 렌더 비용 절감)를 모니터링하는 관리자 대시보드용 통계입니다.
POST /api/admin/seo/warmup
- 라우트명:
api.admin.seo.warmup - 컨트롤러:
App\Http\Controllers\Api\Admin\SeoCacheController@warmup - 인증/권한:
auth:sanctum+permission:core.settings.update
요청 파라미터
요청 파라미터 없음.
요청 예시
POST /api/admin/seo/warmup HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}
응답 필드 (data 내부)
응답 예시
에러 응답
| 상태코드 | 의미 | 발생 조건 |
|---|---|---|
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
| 403 | Forbidden | 요구 권한(core.settings.update)이 없는 경우 |
설명
SEO 캐시 워밍업(모든 SEO 레이아웃 사전 렌더)을 위한 엔드포인트입니다. 현재 컨트롤러는 실제 워밍업 로직 없이 status: dispatched 와 안내 메시지만 반환합니다(실 렌더링은 후속 구현 예정). 응답 성공은 요청 접수만을 의미하며 이 시점에 캐시가 채워지지는 않습니다.