api/openapi.yaml
Rama main · réplica de lectura del repositorio original.
openapi: 3.1.0
info:
title: ImageSilo API
version: 0.1.0
description: ImageSilo V1 的唯一 HTTP 契约来源。
servers:
- url: /
paths:
/healthz:
get:
operationId: getLiveness
responses:
'200':
description: 进程存活
content:
application/json:
schema:
$ref: '#/components/schemas/HealthResponse'
/readyz:
get:
operationId: getReadiness
responses:
'200':
description: SQLite 连接就绪
content:
application/json:
schema:
$ref: '#/components/schemas/HealthResponse'
'503':
$ref: '#/components/responses/ServiceUnavailable'
/api/v1/setup/status:
get:
operationId: getSetupStatus
responses:
'200':
description: 是否已完成首次初始化
content:
application/json:
schema: { $ref: '#/components/schemas/SetupStatus' }
/api/v1/setup:
post:
operationId: initializeImageSilo
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/SetupRequest' }
responses:
'201':
description: 首次初始化完成并创建管理员 Session
content:
application/json:
schema: { $ref: '#/components/schemas/AdminSession' }
'400':
$ref: '#/components/responses/BadRequest'
'403':
$ref: '#/components/responses/Forbidden'
'409':
$ref: '#/components/responses/Conflict'
/api/v1/auth/login:
post:
operationId: login
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/LoginRequest'
responses:
'200':
description: 登录成功并设置安全 Session Cookie
content:
application/json:
schema:
$ref: '#/components/schemas/AdminSession'
'401':
$ref: '#/components/responses/Unauthorized'
'429':
$ref: '#/components/responses/TooManyRequests'
/api/v1/auth/session:
get:
operationId: getCurrentSession
security:
- sessionCookie: []
responses:
'200':
description: 当前管理员 Session
content:
application/json:
schema:
$ref: '#/components/schemas/AdminSession'
'401':
$ref: '#/components/responses/Unauthorized'
/api/v1/auth/logout:
post:
operationId: logout
security:
- sessionCookie: []
parameters:
- $ref: '#/components/parameters/CSRFToken'
responses:
'204':
description: Session 已撤销
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/api/v1/auth/password:
post:
operationId: changePassword
security:
- sessionCookie: []
parameters:
- $ref: '#/components/parameters/CSRFToken'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/ChangePasswordRequest'
responses:
'204':
description: 密码已更新,当前 Session 保留,其他 Session 已撤销
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/api/v1/auth/profile:
patch:
operationId: updateAdministratorProfile
security:
- sessionCookie: []
parameters:
- $ref: '#/components/parameters/CSRFToken'
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/UpdateProfileRequest' }
responses:
'200':
description: 管理员显示名称和邮箱已更新
content:
application/json:
schema: { $ref: '#/components/schemas/AdminSession' }
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'409':
$ref: '#/components/responses/Conflict'
/api/v1/migration-images:
get:
operationId: listMigrationImages
security:
- sessionCookie: []
parameters:
- name: limit
in: query
schema: { type: integer, minimum: 1, maximum: 100, default: 24 }
- name: cursor
in: query
schema: { type: string }
- name: q
in: query
description: 按迁移图片文件名或完整公网路径搜索
schema: { type: string, maxLength: 256 }
- name: format
in: query
schema: { type: string, enum: [jpeg, png, webp, gif] }
- name: minBytes
in: query
schema: { type: integer, format: int64, minimum: 0 }
- name: maxBytes
in: query
schema: { type: integer, format: int64, minimum: 0 }
responses:
'200':
description: 迁移目录中可公开交付图片的平铺分页列表
content:
application/json:
schema: { $ref: '#/components/schemas/MigrationImageList' }
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
/api/v1/migration-images/batch-delete:
post:
operationId: batchDeleteMigrationImages
security:
- sessionCookie: []
parameters:
- $ref: '#/components/parameters/CSRFToken'
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/BatchMigrationImageRequest' }
responses:
'200':
description: 永久删除迁移图片并递归清理空父目录后的逐项结果
content:
application/json:
schema: { $ref: '#/components/schemas/MigrationImageBatchResult' }
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/api/v1/migration-images/refresh:
post:
operationId: refreshMigrationImages
security:
- sessionCookie: []
parameters:
- $ref: '#/components/parameters/CSRFToken'
responses:
'204':
description: 已重新扫描迁移目录并替换文件列表快照
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'500':
$ref: '#/components/responses/InternalServerError'
/api/v1/images:
get:
operationId: listImages
security:
- sessionCookie: []
parameters:
- name: limit
in: query
schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
- name: cursor
in: query
schema: { type: string }
- name: q
in: query
description: 按原始文件名、标准路径 UUID、历史路径或 SHA-256 搜索
schema: { type: string, maxLength: 200 }
- name: visibility
in: query
schema: { $ref: '#/components/schemas/Visibility' }
- name: format
in: query
schema: { type: string, enum: [jpeg, png, webp, gif] }
- name: uploadedVia
in: query
schema: { type: string, enum: [admin, api_token, import] }
- name: createdFrom
in: query
schema: { type: string, format: date-time }
- name: createdTo
in: query
schema: { type: string, format: date-time }
- name: minBytes
in: query
schema: { type: integer, format: int64, minimum: 0 }
- name: maxBytes
in: query
schema: { type: integer, format: int64, minimum: 0 }
- name: minWidth
in: query
schema: { type: integer, minimum: 0 }
- name: maxWidth
in: query
schema: { type: integer, minimum: 0 }
- name: minHeight
in: query
schema: { type: integer, minimum: 0 }
- name: maxHeight
in: query
schema: { type: integer, minimum: 0 }
responses:
'200':
description: 使用稳定 keyset cursor 的分页图片列表
content:
application/json:
schema:
$ref: '#/components/schemas/ImageList'
'401':
$ref: '#/components/responses/Unauthorized'
'400':
$ref: '#/components/responses/BadRequest'
post:
operationId: uploadImage
security:
- sessionCookie: []
- bearerToken: ['images:upload']
parameters:
- $ref: '#/components/parameters/CSRFTokenForSession'
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [file]
properties:
file:
type: string
format: binary
visibility:
$ref: '#/components/schemas/Visibility'
responses:
'201':
description: 图片上传成功
content:
application/json:
schema:
$ref: '#/components/schemas/Image'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'503':
$ref: '#/components/responses/ServiceUnavailable'
/api/v1/images/batch-delete:
post:
operationId: batchDeleteImages
security:
- sessionCookie: []
- bearerToken: ['images:delete']
parameters:
- $ref: '#/components/parameters/CSRFTokenForSession'
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/BatchImageRequest' }
responses:
'200':
description: 每张图片的永久删除结果;文件清理失败会明确标记
content:
application/json:
schema: { $ref: '#/components/schemas/BatchOperationResult' }
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/api/v1/images/batch-visibility:
patch:
operationId: batchUpdateImageVisibility
security:
- sessionCookie: []
parameters:
- $ref: '#/components/parameters/CSRFToken'
requestBody:
required: true
content:
application/json:
schema: { $ref: '#/components/schemas/BatchVisibilityRequest' }
responses:
'200':
description: 每张图片的可见性修改结果
content:
application/json:
schema: { $ref: '#/components/schemas/BatchOperationResult' }
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/api/v1/images/{imageId}:
get:
operationId: getImageDetail
security:
- sessionCookie: []
parameters:
- $ref: '#/components/parameters/ImageId'
responses:
'200':
description: 图片完整管理详情及历史别名
content:
application/json:
schema: { $ref: '#/components/schemas/ImageDetail' }
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
delete:
operationId: deleteImage
security:
- sessionCookie: []
- bearerToken: ['images:delete']
parameters:
- $ref: '#/components/parameters/ImageId'
- $ref: '#/components/parameters/CSRFTokenForSession'
responses:
'200':
description: 数据库和索引已永久删除;响应说明文件清理是否待巡检
content:
application/json:
schema: { $ref: '#/components/schemas/DeleteImageResult' }
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/api/v1/images/{imageId}/visibility:
patch:
operationId: updateImageVisibility
security:
- sessionCookie: []
parameters:
- $ref: '#/components/parameters/ImageId'
- $ref: '#/components/parameters/CSRFToken'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateImageVisibilityRequest'
responses:
'204':
description: 可见性已提交到 SQLite 并同步更新交付索引
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/api/v1/images/{imageId}/convert-webp:
post:
operationId: convertImageToWebP
security:
- sessionCookie: []
parameters:
- $ref: '#/components/parameters/ImageId'
- $ref: '#/components/parameters/CSRFToken'
responses:
'200':
description: 保持图片 ID 和 URL 不变,将现有 JPEG 或 PNG 原子替换为 WebP
content:
application/json:
schema: { $ref: '#/components/schemas/WebPConversionResult' }
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
'503':
$ref: '#/components/responses/ServiceUnavailable'
/api/v1/images/{imageId}/thumbnail:
get:
operationId: getImageThumbnail
security:
- sessionCookie: []
parameters:
- $ref: '#/components/parameters/ImageId'
responses:
'200':
description: 可重建的后台 JPEG 预览缩略图
content:
image/jpeg:
schema: { type: string, format: binary }
'401':
$ref: '#/components/responses/Unauthorized'
'404':
$ref: '#/components/responses/NotFound'
'503':
$ref: '#/components/responses/ServiceUnavailable'
/image/{imageId}:
get:
operationId: deliverImage
security:
- {}
- sessionCookie: []
- bearerToken: ['images:read_private']
parameters:
- $ref: '#/components/parameters/ImageId'
responses:
'200':
description: 图片字节;私密图片需要 Session 或 images:read_private Scope
headers:
ETag:
schema: { type: string }
Last-Modified:
schema: { type: string }
Cache-Control:
schema: { type: string }
content:
image/jpeg: { schema: { type: string, format: binary } }
image/png: { schema: { type: string, format: binary } }
image/webp: { schema: { type: string, format: binary } }
image/gif: { schema: { type: string, format: binary } }
'206':
description: Range 局部响应
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'503':
$ref: '#/components/responses/ServiceUnavailable'
/api/v1/api-tokens:
get:
operationId: listApiTokens
security:
- sessionCookie: []
responses:
'200':
description: API Token 元数据列表,永不返回 Token 明文
content:
application/json:
schema:
$ref: '#/components/schemas/ApiTokenList'
'401':
$ref: '#/components/responses/Unauthorized'
post:
operationId: createApiToken
security:
- sessionCookie: []
parameters:
- $ref: '#/components/parameters/CSRFToken'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateApiTokenRequest'
responses:
'201':
description: Token 明文仅在本响应返回一次
content:
application/json:
schema:
$ref: '#/components/schemas/CreatedApiToken'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/api/v1/api-tokens/{tokenId}:
delete:
operationId: revokeApiToken
security:
- sessionCookie: []
parameters:
- $ref: '#/components/parameters/TokenId'
- $ref: '#/components/parameters/CSRFToken'
responses:
'204':
description: Token 已吊销并立即从活跃内存索引移除
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/api/v1/settings:
get:
operationId: getSettings
security:
- sessionCookie: []
responses:
'200':
description: 当前可由阶段 2 管理的系统设置
content:
application/json:
schema:
$ref: '#/components/schemas/AppSettings'
'401':
$ref: '#/components/responses/Unauthorized'
/api/v1/settings/default-visibility:
patch:
operationId: updateDefaultVisibility
security:
- sessionCookie: []
parameters:
- $ref: '#/components/parameters/CSRFToken'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateDefaultVisibilityRequest'
responses:
'200':
description: 默认可见性已更新
content:
application/json:
schema:
$ref: '#/components/schemas/AppSettings'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/api/v1/settings/processing:
patch:
operationId: updateProcessingSettings
security:
- sessionCookie: []
parameters:
- $ref: '#/components/parameters/CSRFToken'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/UpdateProcessingSettingsRequest'
responses:
'200':
description: 图片压缩与 WebP 转换设置已更新
content:
application/json:
schema:
$ref: '#/components/schemas/AppSettings'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/api/v1/system:
get:
operationId: getSystemInfo
security:
- sessionCookie: []
responses:
'200':
description: 只读处理并发与上传安全边界
content:
application/json:
schema:
$ref: '#/components/schemas/SystemInfo'
'401':
$ref: '#/components/responses/Unauthorized'
/api/v1/overview:
get:
operationId: getSystemOverview
security:
- sessionCookie: []
responses:
'200':
description: 图片、存储、运行内存、索引和最近维护结果的最小概览
content:
application/json:
schema: { $ref: '#/components/schemas/SystemOverview' }
'401':
$ref: '#/components/responses/Unauthorized'
/api/v1/maintenance/rebuild:
post:
operationId: rebuildIndexes
security:
- sessionCookie: []
parameters:
- $ref: '#/components/parameters/CSRFToken'
responses:
'200':
description: 全部内存索引已在独占变更屏障中整体重建
content:
application/json:
schema: { $ref: '#/components/schemas/RebuildResult' }
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/api/v1/maintenance/inspect:
post:
operationId: inspectConsistency
security:
- sessionCookie: []
parameters:
- $ref: '#/components/parameters/CSRFToken'
responses:
'200':
description: 只读一致性巡检结果;本阶段不自动删除文件
content:
application/json:
schema: { $ref: '#/components/schemas/InspectionResult' }
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
/api/v1/imports:
post:
operationId: importImage
security:
- sessionCookie: []
- bearerToken: ['images:upload', 'aliases:write']
parameters:
- $ref: '#/components/parameters/CSRFTokenForSession'
requestBody:
required: true
content:
multipart/form-data:
schema:
type: object
required: [file, alias]
properties:
file: { type: string, format: binary }
alias: { type: string, maxLength: 2048, example: /i/2022/05/example.webp }
visibility:
$ref: '#/components/schemas/Visibility'
responses:
'201':
description: 原始图片与一个历史别名成功导入
content:
application/json:
schema:
$ref: '#/components/schemas/ImportResult'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'409':
$ref: '#/components/responses/Conflict'
'503':
$ref: '#/components/responses/ServiceUnavailable'
/api/v1/aliases:
get:
operationId: listAliases
security:
- sessionCookie: []
- bearerToken: ['aliases:write']
parameters:
- name: limit
in: query
required: false
schema: { type: integer, minimum: 1, maximum: 100, default: 50 }
- name: cursor
in: query
required: false
schema: { type: string, maxLength: 512 }
responses:
'200':
description: 历史路径映射列表
content:
application/json:
schema:
$ref: '#/components/schemas/ImageAliasList'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
post:
operationId: createAlias
security:
- sessionCookie: []
- bearerToken: ['aliases:write']
parameters:
- $ref: '#/components/parameters/CSRFTokenForSession'
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/CreateAliasRequest'
responses:
'201':
description: 别名创建成功
content:
application/json:
schema:
$ref: '#/components/schemas/ImageAlias'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
'409':
$ref: '#/components/responses/Conflict'
/api/v1/aliases/resolve:
get:
operationId: resolveAlias
security:
- sessionCookie: []
- bearerToken: ['aliases:write']
parameters:
- name: path
in: query
required: true
schema: { type: string, maxLength: 2048 }
responses:
'200':
description: 已解析的历史路径映射
content:
application/json:
schema:
$ref: '#/components/schemas/ImageAlias'
'400':
$ref: '#/components/responses/BadRequest'
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
/api/v1/aliases/{aliasId}:
delete:
operationId: deleteAlias
security:
- sessionCookie: []
- bearerToken: ['aliases:write']
parameters:
- $ref: '#/components/parameters/AliasId'
- $ref: '#/components/parameters/CSRFTokenForSession'
responses:
'204':
description: 历史路径映射已删除
'401':
$ref: '#/components/responses/Unauthorized'
'403':
$ref: '#/components/responses/Forbidden'
'404':
$ref: '#/components/responses/NotFound'
components:
securitySchemes:
sessionCookie:
type: apiKey
in: cookie
name: imagesilo_session
bearerToken:
type: http
scheme: bearer
parameters:
ImageId:
name: imageId
in: path
required: true
schema:
type: string
format: uuid
TokenId:
name: tokenId
in: path
required: true
schema:
type: string
format: uuid
AliasId:
name: aliasId
in: path
required: true
schema:
type: string
format: uuid
CSRFToken:
name: X-CSRF-Token
in: header
required: true
description: 必须与当前 Session 绑定的 imagesilo_csrf Cookie 完全一致。
schema:
type: string
pattern: '^isc_[A-Za-z0-9_-]{43}$'
CSRFTokenForSession:
name: X-CSRF-Token
in: header
required: false
description: 使用管理员 Session 上传时必填;使用 Bearer Token 时忽略。
schema:
type: string
pattern: '^isc_[A-Za-z0-9_-]{43}$'
schemas:
Visibility:
type: string
enum: [public, private]
ApiTokenScope:
type: string
enum: ['images:upload', 'images:read_private', 'images:delete', 'aliases:write']
HealthResponse:
type: object
required: [status]
properties:
status: { type: string }
LoginRequest:
type: object
additionalProperties: false
required: [email, password]
properties:
email: { type: string, format: email, maxLength: 254 }
password: { type: string, minLength: 1, maxLength: 1024 }
SetupStatus:
type: object
required: [initialized]
properties:
initialized: { type: boolean }
SetupRequest:
type: object
additionalProperties: false
required: [bootstrapToken, displayName, email, password, defaultVisibility, compressionEnabled, jpegQuality, webpQuality, pngCompressionLevel, conversionEnabled, conversionWebpQuality, conversionWebpLossless]
properties:
bootstrapToken: { type: string, pattern: '^isb_[A-Za-z0-9_-]{43}$' }
displayName: { type: string, minLength: 1, maxLength: 80 }
email: { type: string, format: email, maxLength: 254 }
password: { type: string, minLength: 12, maxLength: 1024 }
defaultVisibility: { $ref: '#/components/schemas/Visibility' }
compressionEnabled: { type: boolean }
jpegQuality: { type: integer, minimum: 1, maximum: 100 }
webpQuality: { type: integer, minimum: 1, maximum: 100 }
pngCompressionLevel: { type: integer, minimum: 0, maximum: 9 }
conversionEnabled: { type: boolean }
conversionWebpQuality: { type: integer, minimum: 1, maximum: 100 }
conversionWebpLossless: { type: boolean }
AdminSession:
type: object
required: [adminId, displayName, email, csrfToken, expiresAt]
properties:
adminId: { type: string, format: uuid }
displayName: { type: string }
email: { type: string, format: email }
csrfToken: { type: string, pattern: '^isc_[A-Za-z0-9_-]{43}$' }
expiresAt: { type: string, format: date-time }
ChangePasswordRequest:
type: object
additionalProperties: false
required: [currentPassword, newPassword]
properties:
currentPassword: { type: string, minLength: 1, maxLength: 1024 }
newPassword: { type: string, minLength: 12, maxLength: 1024 }
UpdateProfileRequest:
type: object
additionalProperties: false
required: [displayName, email]
properties:
displayName: { type: string, minLength: 1, maxLength: 80 }
email: { type: string, format: email, maxLength: 254 }
Image:
type: object
required: [id, originalName, mimeType, extension, width, height, sourceSize, storedSize, sourceSha256, storedSha256, processingSummary, visibility, uploadedVia, standardUrl, thumbnailUrl, createdAt]
properties:
id: { type: string, format: uuid }
originalName: { type: string }
mimeType: { type: string, enum: [image/jpeg, image/png, image/webp, image/gif] }
extension: { type: string, enum: [.jpg, .png, .webp, .gif] }
width: { type: integer, minimum: 1 }
height: { type: integer, minimum: 1 }
sourceSize: { type: integer, format: int64, minimum: 1 }
storedSize: { type: integer, format: int64, minimum: 1 }
sourceSha256: { type: string, pattern: '^[0-9a-f]{64}$' }
storedSha256: { type: string, pattern: '^[0-9a-f]{64}$' }
processingSummary: { $ref: '#/components/schemas/ProcessingSummary' }
visibility: { $ref: '#/components/schemas/Visibility' }
uploadedVia: { type: string, enum: [admin, api_token, import] }
standardUrl: { type: string }
thumbnailUrl: { type: string }
createdAt: { type: string, format: date-time }
ImageList:
type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/Image' }
nextCursor: { type: string }
MigrationImage:
type: object
required: [path, originalName, mimeType, extension, storedSize, standardUrl, modifiedAt]
properties:
path: { type: string, maxLength: 2048, pattern: '^/' }
originalName: { type: string }
mimeType: { type: string, enum: [image/jpeg, image/png, image/webp, image/gif] }
extension: { type: string, enum: [.jpg, .jpeg, .png, .webp, .gif] }
storedSize: { type: integer, format: int64, minimum: 1 }
standardUrl: { type: string, maxLength: 2048, pattern: '^/' }
modifiedAt: { type: string, format: date-time }
MigrationImageList:
type: object
required: [items, skippedFiles, mutationsEnabled]
properties:
items:
type: array
items: { $ref: '#/components/schemas/MigrationImage' }
nextCursor: { type: string }
skippedFiles: { type: integer, minimum: 0 }
mutationsEnabled: { type: boolean }
BatchMigrationImageRequest:
type: object
additionalProperties: false
required: [paths]
properties:
paths:
type: array
minItems: 1
maxItems: 100
uniqueItems: true
items: { type: string, maxLength: 2048, pattern: '^/' }
MigrationImageOperationItem:
type: object
required: [path, status]
properties:
path: { type: string, maxLength: 2048, pattern: '^/' }
status: { type: string, enum: [deleted, cleanup_pending, not_found, error] }
removedDirectories: { type: integer, minimum: 0 }
directoryCleanupPending: { type: boolean }
errorCode: { type: string }
MigrationImageBatchResult:
type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/MigrationImageOperationItem' }
ImageDetail:
allOf:
- $ref: '#/components/schemas/Image'
- type: object
required: [aliases]
properties:
aliases:
type: array
items: { $ref: '#/components/schemas/ImageAlias' }
DeleteImageResult:
type: object
required: [imageId, imageFileDeleted, thumbnailDeleted, cleanupPending]
properties:
imageId: { type: string, format: uuid }
imageFileDeleted: { type: boolean }
thumbnailDeleted: { type: boolean }
cleanupPending: { type: boolean }
WebPConversionResult:
type: object
required: [image, originalFileDeleted, thumbnailUpdated, cleanupPending]
properties:
image: { $ref: '#/components/schemas/Image' }
originalFileDeleted: { type: boolean }
thumbnailUpdated: { type: boolean }
cleanupPending: { type: boolean }
BatchImageRequest:
type: object
additionalProperties: false
required: [imageIds]
properties:
imageIds:
type: array
minItems: 1
maxItems: 100
uniqueItems: true
items: { type: string, format: uuid }
BatchVisibilityRequest:
allOf:
- $ref: '#/components/schemas/BatchImageRequest'
- type: object
required: [visibility]
properties:
visibility: { $ref: '#/components/schemas/Visibility' }
BatchOperationItem:
type: object
required: [imageId, status]
properties:
imageId: { type: string, format: uuid }
status: { type: string, enum: [deleted, cleanup_pending, updated, not_found, error] }
cleanupPending: { type: boolean }
errorCode: { type: string }
BatchOperationResult:
type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/BatchOperationItem' }
UpdateImageVisibilityRequest:
type: object
additionalProperties: false
required: [visibility]
properties:
visibility: { $ref: '#/components/schemas/Visibility' }
CreateApiTokenRequest:
type: object
additionalProperties: false
required: [name, scopes]
properties:
name: { type: string, minLength: 1, maxLength: 100 }
scopes:
type: array
minItems: 1
uniqueItems: true
items: { $ref: '#/components/schemas/ApiTokenScope' }
expiresAt: { type: [string, 'null'], format: date-time }
CreatedApiToken:
type: object
required: [id, name, token, tokenPrefix, scopes, expiresAt, status, createdAt]
properties:
id: { type: string, format: uuid }
name: { type: string }
token: { type: string, writeOnly: true, pattern: '^ist_[A-Za-z0-9_-]{43}$' }
tokenPrefix: { type: string }
scopes:
type: array
items: { $ref: '#/components/schemas/ApiTokenScope' }
expiresAt: { type: [string, 'null'], format: date-time }
status: { type: string, enum: [active] }
createdAt: { type: string, format: date-time }
ApiToken:
type: object
required: [id, name, tokenPrefix, scopes, expiresAt, status, createdAt]
properties:
id: { type: string, format: uuid }
name: { type: string }
tokenPrefix: { type: string }
scopes:
type: array
items: { $ref: '#/components/schemas/ApiTokenScope' }
expiresAt: { type: [string, 'null'], format: date-time }
status: { type: string, enum: [active, expired, revoked] }
createdAt: { type: string, format: date-time }
ApiTokenList:
type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/ApiToken' }
AppSettings:
type: object
required: [defaultVisibility, compressionEnabled, jpegQuality, webpQuality, pngCompressionLevel, conversionEnabled, conversionWebpQuality, conversionWebpLossless]
properties:
defaultVisibility: { $ref: '#/components/schemas/Visibility' }
compressionEnabled: { type: boolean }
jpegQuality: { type: integer, minimum: 1, maximum: 100 }
webpQuality: { type: integer, minimum: 1, maximum: 100 }
pngCompressionLevel: { type: integer, minimum: 0, maximum: 9 }
conversionEnabled: { type: boolean }
conversionWebpQuality: { type: integer, minimum: 1, maximum: 100 }
conversionWebpLossless: { type: boolean }
UpdateDefaultVisibilityRequest:
type: object
additionalProperties: false
required: [defaultVisibility]
properties:
defaultVisibility: { $ref: '#/components/schemas/Visibility' }
UpdateProcessingSettingsRequest:
type: object
additionalProperties: false
required: [compressionEnabled, jpegQuality, webpQuality, pngCompressionLevel, conversionEnabled, conversionWebpQuality, conversionWebpLossless]
properties:
compressionEnabled: { type: boolean }
jpegQuality: { type: integer, minimum: 1, maximum: 100 }
webpQuality: { type: integer, minimum: 1, maximum: 100 }
pngCompressionLevel: { type: integer, minimum: 0, maximum: 9 }
conversionEnabled: { type: boolean }
conversionWebpQuality: { type: integer, minimum: 1, maximum: 100 }
conversionWebpLossless: { type: boolean }
ProcessingSummary:
type: object
required: [action, sourceFormat, storedFormat, preserved, compressionEnabled, conversionEnabled]
properties:
action: { type: string, enum: [preserve, compress, convert] }
sourceFormat: { type: string, enum: [jpeg, png, webp, gif] }
storedFormat: { type: string, enum: [jpeg, png, webp, gif] }
preserved: { type: boolean }
compressionEnabled: { type: boolean }
conversionEnabled: { type: boolean }
compressionRejected: { type: boolean }
SystemInfo:
type: object
required: [processingConcurrency, deliveryConcurrency, maxBatchCount, maxUploadBytes, maxTotalPixels, supportedFormats, vipsVersion]
properties:
processingConcurrency: { type: integer, minimum: 1 }
deliveryConcurrency:
type: integer
minimum: 0
description: Zero means image delivery concurrency is unlimited.
maxBatchCount: { type: integer, minimum: 1 }
maxUploadBytes: { type: integer, format: int64, minimum: 1 }
maxTotalPixels: { type: integer, format: int64, minimum: 1 }
supportedFormats:
type: array
items: { type: string, enum: [image/jpeg, image/png, image/webp, image/gif] }
vipsVersion: { type: string }
IndexStats:
type: object
required: [images, aliases, sessions, tokens]
properties:
images: { type: integer, minimum: 0 }
aliases: { type: integer, minimum: 0 }
sessions: { type: integer, minimum: 0 }
tokens: { type: integer, minimum: 0 }
InspectionResult:
type: object
required: [checkedAt, databaseImages, imageFiles, thumbnailFiles, temporaryFiles, missingImageCount, missingImageIds, orphanImageCount, orphanThumbnailCount]
properties:
checkedAt: { type: string, format: date-time }
databaseImages: { type: integer, minimum: 0 }
imageFiles: { type: integer, minimum: 0 }
thumbnailFiles: { type: integer, minimum: 0 }
temporaryFiles: { type: integer, minimum: 0 }
missingImageCount: { type: integer, minimum: 0 }
missingImageIds:
type: array
maxItems: 100
items: { type: string, format: uuid }
orphanImageCount: { type: integer, minimum: 0 }
orphanThumbnailCount: { type: integer, minimum: 0 }
RebuildResult:
type: object
required: [completedAt, images, aliases, sessions, tokens, missingImageCount, missingImageIds]
properties:
completedAt: { type: string, format: date-time }
images: { type: integer, minimum: 0 }
aliases: { type: integer, minimum: 0 }
sessions: { type: integer, minimum: 0 }
tokens: { type: integer, minimum: 0 }
missingImageCount: { type: integer, minimum: 0 }
missingImageIds:
type: array
maxItems: 100
items: { type: string, format: uuid }
DailyMaintenanceResult:
type: object
required: [completedAt, inspection, removedTemporaryFiles, removedOrphanImages, removedOrphanThumbnails, cleanupFailures, indexConsistent]
properties:
completedAt: { type: string, format: date-time }
inspection: { $ref: '#/components/schemas/InspectionResult' }
removedTemporaryFiles: { type: integer, minimum: 0 }
removedOrphanImages: { type: integer, minimum: 0 }
removedOrphanThumbnails: { type: integer, minimum: 0 }
cleanupFailures: { type: integer, minimum: 0 }
indexConsistent: { type: boolean }
SystemOverview:
type: object
required: [imageCount, storedBytes, migrationStoredBytes, aliasCount, heapAllocBytes, heapSysBytes, rssBytes, goroutines, indexes, indexConsistent, missingImageCount, missingImageIds, lastInspection, lastRebuild, lastDaily]
properties:
imageCount: { type: integer, format: int64, minimum: 0 }
storedBytes: { type: integer, format: int64, minimum: 0 }
migrationStoredBytes: { type: integer, format: int64, minimum: 0 }
aliasCount: { type: integer, format: int64, minimum: 0 }
heapAllocBytes: { type: integer, format: int64, minimum: 0 }
heapSysBytes: { type: integer, format: int64, minimum: 0 }
rssBytes: { type: integer, format: int64, minimum: 0 }
goroutines: { type: integer, minimum: 0 }
indexes: { $ref: '#/components/schemas/IndexStats' }
indexConsistent: { type: boolean }
missingImageCount: { type: integer, minimum: 0 }
missingImageIds:
type: array
maxItems: 100
items: { type: string, format: uuid }
lastInspection:
oneOf:
- $ref: '#/components/schemas/InspectionResult'
- type: 'null'
lastRebuild:
oneOf:
- $ref: '#/components/schemas/RebuildResult'
- type: 'null'
lastDaily:
oneOf:
- $ref: '#/components/schemas/DailyMaintenanceResult'
- type: 'null'
CreateAliasRequest:
type: object
additionalProperties: false
required: [path, imageId, source]
properties:
path: { type: string }
imageId: { type: string, format: uuid }
source: { type: string, minLength: 1, maxLength: 100 }
ImageAlias:
type: object
required: [id, path, imageId, source, createdAt]
properties:
id: { type: string, format: uuid }
path: { type: string }
imageId: { type: string, format: uuid }
source: { type: string }
createdAt: { type: string, format: date-time }
ImageAliasList:
type: object
required: [items]
properties:
items:
type: array
items: { $ref: '#/components/schemas/ImageAlias' }
nextCursor: { type: string }
ImportResult:
type: object
required: [imageId, standardUrl, sha256, alias]
properties:
imageId: { type: string, format: uuid }
standardUrl: { type: string }
sha256: { type: string, pattern: '^[0-9a-f]{64}$' }
alias: { $ref: '#/components/schemas/ImageAlias' }
Error:
type: object
required: [code, message, requestId]
properties:
code: { type: string }
message: { type: string }
requestId: { type: string }
fields:
type: object
additionalProperties: { type: string }
responses:
BadRequest:
description: 请求无效
content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
Unauthorized:
description: 缺少或认证信息无效
content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
Forbidden:
description: 已认证但 Scope 不足
content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
NotFound:
description: 资源不存在
content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
Conflict:
description: 唯一路径冲突
content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
TooManyRequests:
description: 请求被限速
headers:
Retry-After: { schema: { type: integer, minimum: 1 } }
content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
InternalServerError:
description: 服务器无法完成请求
content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }
ServiceUnavailable:
description: 服务未就绪或图片解码安全门满载
headers:
Retry-After: { schema: { type: integer, minimum: 1 } }
content: { application/json: { schema: { $ref: '#/components/schemas/Error' } } }