공개 (@Tuwasduliebst 제보) 대응. - ckeditor5: 미참조 이미지 정리 엔진 신설 — 참조 판정(hash + 파일명 2토큰, filter_reference_sources 훅으로 확장 콘텐츠 등록) + 정리 커맨드(기본 꺼짐 옵트인, 3중 게이트) + 업로드 관리 화면(권한 분리·참조 배지·개별/선택 삭제) + 기설치본 설정 백필 upgrade step - board: 임시 첨부 GC(상시) + 소프트삭제 첨부 영구 정리(옵트인) + 설정 UI, 저장 payload 누락(F4)·빈 temp 디렉토리 잔존(F5) 수정 - ecommerce: 임시 상품 이미지 GC + 카테고리 삭제 시 이미지 파일 미삭제(S4) 수정 - page: 임시 첨부 GC - 코어: 고아 첨부 정리 커맨드(옵트인, site_logo 보호) + 사이트 로고 교체 시 구 로고 재편입 차단(S6) + DB 백업 미구현 501 정직화(B3) + 플러그인 관리자 메뉴 설치·업데이트 자동 동기화(F1·F7) - 동반: 플러그인 설정 검증 오류 필드명 라벨화(F3), Toggle 노드 type 정정 수평 전개(marketing 1.0.2 bump·gdpr·ecommerce), ja 언어팩 4종, API 문서, hooks.md, 시나리오 매니페스트 8종, Playwright E2E 4종
12 KiB
12 KiB
Uploads API 레퍼런스
소유: plugin
sirsoft-ckeditor5· 생성:php artisan api:docgen(실측 기반). @generated 블록은 재생성 시 갱신되며, 사람이 작성한 설명은 보존됩니다.
TL;DR (5초 요약)
1. 이 문서는 에디터 업로드 이미지 관리(조회·삭제) 엔드포인트 레퍼런스입니다
2. 각 엔드포인트: 메서드/URI/권한 + 요청 파라미터 표 + 요청 예시(raw HTTP) + 응답 필드 표 + 응답 예시(envelope)
3. 조회(uploads.read)와 삭제(uploads.delete)는 별도 권한입니다 — 삭제는 파일을 실제로 파기합니다
4. 참조 상태(referenced)는 저장 컬럼이 아니라 조회 시점에 판정한 값입니다
5. 갱신: 코드 변경 후 php artisan api:docgen 재실행
GET /api/plugins/sirsoft-ckeditor5/admin/uploads
- 라우트명:
api.plugins.sirsoft-ckeditor5.admin.uploads.index - 컨트롤러:
Plugins\Sirsoft\Ckeditor5\Http\Controllers\Admin\ImageUploadAdminController@index - 인증/권한:
auth:sanctum,permission:admin,sirsoft-ckeditor5.uploads.read
요청 파라미터
| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 |
|---|---|---|---|---|---|
| page | query | integer | 아니오 | min 1 | 페이지 번호 (기본 1) |
| per_page | query | integer | 아니오 | 1 ~ 100 | 페이지 크기 (기본 20). 참조 판정을 페이지 단위로 수행하므로 이 값이 곧 판정 비용의 상한이다. |
| search | query | string | 아니오 | max 255 | 원본 파일명 또는 해시 부분 일치 |
| referenced | query | string | 아니오 | all, referenced, unreferenced |
참조 상태 필터 (기본 all) |
| date_from | query | date | 아니오 | — | 업로드 시작일 (그날 00:00:00 포함) |
| date_to | query | date | 아니오 | — | 업로드 종료일 (그날 23:59:59 포함) |
| sort_by | query | string | 아니오 | created_at, file_size, original_name |
정렬 컬럼 (기본 created_at). 목록 밖 컬럼은 422 |
| sort_order | query | string | 아니오 | asc, desc |
정렬 방향 (기본 desc) |
요청 예시
GET /api/plugins/sirsoft-ckeditor5/admin/uploads?referenced=unreferenced&per_page=20 HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}
응답 필드 (data 내부)
| 필드 | 타입 | 실측 예시값 | 용도/설명 |
|---|---|---|---|
| data[].id | integer | 12 |
업로드 기록 ID (삭제 요청의 대상 식별자) |
| data[].hash | string | a1b2c3d4e5f6 |
URL용 고유 해시 12자리 hex. 본문에 API 폴백형 URL 로 삽입될 때 이 값이 들어간다 |
| data[].original_name | string | banner.png |
업로드 당시 원본 파일명 |
| data[].file_size | integer | 78310 |
파일 크기 (bytes) |
| data[].mime_type | string | image/png |
MIME 타입 |
| data[].uploaded_by | integer|null | 1 |
업로드한 사용자 ID (탈퇴·미상이면 null) |
| data[].uploader_name | string|null | 관리자 |
업로더 표시명. 관계가 로드된 경우에만 채워진다 |
| data[].created_at | string | 2026-08-14 19:20:11 |
업로드 일시 (요청자 시간대 기준 포맷) |
| data[].download_url | string | /api/plugins/sirsoft-ckeditor5/images/a1b2c3d4e5f6 |
이미지 서빙 URL. 공개 자산 디스크가 직접 URL 을 지원하면 CDN 주소가 반환된다 |
| data[].referenced | boolean | false |
조회 시점 판정값(저장 컬럼 아님). 등록된 참조 소스 본문에 hash 또는 저장 파일명이 등장하면 true. 판정 불가 행은 안전 방향인 true 로 본다 |
| pagination.current_page | integer | 1 |
현재 페이지 |
| pagination.last_page | integer | 1 |
마지막 페이지 |
| pagination.per_page | integer | 20 |
페이지 크기 |
| pagination.total | integer | 3 |
총 건수 |
| pagination.from | integer|null | 1 |
현재 페이지 첫 항목 순번 (비어 있으면 null) |
| pagination.to | integer|null | 3 |
현재 페이지 마지막 항목 순번 (비어 있으면 null) |
| meta.scan_limited | boolean | false |
참조 상태 필터가 스캔 윈도우 상한에 걸렸는지. true 면 그보다 오래된 이미지는 목록에 포함되지 않는다 |
| meta.scan_window | integer | 500 |
참조 상태 필터가 훑는 최신 업로드 건수 상한 |
응답 예시
{
"success": true,
"message": "요청이 성공적으로 처리되었습니다.",
"data": {
"data": [
{
"id": 12,
"hash": "a1b2c3d4e5f6",
"original_name": "banner.png",
"file_size": 78310,
"mime_type": "image/png",
"uploaded_by": 1,
"uploader_name": "관리자",
"created_at": "2026-08-14 19:20:11",
"download_url": "/api/plugins/sirsoft-ckeditor5/images/a1b2c3d4e5f6",
"referenced": false
}
],
"pagination": {
"current_page": 1,
"last_page": 1,
"per_page": 20,
"total": 1,
"from": 1,
"to": 1
},
"meta": {
"scan_limited": false,
"scan_window": 500
}
}
}
에러 응답
| 상태코드 | 의미 | 발생 조건 |
|---|---|---|
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
| 403 | Forbidden | sirsoft-ckeditor5.uploads.read 권한이 없는 경우 |
| 422 | Unprocessable Entity | 허용 목록 밖 sort_by/sort_order/referenced, per_page 범위 초과, 날짜 형식 오류 |
설명
에디터로 업로드된 이미지 목록을 관리 화면에 제공하는 엔드포인트다.
- 참조 상태는 저장하지 않는다. 참조 관계는 콘텐츠가 편집될 때마다 바뀌므로 스냅샷을 저장하면 "저장 당시엔 미참조였던" 이미지를 이후에 오판 삭제할 수 있다. 대신 판정 비용에 상한을 둔다 — 기본 목록은 현재 페이지(≤100행)만 판정하고, 참조 상태 필터를 쓸 때만 최신
meta.scan_window건을 훑는다. meta.scan_limited가 true 면 목록이 전수가 아니다. 화면은 이 값을 근거로 "최근 N건 기준" 안내를 표시해야 한다. 이 값을 무시하면 운영자가 "미참조 이미지가 이게 전부" 로 오해한다.- 판정 토큰은
hash와 저장 파일명 둘 다이다. 본문에 박히는 URL 이 API 폴백형(hash 포함)과 디스크 직접 URL(저장 파일명 포함) 두 형태이기 때문이며, 한쪽만 검사하면 다른 형태로 저장된 이미지를 미참조로 오판한다.
DELETE /api/plugins/sirsoft-ckeditor5/admin/uploads/{id}
- 라우트명:
api.plugins.sirsoft-ckeditor5.admin.uploads.destroy - 컨트롤러:
Plugins\Sirsoft\Ckeditor5\Http\Controllers\Admin\ImageUploadAdminController@destroy - 인증/권한:
auth:sanctum,permission:admin,sirsoft-ckeditor5.uploads.delete
요청 파라미터
| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 |
|---|---|---|---|---|---|
| id | path | integer | 예 | 숫자만 | 삭제할 업로드 기록 ID |
요청 예시
DELETE /api/plugins/sirsoft-ckeditor5/admin/uploads/12 HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}
응답 필드 (data 내부)
성공 시 data 는 null 이며, 결과는 message 로만 전달된다.
응답 예시
{
"success": true,
"message": "이미지를 삭제했습니다.",
"data": null
}
에러 응답
| 상태코드 | 의미 | 발생 조건 |
|---|---|---|
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
| 403 | Forbidden | sirsoft-ckeditor5.uploads.delete 권한이 없는 경우 (조회 권한만으로는 삭제 불가) |
| 404 | Not Found | 해당 ID 의 업로드 기록이 없는 경우 |
| 500 | Internal Server Error | 물리 파일이 존재하는데 삭제에 실패한 경우 — 기록은 보존되며 다음 시도에서 재시도할 수 있다 |
설명
업로드 이미지 1건을 파일과 기록까지 삭제한다.
- 삭제 순서는 파일 먼저, 기록 나중이다. 반대로 하면 기록이 사라진 파일은 어떤 경로로도 다시 찾을 수 없어 영구 고아가 된다.
- 파일이 이미 없는 행은 고아 기록으로 보고 기록만 지운다. 그러지 않으면 매 회차 같은 행을 재시도하는 루프가 된다.
- 행마다
storage_disk가 다를 수 있으므로 삭제는 그 행에 기록된 디스크를 향해 수행된다(디스크 전환 이후 혼재 대응). - 이 경로는 정리 커맨드(
sirsoft-ckeditor5:prune-unused-images)와 같은 삭제 구현을 공유한다 — 화면 삭제와 자동 정리의 동작이 갈리지 않게 하기 위함이다.
POST /api/plugins/sirsoft-ckeditor5/admin/uploads/bulk-delete
- 라우트명:
api.plugins.sirsoft-ckeditor5.admin.uploads.bulk-delete - 컨트롤러:
Plugins\Sirsoft\Ckeditor5\Http\Controllers\Admin\ImageUploadAdminController@bulkDestroy - 인증/권한:
auth:sanctum,permission:admin,sirsoft-ckeditor5.uploads.delete
요청 파라미터
| 이름 | 위치 | 타입 | 필수 | 허용값 | 용도 |
|---|---|---|---|---|---|
| ids | body | array | 예 | 1 ~ 100개 | 삭제할 업로드 기록 ID 목록 |
| ids.* | body | integer | 예 | 존재하는 업로드 ID | 각 항목은 실재하는 기록이어야 한다 (아니면 422) |
요청 예시
POST /api/plugins/sirsoft-ckeditor5/admin/uploads/bulk-delete HTTP/1.1
Host: api.example.com
Accept: application/json
Authorization: Bearer {YOUR_TOKEN}
Content-Type: application/json
{
"ids": [12, 13, 14]
}
응답 필드 (data 내부)
| 필드 | 타입 | 실측 예시값 | 용도/설명 |
|---|---|---|---|
| deleted | integer | 3 |
파일·기록이 함께 삭제된 건수 |
| failed | integer | 0 |
물리 파일 삭제 실패로 기록을 보존한 건수 (다음 시도에서 재시도 가능) |
응답 예시
{
"success": true,
"message": "선택한 이미지 3건을 삭제했습니다.",
"data": {
"deleted": 3,
"failed": 0
}
}
에러 응답
| 상태코드 | 의미 | 발생 조건 |
|---|---|---|
| 401 | Unauthenticated | 유효한 Bearer 토큰이 없거나 만료된 경우 |
| 403 | Forbidden | sirsoft-ckeditor5.uploads.delete 권한이 없는 경우 |
| 422 | Unprocessable Entity | ids 누락/빈 배열/100개 초과, 또는 존재하지 않는 ID 포함 |
| 500 | Internal Server Error | 요청한 전부가 파일 삭제에 실패한 경우 (일부만 실패하면 200 + failed 로 보고) |
설명
관리 화면에서 체크한 이미지를 한 번에 삭제한다.
- 선택 범위는 화면에 보이는 페이지로 한정된다(화면 계약). 검색·필터·페이지 이동으로 목록에서 빠진 행의 선택이 남아 대상에 실리면, 운영자가 보지도 체크하지도 않은 파일이 지워지기 때문이다.
- 일부만 실패한 경우 요청 자체는 성공(200)으로 응답하고
failed로 보고한다. 전부 실패한 경우에만 500 이다 — 부분 성공을 실패로 되돌릴 방법이 없기 때문이다.