Дополнительные действия
Denchikp (обсуждение | вклад) Нет описания правки |
Denchikp (обсуждение | вклад) Нет описания правки |
||
| Строка 1: | Строка 1: | ||
= | = CDN API (Reference) = | ||
== | == Общие требования == | ||
'''Base URL:''' https://cdnapi.denchikp.com<br> | '''Base URL:''' https://cdnapi.denchikp.com<br> | ||
'''CDN URL файлов:''' https://cdn.denchikp.com/<domain>/<folder>/<filename> | '''CDN URL файлов:''' https://cdn.denchikp.com/<domain>/<folder>/<filename> | ||
Большинство CDN‑эндпоинтов требуют: | |||
* заголовок <code>X-Api-Key: <API_KEY></code> | |||
или | |||
* активную cookie‑сессию, где ключ может резолвиться автоматически (OAuth/CDN login + bind). | |||
Во многих операциях, где передаётся <code>url</code>, принимаются '''только''' ссылки вида: | |||
<pre> | |||
https://cdn.denchikp.com/<domain>/<folder>/<filename> | |||
</pre> | |||
== Ключ / домены / квоты == | |||
=== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(96,165,250,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#60a5fa;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">GET</span> <code>/api-key/me</code> === | |||
Информация о текущем API‑ключе (по <code>X-Api-Key</code> или по сессии). | |||
=== | |||
'''Headers:''' | |||
< | * <code>X-Api-Key: <API_KEY></code> (рекомендуется) | ||
</ | |||
'''Response 200:''' | |||
<pre> | <pre> | ||
{ | { | ||
"status": " | "status":"ok", | ||
" | "key":{ | ||
"name":"ClientName", | |||
"is_admin":false, | |||
"domains":["public"], | |||
"ips":["1.2.3.4"] | |||
} | |||
} | } | ||
</pre> | </pre> | ||
'''Ошибки:''' | |||
* 401 — нет ключа и нет валидной авторизации | |||
---- | |||
'''Response 200''' | === <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(96,165,250,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#60a5fa;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">GET</span> <code>/domains</code> === | ||
Список доменов, доступных для ключа. | |||
'''Response 200 (примеры):''' | |||
* admin‑ключ: | |||
<pre> | <pre> | ||
{"status":"ok"} | {"status":"ok","mode":"any","domains":[]} | ||
</pre> | </pre> | ||
* allowlist‑ключ: | |||
<pre> | <pre> | ||
{ | {"status":"ok","mode":"allowlist","domains":["public","example.com"]} | ||
} | |||
</pre> | </pre> | ||
* ключ без доменов (разрешён как минимум public): | |||
<pre> | <pre> | ||
{ | {"status":"ok","mode":"any","domains":["public"]} | ||
} | |||
</pre> | </pre> | ||
---- | |||
=== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(96,165,250,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#60a5fa;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">GET</span> <code>/usage</code> === | |||
Текущее использование (без лимитов квоты). | |||
'''Response 200''' | '''Response 200 (пример):''' | ||
<pre> | <pre> | ||
{ | { | ||
"status": "ok", | "status":"ok", | ||
" | "usage":{ | ||
" | "total_files":120, | ||
" | "total_bytes":123456789, | ||
" | "month":"2026-06", | ||
" | "monthly_uploaded_files":10, | ||
"monthly_uploaded_bytes":12300000 | |||
} | } | ||
} | } | ||
</pre> | </pre> | ||
---- | |||
=== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(96,165,250,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#60a5fa;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">GET</span> <code>/quota</code> === | |||
Квоты + использование + остатки (remaining). | |||
'''Response 200 (пример) | '''Response 200 (пример):''' | ||
<pre> | <pre> | ||
{ | { | ||
"status": "ok", | "status":"ok", | ||
"api_key_name": "ClientName", | "api_key_name":"ClientName", | ||
"is_admin": false, | "is_admin":false, | ||
"quota": { | "quota":{ | ||
"max_file_bytes": 524288000, | "max_file_bytes":524288000, | ||
"max_total_bytes": 10737418240, | "max_total_bytes":10737418240, | ||
"max_total_files": 50000, | "max_total_files":50000, | ||
"max_monthly_upload_bytes": 5368709120, | "max_monthly_upload_bytes":5368709120, | ||
"max_monthly_upload_files": null | "max_monthly_upload_files":null | ||
}, | }, | ||
"usage": { | "usage":{ | ||
"total_files": 120, | "total_files":120, | ||
"total_bytes": 123456789, | "total_bytes":123456789, | ||
"month": "2026-06", | "month":"2026-06", | ||
"monthly_uploaded_files": 10, | "monthly_uploaded_files":10, | ||
"monthly_uploaded_bytes": 12300000 | "monthly_uploaded_bytes":12300000 | ||
}, | }, | ||
"remaining": { | "remaining":{ | ||
"total_bytes": 10000000000, | "total_bytes":10000000000, | ||
"total_files": 49880, | "total_files":49880, | ||
"monthly_upload_bytes": 5300000000, | "monthly_upload_bytes":5300000000, | ||
"monthly_upload_files": null | "monthly_upload_files":null | ||
} | } | ||
} | } | ||
</pre> | </pre> | ||
'''Ошибки:''' | |||
* 403 — если ключ невалиден/запрещён по IP и т.п. | |||
== | == Файлы (БД / meta / exists) == | ||
=== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(96,165,250,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#60a5fa;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">GET</span> <code>/files</code> === | |||
Список «моих» файлов из БД. | |||
'''Headers:''' | |||
* <code>X-Api-Key: <API_KEY></code> | |||
'''Query parameters (все опциональны):''' | |||
{| class="wikitable" | {| class="wikitable" | ||
! Name !! Type !! Default !! Description | ! Name !! Type !! Default !! Description | ||
|- | |- | ||
| q || string || || поиск по filename/original_filename (LIKE) | | q || string || — || поиск по <code>filename</code>/<code>original_filename</code> (LIKE) | ||
|- | |- | ||
| domain || string || || фильтр домена (нормализуется) | | domain || string || — || фильтр домена (нормализуется) | ||
|- | |- | ||
| folder || string || || фильтр папки | | folder || string || — || фильтр папки | ||
|- | |- | ||
| ext || string || || | | ext || string || — || фильтр расширения | ||
|- | |- | ||
| exists_on_disk || bool || true || фильтр по наличию на диске | | exists_on_disk || bool || true || фильтр по наличию на диске | ||
|- | |- | ||
| limit || int || 50 || | | limit || int || 50 || максимум 200 | ||
|- | |- | ||
| offset || int || 0 || смещение | | offset || int || 0 || смещение | ||
|} | |} | ||
'''Response 200 (пример)''' | '''Response 200 (пример):''' | ||
<pre> | <pre> | ||
{ | { | ||
"status": "ok", | "status":"ok", | ||
"total": 123, | "total":123, | ||
"limit": 50, | "limit":50, | ||
"offset": 0, | "offset":0, | ||
"files": [ | "files":[ | ||
{ | { | ||
"id": 1, | "id":1, | ||
"domain": "public", | "domain":"public", | ||
"folder": "img", | "folder":"img", | ||
"filename": "a.png", | "filename":"a.png", | ||
"original_filename": "cat.png", | "original_filename":"cat.png", | ||
"url": "https://cdn.denchikp.com/public/img/a.png", | "url":"https://cdn.denchikp.com/public/img/a.png", | ||
"size": 12345, | "size":12345, | ||
"created_at": "2026-06-16T10:00:00", | "created_at":"2026-06-16T10:00:00", | ||
"exists_on_disk": true | "exists_on_disk":true | ||
} | } | ||
] | ] | ||
| Строка 279: | Строка 164: | ||
</pre> | </pre> | ||
---- | |||
''' | === <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/file/meta</code> === | ||
Метаданные файла: сведения из БД + проверка на диске. | |||
'''Body (JSON):''' | |||
<pre> | <pre> | ||
{"url":"https://cdn.denchikp.com/public/img/a.png"} | {"url":"https://cdn.denchikp.com/public/img/a.png"} | ||
</pre> | </pre> | ||
== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/file/exists</code> == | '''Response 200 (схема):''' | ||
<pre> | |||
{ | |||
"status":"ok", | |||
"url":"...", | |||
"domain":"public", | |||
"folder":"img", | |||
"filename":"a.png", | |||
"db":{ | |||
"id":1, | |||
"owner":"ClientName", | |||
"api_key_id":10, | |||
"size":12345, | |||
"exists_on_disk":true, | |||
"created_at":"..." | |||
}, | |||
"disk":{ | |||
"exists":true, | |||
"size":12345, | |||
"mime_type":"image/png" | |||
} | |||
} | |||
</pre> | |||
'''Ошибки:''' | |||
* 400 — URL не CDN формата/не парсится | |||
* 403 — нет доступа к домену | |||
* 401 — нет ключа | |||
---- | |||
=== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/file/exists</code> === | |||
Проверка существования файла (в БД и на диске). | Проверка существования файла (в БД и на диске). | ||
''' | '''Body (JSON):''' | ||
<pre> | <pre> | ||
{"url":"https://cdn.denchikp.com/public/img/a.png"} | {"url":"https://cdn.denchikp.com/public/img/a.png"} | ||
</pre> | </pre> | ||
'''Response 200''' | '''Response 200:''' | ||
<pre> | <pre> | ||
{"status":"ok","exists_in_db":true,"exists_on_disk":true} | {"status":"ok","exists_in_db":true,"exists_on_disk":true} | ||
</pre> | </pre> | ||
== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(96,165,250,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#60a5fa;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">GET</span> <code>/file/redirect</code> == | ---- | ||
=== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(96,165,250,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#60a5fa;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">GET</span> <code>/file/redirect</code> === | |||
Публичный редирект на CDN URL. | Публичный редирект на CDN URL. | ||
Query: | '''Query:''' | ||
* <code>?url=https://cdn.denchikp.com/...</code> | * <code>?url=https://cdn.denchikp.com/...</code> | ||
= | '''Ошибки:''' | ||
* 400 — если url не начинается с <code>https://cdn.denchikp.com/</code> | |||
== Загрузка == | |||
== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/upload</code> == | === <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/upload</code> === | ||
Загрузка файла (multipart/form-data). | Загрузка файла (multipart/form-data). | ||
''' | '''Headers:''' | ||
* <code>X-Api-Key: <API_KEY></code> | |||
'''Content-Type:''' <code>multipart/form-data</code> | |||
'''Max size:''' 500 MB | '''Max size:''' 500 MB | ||
Form fields: | '''Form fields:''' | ||
{| class="wikitable" | {| class="wikitable" | ||
! Field !! Type !! Required !! Description | ! Field !! Type !! Required !! Default !! Description | ||
|- | |- | ||
| file || file || да || загружаемый файл | | file || file || да || — || загружаемый файл | ||
|- | |- | ||
| domain || string || нет || "" → public | | domain || string || нет || "" || "" → public | ||
|- | |- | ||
| name_mode || string || нет || uuid | original | custom | | name_mode || string || нет || uuid || uuid | original | custom | ||
|- | |- | ||
| custom_filename || string || нет || используется при name_mode=custom | | custom_filename || string || нет || "" || используется при name_mode=custom | ||
|- | |- | ||
| conflict_action || string || нет || overwrite → перезаписать (с проверками прав) | | conflict_action || string || нет || "" || overwrite → перезаписать (с проверками прав) | ||
|} | |} | ||
'''Response 200 (пример):''' | |||
<pre> | <pre> | ||
{ | |||
"status":"ok", | |||
"url":"https://cdn.denchikp.com/public/img/...", | |||
"size":12345, | |||
"owner":"ClientName", | |||
"ip":"1.2.3.4", | |||
"domain":"public", | |||
"folder":"img", | |||
"filename":"..." | |||
} | |||
</pre> | </pre> | ||
'''Response | '''Response 409 (конфликт имени, если не overwrite):''' | ||
<pre> | <pre> | ||
{ | { | ||
"status": " | "status":"conflict", | ||
" | "message":"FILE_EXISTS", | ||
"requested_filename":"a.png", | |||
" | "existing_url":"https://cdn.denchikp.com/public/img/a.png", | ||
" | "domain":"public", | ||
"domain": "public", | "folder":"img" | ||
"folder": "img | |||
} | } | ||
</pre> | </pre> | ||
''' | '''Ошибки (частые):''' | ||
* 403 — квота превышена / домен не разрешён / IP не разрешён / overwrite запрещён | |||
* 413 — файл слишком большой | |||
* 500 — внутренняя ошибка | |||
Webhook event: <code>UPLOAD</code> (payload.source="api") | Webhook event: <code>UPLOAD</code> (payload.source="api") | ||
Пример (curl): | |||
<pre> | |||
curl -X POST "https://cdnapi.denchikp.com/upload" \ | |||
-H "X-Api-Key: <API_KEY>" \ | |||
-F "domain=public" \ | |||
-F "name_mode=uuid" \ | |||
-F "file=@./cat.png" | |||
</pre> | |||
''' | ---- | ||
=== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/upload/by-url</code> === | |||
Сервер скачивает файл по ссылке и сохраняет в CDN. | |||
'''Body (JSON):''' | |||
<pre> | <pre> | ||
{"source_url":"https://example.com/file.png","domain":"public","folder":"img"} | { | ||
"source_url":"https://example.com/file.png", | |||
"domain":"public", | |||
"folder":"img" | |||
} | |||
</pre> | </pre> | ||
Ограничения: | '''Ограничения:''' | ||
* только http/https | * только http/https | ||
* | * SSRF‑защита: запрещено скачивать с private/loopback/link-local/reserved/multicast адресов | ||
* max 500 MB | * max 500 MB | ||
'''Response 200 (пример):''' | |||
<pre> | |||
{"status":"ok","url":"https://cdn.denchikp.com/public/img/...","size":12345,"domain":"public","folder":"img"} | |||
</pre> | |||
Webhook event: <code>UPLOAD</code> (payload.source="by_url") | Webhook event: <code>UPLOAD</code> (payload.source="by_url") | ||
= Multipart Upload = | == Multipart Upload == | ||
== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/upload/session/start</code> == | === <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/upload/session/start</code> === | ||
Создать multipart‑сессию. | Создать multipart‑сессию. | ||
''' | '''Body (JSON):''' | ||
<pre> | <pre> | ||
{"domain":"public","ext":"mp4","mime_type":"video/mp4","folder":"files"} | { | ||
"domain":"public", | |||
"ext":"mp4", | |||
"mime_type":"video/mp4", | |||
"folder":"files" | |||
} | |||
</pre> | </pre> | ||
'''Response 200''' | '''Response 200:''' | ||
<pre> | <pre> | ||
{"status":"ok","upload_id":" | {"status":"ok","upload_id":"<uuid>","chunk_size":8388608} | ||
</pre> | </pre> | ||
== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(251,191,36,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#fbbf24;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">PUT</span> <code>/upload/session/{upload_id}/part/{part_no}</code> == | ---- | ||
=== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(251,191,36,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#fbbf24;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">PUT</span> <code>/upload/session/{upload_id}/part/{part_no}</code> === | |||
Загрузить часть файла (chunk). | |||
''' | '''Content-Type:''' <code>multipart/form-data</code> | ||
Form-data: | Form-data: | ||
* <code>file</code> — chunk | * <code>file</code> — chunk | ||
'''Response 200''' | '''Response 200:''' | ||
<pre> | <pre> | ||
{"status":"ok","part_no":1,"bytes":8388608} | {"status":"ok","part_no":1,"bytes":8388608} | ||
</pre> | </pre> | ||
== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/upload/session/{upload_id}/complete</code> == | '''Ошибки:''' | ||
* 404 — upload_id не найден | |||
* 403 — upload_id принадлежит другому ключу (для не‑админов) | |||
---- | |||
=== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/upload/session/{upload_id}/complete</code> === | |||
Склеить части и создать итоговый файл. | |||
'''Ошибки:''' | |||
* 400 — нет частей | |||
* 413 — итоговый файл > 500MB | |||
* 403 — квота/права | |||
Webhook event: <code>UPLOAD</code> (payload.source="multipart") | Webhook event: <code>UPLOAD</code> (payload.source="multipart") | ||
---- | |||
=== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/upload/session/{upload_id}/abort</code> === | |||
Отменить multipart‑сессию и удалить временные части. | |||
= | '''Response 200:''' | ||
<pre> | |||
{"status":"ok"} | |||
</pre> | |||
== Операции с файлами == | |||
== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/files/bulk-delete</code> == | === <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/files/bulk-delete</code> === | ||
Удалить | Удалить много файлов за один запрос (до 500 URL). | ||
''' | '''Body (JSON):''' | ||
<pre> | <pre> | ||
{"urls":["https://cdn.denchikp.com/public/files/a.txt","..."]} | {"urls":["https://cdn.denchikp.com/public/files/a.txt","..."]} | ||
</pre> | </pre> | ||
'''Response 200 (пример)''' | '''Response 200 (пример):''' | ||
<pre> | <pre> | ||
{ | { | ||
"status": "ok", | "status":"ok", | ||
"results": [ | "results":[ | ||
{"url":" | {"url":"...","status":"ok"}, | ||
{"url":" | {"url":"...","status":"error","error":"..."} | ||
] | ] | ||
} | } | ||
| Строка 438: | Строка 406: | ||
Webhook event: <code>BULK_DELETE</code> | Webhook event: <code>BULK_DELETE</code> | ||
== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/file/move</code> == | ---- | ||
Перемещение файла. | |||
=== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/file/move</code> === | |||
Перемещение/переименование файла (можно менять домен/папку/имя). | |||
''' | '''Body (JSON):''' | ||
<pre> | <pre> | ||
{ | { | ||
| Строка 450: | Строка 419: | ||
"new_filename":"b.png" | "new_filename":"b.png" | ||
} | } | ||
</pre> | |||
'''Правила:''' | |||
* <code>new_folder</code> должен быть одним из: img/css/js/fonts/files | |||
* <code>new_filename</code> не должен содержать <code>..</code>, <code>/</code>, <code>\</code> | |||
'''Response 200:''' | |||
<pre> | |||
{"status":"ok","from":"https://.../a.png","to":"https://.../b.png"} | |||
</pre> | </pre> | ||
Webhook event: <code>FILE_MOVE</code> | Webhook event: <code>FILE_MOVE</code> | ||
== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/file/rename</code> == | ---- | ||
=== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/file/rename</code> === | |||
Переименовать файл (alias для <code>/file/move</code>, но меняется только имя). | |||
'''Body (JSON):''' | |||
<pre> | |||
{ | |||
"url":"https://cdn.denchikp.com/public/img/a.png", | |||
"new_filename":"b.png" | |||
} | |||
</pre> | |||
'''Ошибки:''' | |||
* 400 — <code>new_filename</code> обязателен | |||
---- | |||
''' | === <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/file/copy</code> === | ||
Копирование файла (квоты учитываются как новая загрузка). | |||
'''Body (JSON):''' | |||
<pre> | <pre> | ||
{ | { | ||
| Строка 469: | Строка 461: | ||
"target_filename":"copy.png" | "target_filename":"copy.png" | ||
} | } | ||
</pre> | |||
'''Response 200:''' | |||
<pre> | |||
{"status":"ok","url":"https://cdn.denchikp.com/public/img/copy.png","size":12345} | |||
</pre> | </pre> | ||
Webhook event: <code>FILE_COPY</code> | Webhook event: <code>FILE_COPY</code> | ||
== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/file/hash</code> == | ---- | ||
=== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/file/hash</code> === | |||
Посчитать хэш файла. | |||
''' | '''Body (JSON):''' | ||
<pre> | <pre> | ||
{"url":"https://cdn.denchikp.com/public/files/a.bin","algo":"sha256"} | {"url":"https://cdn.denchikp.com/public/files/a.bin","algo":"sha256"} | ||
</pre> | </pre> | ||
<code>algo</code>: sha256 | sha1 | md5 | |||
'''Response 200:''' | |||
<pre> | |||
{"status":"ok","algo":"sha256","hash":"<hex>","url":"https://..."} | |||
</pre> | |||
== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/browse</code> == | == Browse / Folders / Download == | ||
=== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/browse</code> === | |||
Просмотр файлов на диске в домене. | Просмотр файлов на диске в домене. | ||
''' | '''Body (JSON):''' | ||
<pre> | <pre> | ||
{"domain":"public","folder":"img","filter":"cat"} | {"domain":"public","folder":"img","filter":"cat"} | ||
</pre> | </pre> | ||
Поля: | |||
* <code>domain</code> — обязательно | |||
* <code>folder</code> — опционально | |||
* <code>filter</code> — подстрока в имени файла (опционально) | |||
''' | '''Response 200 (схема):''' | ||
<pre> | |||
{"status":"ok","domain":"public","total_files":2,"files":[{"name":"a.png","path":"img/a.png","size":12345,"url":"..."}]} | |||
</pre> | |||
---- | |||
=== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(96,165,250,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#60a5fa;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">GET</span> <code>/folders/{domain}</code> === | |||
Список папок домена + агрегаты. | |||
'''Response 200 (пример):''' | |||
<pre> | |||
{ | |||
"status":"ok", | |||
"domain":"public", | |||
"total_files":10, | |||
"total_size":1234567, | |||
"folders":[{"name":"img","file_count":5,"size":1000000,"path":"public/img"}] | |||
} | |||
</pre> | |||
== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/validate</code> == | ---- | ||
=== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(96,165,250,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#60a5fa;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">GET</span> <code>/download/{domain}/{folder}/{filename:path}</code> === | |||
Скачивание файла. | |||
'''Важно:''' | |||
хотя параметр <code>{filename:path}</code> технически может содержать “/”, сервер отклонит имя, если в нём есть <code>/</code> или <code>\</code> или <code>..</code>. | |||
'''Response 200:''' | |||
* бинарный поток (FileResponse), <code>application/octet-stream</code> | |||
'''Ошибки:''' | |||
* 404 — файл не найден | |||
* 403 — нет доступа к домену | |||
== Validate URL == | |||
=== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/validate</code> === | |||
Валидация ссылки (http/https). Делает HEAD, при необходимости GET range 0-0, возвращает HTTP статус и заголовки. | |||
Если URL — CDN URL, дополнительно парсит <code>domain/folder/filename</code> и проверяет наличие на диске. | Если URL — CDN URL, дополнительно парсит <code>domain/folder/filename</code> и проверяет наличие на диске. | ||
''' | '''Body (JSON):''' | ||
<pre> | <pre> | ||
{"url":"https://example.com/file.png"} | {"url":"https://example.com/file.png"} | ||
</pre> | </pre> | ||
'''Response 200 (схема, укорочено):''' | |||
<pre> | |||
{ | |||
"status":"ok", | |||
"checked_by":"ClientName", | |||
"url":"https://example.com/file.png", | |||
"is_valid_url":true, | |||
"is_cdn_url":false, | |||
"available":true, | |||
"http_status":200, | |||
"headers":{"content_type":"image/png","content_length":"12345","last_modified":null,"etag":null,"server":"nginx"}, | |||
"file_info":{"filename":"file.png","extension":"png","host":"example.com","path":"/file.png"}, | |||
"cdn_info":null, | |||
"error":null | |||
} | |||
</pre> | |||
== | == Delete file == | ||
''' | === <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(251,113,133,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#fb7185;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">DELETE</span> <code>/delete</code> === | ||
Удаление файла по CDN URL. | |||
'''Body (JSON):''' | |||
<pre> | <pre> | ||
{"url":"https://cdn.denchikp.com/public/files/a.txt"} | {"url":"https://cdn.denchikp.com/public/files/a.txt"} | ||
</pre> | |||
'''Response 200 (пример):''' | |||
<pre> | |||
{ | |||
"status":"ok", | |||
"message":"Файл успешно удален", | |||
"domain":"public", | |||
"folder":"files", | |||
"filename":"a.txt", | |||
"url":"https://cdn.denchikp.com/public/files/a.txt", | |||
"owner":"ClientName", | |||
"ip":"1.2.3.4" | |||
} | |||
</pre> | </pre> | ||
Webhook event: <code>DELETE</code> (payload.source="api") | Webhook event: <code>DELETE</code> (payload.source="api") | ||
= Text Editor (Read/Save) = | == Text Editor (Read/Save) == | ||
Только для “текстовых” файлов и ограничение по размеру: ≤ 2 MB. | |||
---- | |||
== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/file/content</code> == | === <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/file/content</code> === | ||
Получить содержимое текстового файла. | Получить содержимое текстового файла. | ||
''' | '''Body (JSON):''' | ||
<pre> | <pre> | ||
{"url":"https://cdn.denchikp.com/public/files/app.js"} | {"url":"https://cdn.denchikp.com/public/files/app.js"} | ||
</pre> | </pre> | ||
'''Response 200 (пример):''' | |||
''' | |||
<pre> | <pre> | ||
{"url":"https://cdn.denchikp.com/public/files/app.js","content":"console.log('hi');\n"} | { | ||
"status":"ok", | |||
"url":"https://cdn.denchikp.com/public/files/app.js", | |||
"domain":"public", | |||
"folder":"files", | |||
"filename":"app.js", | |||
"mime_type":"application/javascript", | |||
"size":1200, | |||
"content":"console.log('hi');\n" | |||
} | |||
</pre> | </pre> | ||
'''Ошибки:''' | |||
* 400 — файл не текстовый / >2MB / неподдерживаемая кодировка | |||
* 404 — файл не найден | |||
---- | |||
== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/ | === <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/file/save</code> === | ||
Сохранить содержимое текстового файла. | |||
''' | '''Body (JSON):''' | ||
<pre> | <pre> | ||
{ | {"url":"https://cdn.denchikp.com/public/files/app.js","content":"console.log('hi');\n"} | ||
} | |||
</pre> | </pre> | ||
'''Response 200:''' | |||
''' | |||
<pre> | <pre> | ||
{" | {"status":"ok","message":"Файл успешно сохранен","url":"...","domain":"public","folder":"files","filename":"app.js","size":1200} | ||
</pre> | </pre> | ||
Webhook event: <code>FILE_SAVE</code> | |||
---- | |||
= Webhooks (Reference) = | |||
Webhooks позволяют получать события от сервиса (UPLOAD/DELETE и т.д.) на ваш URL. | |||
= | == Создание / управление == | ||
=== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(52,211,153,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#34d399;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">POST</span> <code>/webhooks</code> === | |||
Создать webhook. | |||
'''Headers:''' | |||
* <code> | * <code>X-Api-Key: <API_KEY></code> | ||
'''Body (JSON):''' | |||
<pre> | <pre> | ||
{ | { | ||
" | "url":"https://example.com/hook", | ||
" | "events":["UPLOAD","DELETE"], | ||
"secret":"optional", | |||
" | "enabled":true | ||
" | |||
} | } | ||
</pre> | </pre> | ||
'''Response 200:''' | |||
<pre> | <pre> | ||
{" | {"status":"ok","id":123} | ||
</pre> | </pre> | ||
'''Ошибки:''' | |||
* 401/403 — нет доступа | |||
---- | |||
=== | === <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(96,165,250,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#60a5fa;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">GET</span> <code>/webhooks</code> === | ||
Список webhooks текущего ключа. | |||
'''Response 200 (пример):''' | |||
<pre> | <pre> | ||
{ | { | ||
" | "status":"ok", | ||
" | "webhooks":[ | ||
{ | |||
"id":123, | |||
"url":"https://example.com/hook", | |||
"events":["UPLOAD","DELETE"], | |||
"enabled":true, | |||
"created_at":"2026-06-16T12:00:00" | |||
} | |||
] | |||
} | } | ||
</pre> | </pre> | ||
---- | |||
=== <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba( | === <span style="display:inline-flex;align-items:center;justify-content:center;padding:2px 10px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(251,113,133,.55);box-shadow:0 1px 0 rgba(255,255,255,.06) inset,0 6px 18px rgba(0,0,0,.18);color:#fb7185;font-weight:800;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.78em;letter-spacing:.10em;text-transform:uppercase;line-height:1.1;vertical-align:middle;">DELETE</span> <code>/webhooks/{webhook_id}</code> === | ||
Удалить webhook. | |||
'''Path params:''' | |||
* <code>webhook_id</code> — integer | |||
* <code> | |||
'''Response 200:''' | |||
<pre> | <pre> | ||
{" | {"status":"ok"} | ||
</pre> | </pre> | ||
'''Ошибки:''' | |||
* 404 — webhook не найден (или не принадлежит ключу) | |||
== | == Delivery format == | ||
Сервер отправляет на URL webhook’а запрос: | |||
* Method: POST | |||
Body: | * Content-Type: application/json | ||
* Body: | |||
<pre> | <pre> | ||
{" | {"event":"UPLOAD","payload":{...}} | ||
</pre> | </pre> | ||
== События == | |||
События, которые реально используются в текущем коде: | |||
* <code>UPLOAD</code> | |||
* <code>DELETE</code> | |||
* <code>BULK_DELETE</code> | |||
* <code>FILE_MOVE</code> | |||
* <code>FILE_COPY</code> | |||
* <code>FILE_SAVE</code> | |||
* <code>SCAN_FINISHED</code> | |||
=== | == Подпись (HMAC) == | ||
Если у webhook задан <code>secret</code> и на сервере включена подпись, рекомендуется добавлять: | |||
Headers: | |||
* <code>X-Webhook-Event: <EVENT></code> | |||
* <code>X-Webhook-Timestamp: <unix_seconds></code> | |||
* <code>X-Webhook-Signature: sha256=<hex></code> | |||
Алгоритм: | |||
* base = <code>{timestamp}.{raw_body}</code> | |||
* signature = <code>HMAC_SHA256(secret, base)</code> (hex) | |||
* получатель проверяет подпись и свежесть timestamp (например ±300 секунд) | |||
* | |||
''Примечание:'' в текущем коде поле <code>secret</code> хранится, но подпись нужно добавить отдельным патчем (если ещё не добавлено). | |||
Версия от 00:42, 17 июня 2026
CDN API (Reference)
Общие требования
Base URL: https://cdnapi.denchikp.com
CDN URL файлов: https://cdn.denchikp.com/<domain>/<folder>/<filename>
Большинство CDN‑эндпоинтов требуют:
- заголовок
X-Api-Key: <API_KEY>
или
- активную cookie‑сессию, где ключ может резолвиться автоматически (OAuth/CDN login + bind).
Во многих операциях, где передаётся url, принимаются только ссылки вида:
https://cdn.denchikp.com/<domain>/<folder>/<filename>
Ключ / домены / квоты
GET /api-key/me
Информация о текущем API‑ключе (по X-Api-Key или по сессии).
Headers:
X-Api-Key: <API_KEY>(рекомендуется)
Response 200:
{
"status":"ok",
"key":{
"name":"ClientName",
"is_admin":false,
"domains":["public"],
"ips":["1.2.3.4"]
}
}
Ошибки:
- 401 — нет ключа и нет валидной авторизации
GET /domains
Список доменов, доступных для ключа.
Response 200 (примеры):
- admin‑ключ:
{"status":"ok","mode":"any","domains":[]}
- allowlist‑ключ:
{"status":"ok","mode":"allowlist","domains":["public","example.com"]}
- ключ без доменов (разрешён как минимум public):
{"status":"ok","mode":"any","domains":["public"]}
GET /usage
Текущее использование (без лимитов квоты).
Response 200 (пример):
{
"status":"ok",
"usage":{
"total_files":120,
"total_bytes":123456789,
"month":"2026-06",
"monthly_uploaded_files":10,
"monthly_uploaded_bytes":12300000
}
}
GET /quota
Квоты + использование + остатки (remaining).
Response 200 (пример):
{
"status":"ok",
"api_key_name":"ClientName",
"is_admin":false,
"quota":{
"max_file_bytes":524288000,
"max_total_bytes":10737418240,
"max_total_files":50000,
"max_monthly_upload_bytes":5368709120,
"max_monthly_upload_files":null
},
"usage":{
"total_files":120,
"total_bytes":123456789,
"month":"2026-06",
"monthly_uploaded_files":10,
"monthly_uploaded_bytes":12300000
},
"remaining":{
"total_bytes":10000000000,
"total_files":49880,
"monthly_upload_bytes":5300000000,
"monthly_upload_files":null
}
}
Ошибки:
- 403 — если ключ невалиден/запрещён по IP и т.п.
Файлы (БД / meta / exists)
GET /files
Список «моих» файлов из БД.
Headers:
X-Api-Key: <API_KEY>
Query parameters (все опциональны):
| Name | Type | Default | Description |
|---|---|---|---|
| q | string | — | поиск по filename/original_filename (LIKE)
|
| domain | string | — | фильтр домена (нормализуется) |
| folder | string | — | фильтр папки |
| ext | string | — | фильтр расширения |
| exists_on_disk | bool | true | фильтр по наличию на диске |
| limit | int | 50 | максимум 200 |
| offset | int | 0 | смещение |
Response 200 (пример):
{
"status":"ok",
"total":123,
"limit":50,
"offset":0,
"files":[
{
"id":1,
"domain":"public",
"folder":"img",
"filename":"a.png",
"original_filename":"cat.png",
"url":"https://cdn.denchikp.com/public/img/a.png",
"size":12345,
"created_at":"2026-06-16T10:00:00",
"exists_on_disk":true
}
]
}
POST /file/meta
Метаданные файла: сведения из БД + проверка на диске.
Body (JSON):
{"url":"https://cdn.denchikp.com/public/img/a.png"}
Response 200 (схема):
{
"status":"ok",
"url":"...",
"domain":"public",
"folder":"img",
"filename":"a.png",
"db":{
"id":1,
"owner":"ClientName",
"api_key_id":10,
"size":12345,
"exists_on_disk":true,
"created_at":"..."
},
"disk":{
"exists":true,
"size":12345,
"mime_type":"image/png"
}
}
Ошибки:
- 400 — URL не CDN формата/не парсится
- 403 — нет доступа к домену
- 401 — нет ключа
POST /file/exists
Проверка существования файла (в БД и на диске).
Body (JSON):
{"url":"https://cdn.denchikp.com/public/img/a.png"}
Response 200:
{"status":"ok","exists_in_db":true,"exists_on_disk":true}
GET /file/redirect
Публичный редирект на CDN URL.
Query:
?url=https://cdn.denchikp.com/...
Ошибки:
- 400 — если url не начинается с
https://cdn.denchikp.com/
Загрузка
POST /upload
Загрузка файла (multipart/form-data).
Headers:
X-Api-Key: <API_KEY>
Content-Type: multipart/form-data
Max size: 500 MB
Form fields:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| file | file | да | — | загружаемый файл |
| domain | string | нет | "" | "" → public |
| name_mode | string | нет | uuid | original | custom |
| custom_filename | string | нет | "" | используется при name_mode=custom |
| conflict_action | string | нет | "" | overwrite → перезаписать (с проверками прав) |
Response 200 (пример):
{
"status":"ok",
"url":"https://cdn.denchikp.com/public/img/...",
"size":12345,
"owner":"ClientName",
"ip":"1.2.3.4",
"domain":"public",
"folder":"img",
"filename":"..."
}
Response 409 (конфликт имени, если не overwrite):
{
"status":"conflict",
"message":"FILE_EXISTS",
"requested_filename":"a.png",
"existing_url":"https://cdn.denchikp.com/public/img/a.png",
"domain":"public",
"folder":"img"
}
Ошибки (частые):
- 403 — квота превышена / домен не разрешён / IP не разрешён / overwrite запрещён
- 413 — файл слишком большой
- 500 — внутренняя ошибка
Webhook event: UPLOAD (payload.source="api")
Пример (curl):
curl -X POST "https://cdnapi.denchikp.com/upload" \ -H "X-Api-Key: <API_KEY>" \ -F "domain=public" \ -F "name_mode=uuid" \ -F "file=@./cat.png"
POST /upload/by-url
Сервер скачивает файл по ссылке и сохраняет в CDN.
Body (JSON):
{
"source_url":"https://example.com/file.png",
"domain":"public",
"folder":"img"
}
Ограничения:
- только http/https
- SSRF‑защита: запрещено скачивать с private/loopback/link-local/reserved/multicast адресов
- max 500 MB
Response 200 (пример):
{"status":"ok","url":"https://cdn.denchikp.com/public/img/...","size":12345,"domain":"public","folder":"img"}
Webhook event: UPLOAD (payload.source="by_url")
Multipart Upload
POST /upload/session/start
Создать multipart‑сессию.
Body (JSON):
{
"domain":"public",
"ext":"mp4",
"mime_type":"video/mp4",
"folder":"files"
}
Response 200:
{"status":"ok","upload_id":"<uuid>","chunk_size":8388608}
PUT /upload/session/{upload_id}/part/{part_no}
Загрузить часть файла (chunk).
Content-Type: multipart/form-data
Form-data:
file— chunk
Response 200:
{"status":"ok","part_no":1,"bytes":8388608}
Ошибки:
- 404 — upload_id не найден
- 403 — upload_id принадлежит другому ключу (для не‑админов)
POST /upload/session/{upload_id}/complete
Склеить части и создать итоговый файл.
Ошибки:
- 400 — нет частей
- 413 — итоговый файл > 500MB
- 403 — квота/права
Webhook event: UPLOAD (payload.source="multipart")
POST /upload/session/{upload_id}/abort
Отменить multipart‑сессию и удалить временные части.
Response 200:
{"status":"ok"}
Операции с файлами
POST /files/bulk-delete
Удалить много файлов за один запрос (до 500 URL).
Body (JSON):
{"urls":["https://cdn.denchikp.com/public/files/a.txt","..."]}
Response 200 (пример):
{
"status":"ok",
"results":[
{"url":"...","status":"ok"},
{"url":"...","status":"error","error":"..."}
]
}
Webhook event: BULK_DELETE
POST /file/move
Перемещение/переименование файла (можно менять домен/папку/имя).
Body (JSON):
{
"url":"https://cdn.denchikp.com/public/img/a.png",
"new_domain":"public",
"new_folder":"files",
"new_filename":"b.png"
}
Правила:
new_folderдолжен быть одним из: img/css/js/fonts/filesnew_filenameне должен содержать..,/,\
Response 200:
{"status":"ok","from":"https://.../a.png","to":"https://.../b.png"}
Webhook event: FILE_MOVE
POST /file/rename
Переименовать файл (alias для /file/move, но меняется только имя).
Body (JSON):
{
"url":"https://cdn.denchikp.com/public/img/a.png",
"new_filename":"b.png"
}
Ошибки:
- 400 —
new_filenameобязателен
POST /file/copy
Копирование файла (квоты учитываются как новая загрузка).
Body (JSON):
{
"url":"https://cdn.denchikp.com/public/img/a.png",
"target_domain":"public",
"target_folder":"img",
"target_filename":"copy.png"
}
Response 200:
{"status":"ok","url":"https://cdn.denchikp.com/public/img/copy.png","size":12345}
Webhook event: FILE_COPY
POST /file/hash
Посчитать хэш файла.
Body (JSON):
{"url":"https://cdn.denchikp.com/public/files/a.bin","algo":"sha256"}
algo: sha256 | sha1 | md5
Response 200:
{"status":"ok","algo":"sha256","hash":"<hex>","url":"https://..."}
Browse / Folders / Download
POST /browse
Просмотр файлов на диске в домене.
Body (JSON):
{"domain":"public","folder":"img","filter":"cat"}
Поля:
domain— обязательноfolder— опциональноfilter— подстрока в имени файла (опционально)
Response 200 (схема):
{"status":"ok","domain":"public","total_files":2,"files":[{"name":"a.png","path":"img/a.png","size":12345,"url":"..."}]}
GET /folders/{domain}
Список папок домена + агрегаты.
Response 200 (пример):
{
"status":"ok",
"domain":"public",
"total_files":10,
"total_size":1234567,
"folders":[{"name":"img","file_count":5,"size":1000000,"path":"public/img"}]
}
GET /download/{domain}/{folder}/{filename:path}
Скачивание файла.
Важно:
хотя параметр {filename:path} технически может содержать “/”, сервер отклонит имя, если в нём есть / или \ или ...
Response 200:
- бинарный поток (FileResponse),
application/octet-stream
Ошибки:
- 404 — файл не найден
- 403 — нет доступа к домену
Validate URL
POST /validate
Валидация ссылки (http/https). Делает HEAD, при необходимости GET range 0-0, возвращает HTTP статус и заголовки.
Если URL — CDN URL, дополнительно парсит domain/folder/filename и проверяет наличие на диске.
Body (JSON):
{"url":"https://example.com/file.png"}
Response 200 (схема, укорочено):
{
"status":"ok",
"checked_by":"ClientName",
"url":"https://example.com/file.png",
"is_valid_url":true,
"is_cdn_url":false,
"available":true,
"http_status":200,
"headers":{"content_type":"image/png","content_length":"12345","last_modified":null,"etag":null,"server":"nginx"},
"file_info":{"filename":"file.png","extension":"png","host":"example.com","path":"/file.png"},
"cdn_info":null,
"error":null
}
Delete file
DELETE /delete
Удаление файла по CDN URL.
Body (JSON):
{"url":"https://cdn.denchikp.com/public/files/a.txt"}
Response 200 (пример):
{
"status":"ok",
"message":"Файл успешно удален",
"domain":"public",
"folder":"files",
"filename":"a.txt",
"url":"https://cdn.denchikp.com/public/files/a.txt",
"owner":"ClientName",
"ip":"1.2.3.4"
}
Webhook event: DELETE (payload.source="api")
Text Editor (Read/Save)
Только для “текстовых” файлов и ограничение по размеру: ≤ 2 MB.
POST /file/content
Получить содержимое текстового файла.
Body (JSON):
{"url":"https://cdn.denchikp.com/public/files/app.js"}
Response 200 (пример):
{
"status":"ok",
"url":"https://cdn.denchikp.com/public/files/app.js",
"domain":"public",
"folder":"files",
"filename":"app.js",
"mime_type":"application/javascript",
"size":1200,
"content":"console.log('hi');\n"
}
Ошибки:
- 400 — файл не текстовый / >2MB / неподдерживаемая кодировка
- 404 — файл не найден
POST /file/save
Сохранить содержимое текстового файла.
Body (JSON):
{"url":"https://cdn.denchikp.com/public/files/app.js","content":"console.log('hi');\n"}
Response 200:
{"status":"ok","message":"Файл успешно сохранен","url":"...","domain":"public","folder":"files","filename":"app.js","size":1200}
Webhook event: FILE_SAVE
Webhooks (Reference)
Webhooks позволяют получать события от сервиса (UPLOAD/DELETE и т.д.) на ваш URL.
Создание / управление
POST /webhooks
Создать webhook.
Headers:
X-Api-Key: <API_KEY>
Body (JSON):
{
"url":"https://example.com/hook",
"events":["UPLOAD","DELETE"],
"secret":"optional",
"enabled":true
}
Response 200:
{"status":"ok","id":123}
Ошибки:
- 401/403 — нет доступа
GET /webhooks
Список webhooks текущего ключа.
Response 200 (пример):
{
"status":"ok",
"webhooks":[
{
"id":123,
"url":"https://example.com/hook",
"events":["UPLOAD","DELETE"],
"enabled":true,
"created_at":"2026-06-16T12:00:00"
}
]
}
DELETE /webhooks/{webhook_id}
Удалить webhook.
Path params:
webhook_id— integer
Response 200:
{"status":"ok"}
Ошибки:
- 404 — webhook не найден (или не принадлежит ключу)
Delivery format
Сервер отправляет на URL webhook’а запрос:
- Method: POST
- Content-Type: application/json
- Body:
{"event":"UPLOAD","payload":{...}}
События
События, которые реально используются в текущем коде:
UPLOADDELETEBULK_DELETEFILE_MOVEFILE_COPYFILE_SAVESCAN_FINISHED
Подпись (HMAC)
Если у webhook задан secret и на сервере включена подпись, рекомендуется добавлять:
Headers:
X-Webhook-Event: <EVENT>X-Webhook-Timestamp: <unix_seconds>X-Webhook-Signature: sha256=<hex>
Алгоритм:
- base =
{timestamp}.{raw_body} - signature =
HMAC_SHA256(secret, base)(hex) - получатель проверяет подпись и свежесть timestamp (например ±300 секунд)
Примечание: в текущем коде поле secret хранится, но подпись нужно добавить отдельным патчем (если ещё не добавлено).