1260 lines
39 KiB
Rust
1260 lines
39 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
use serde_json::Value;
|
|
use utoipa::{IntoParams, OpenApi, ToSchema};
|
|
use uuid::Uuid;
|
|
|
|
#[derive(OpenApi)]
|
|
#[openapi(
|
|
paths(
|
|
doc::health_check,
|
|
doc::login,
|
|
doc::signup_start,
|
|
doc::signup_finish,
|
|
doc::refresh,
|
|
doc::logout,
|
|
doc::me,
|
|
doc::list_tenants,
|
|
doc::select_tenant,
|
|
doc::passkey_register_start,
|
|
doc::passkey_register_finish,
|
|
doc::passkey_login_start,
|
|
doc::passkey_login_finish,
|
|
doc::list_documents,
|
|
doc::check_document,
|
|
doc::upload_document,
|
|
doc::get_document,
|
|
doc::update_document,
|
|
doc::delete_document,
|
|
doc::download_with_token,
|
|
doc::move_document,
|
|
doc::assign_tags,
|
|
doc::remove_tag,
|
|
doc::bulk_move_documents,
|
|
doc::bulk_update_tags,
|
|
doc::bulk_assign_correspondents,
|
|
doc::assign_correspondents,
|
|
doc::remove_correspondent,
|
|
doc::reanalyze_selected_documents,
|
|
doc::list_document_assets,
|
|
doc::request_document_assets,
|
|
doc::get_document_asset,
|
|
doc::create_folder,
|
|
doc::ensure_folder_path,
|
|
doc::get_folder,
|
|
doc::list_folder_contents,
|
|
doc::delete_folder,
|
|
doc::update_folder,
|
|
doc::list_tags,
|
|
doc::create_tag,
|
|
doc::update_tag,
|
|
doc::delete_tag,
|
|
doc::list_document_types,
|
|
doc::create_document_type,
|
|
doc::update_document_type,
|
|
doc::delete_document_type,
|
|
doc::list_correspondents,
|
|
doc::create_correspondent,
|
|
doc::update_correspondent,
|
|
doc::delete_correspondent,
|
|
doc::list_webdav_tokens,
|
|
doc::create_webdav_token,
|
|
doc::delete_webdav_token,
|
|
doc::list_passkeys,
|
|
doc::delete_passkey,
|
|
),
|
|
components(
|
|
schemas(
|
|
schemas::LoginRequest,
|
|
schemas::SignupStartRequest,
|
|
schemas::SignupStartResponse,
|
|
schemas::SignupFinishRequest,
|
|
schemas::AccessTokenResponse,
|
|
schemas::TenantSnippet,
|
|
schemas::TenantSelectionResponse,
|
|
schemas::TenantSelectionRequest,
|
|
schemas::TenantListResponse,
|
|
schemas::LoginResponseVariants,
|
|
schemas::RegistrationChallengeResponse,
|
|
schemas::AuthenticationChallengeResponse,
|
|
schemas::PasskeySummary,
|
|
schemas::PasskeyRegistrationFinishPayload,
|
|
schemas::PasskeyLoginStartPayload,
|
|
schemas::PasskeyLoginFinishPayload,
|
|
schemas::DocumentResponse,
|
|
schemas::DocumentDetailResponse,
|
|
schemas::DocumentVersionResponse,
|
|
schemas::DocumentVersionDetailResponse,
|
|
schemas::DocumentAssetSummary,
|
|
schemas::DocumentAssetDetail,
|
|
schemas::DocumentAssetObject,
|
|
schemas::DocumentTypeResponse,
|
|
schemas::CreateDocumentTypeRequest,
|
|
schemas::UpdateDocumentTypeRequest,
|
|
schemas::DocumentCorrespondent,
|
|
schemas::DocumentTag,
|
|
schemas::UpdateDocumentRequest,
|
|
schemas::BulkMoveDocumentsRequest,
|
|
schemas::BulkMoveDocumentsResponse,
|
|
schemas::AssignTagsRequest,
|
|
schemas::MoveDocumentRequest,
|
|
schemas::BulkTagRequest,
|
|
schemas::BulkTagResponse,
|
|
schemas::CorrespondentAssignment,
|
|
schemas::BulkTagAction,
|
|
schemas::BulkCorrespondentsRequest,
|
|
schemas::BulkCorrespondentsResponse,
|
|
schemas::BulkCorrespondentAction,
|
|
schemas::AssignCorrespondentsRequest,
|
|
schemas::ReanalyzeRequest,
|
|
schemas::ReanalyzeResponse,
|
|
schemas::DocumentAssetRequestParams,
|
|
schemas::AssetObjectsQuery,
|
|
schemas::DocumentCheckQuery,
|
|
schemas::DocumentCheckResponse,
|
|
schemas::UploadDocumentForm,
|
|
schemas::CreateFolderRequest,
|
|
schemas::EnsureFolderPathRequest,
|
|
schemas::FolderResponse,
|
|
schemas::FolderInfo,
|
|
schemas::FolderContentsResponse,
|
|
schemas::FolderDocumentSummary,
|
|
schemas::UpdateFolderRequest,
|
|
schemas::FolderContentsParams,
|
|
schemas::TagCatalogEntry,
|
|
schemas::CreateTagRequest,
|
|
schemas::UpdateTagRequest,
|
|
schemas::CorrespondentUsage,
|
|
schemas::CorrespondentCatalogEntry,
|
|
schemas::CreateCorrespondentRequest,
|
|
schemas::UpdateCorrespondentRequest,
|
|
schemas::WebdavTokenResponse,
|
|
schemas::WebdavTokenCreatedResponse,
|
|
schemas::CreateWebdavTokenRequest,
|
|
)
|
|
),
|
|
tags(
|
|
(name = "Health", description = "Service health"),
|
|
(name = "Auth", description = "Authentication"),
|
|
(name = "Documents", description = "Document management"),
|
|
(name = "Assets", description = "Document assets"),
|
|
(name = "Folders", description = "Folder management"),
|
|
(name = "Tags", description = "Tag catalog"),
|
|
(name = "DocumentTypes", description = "Document type catalog"),
|
|
(name = "Correspondents", description = "Correspondent catalog"),
|
|
(name = "Profile", description = "User profile and WebDAV tokens")
|
|
)
|
|
)]
|
|
pub struct ApiDoc;
|
|
|
|
#[allow(dead_code)]
|
|
mod doc {
|
|
use super::schemas::*;
|
|
use uuid::Uuid;
|
|
|
|
#[allow(dead_code)]
|
|
fn __keep_uuid_import() {
|
|
let _ = Uuid::nil();
|
|
}
|
|
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/api/health",
|
|
responses((status = 200, description = "Service is healthy")),
|
|
tag = "Health"
|
|
)]
|
|
pub(super) fn health_check() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/auth/login",
|
|
request_body = LoginRequest,
|
|
responses(
|
|
(status = 200, description = "Login succeeded", body = LoginResponseVariants),
|
|
(status = 401, description = "Invalid credentials")
|
|
),
|
|
tag = "Auth"
|
|
)]
|
|
pub(super) fn login() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/auth/signup/start",
|
|
request_body = SignupStartRequest,
|
|
responses(
|
|
(status = 200, description = "Signup challenge created", body = SignupStartResponse),
|
|
(status = 400, description = "Invalid signup request"),
|
|
(status = 409, description = "Username already exists")
|
|
),
|
|
tag = "Auth"
|
|
)]
|
|
pub(super) fn signup_start() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/auth/signup/finish",
|
|
request_body = SignupFinishRequest,
|
|
responses(
|
|
(status = 200, description = "Signup completed", body = LoginResponseVariants),
|
|
(status = 400, description = "Invalid signup completion"),
|
|
(status = 409, description = "Username already exists")
|
|
),
|
|
tag = "Auth"
|
|
)]
|
|
pub(super) fn signup_finish() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/auth/refresh",
|
|
responses(
|
|
(status = 200, description = "Refreshed access token", body = AccessTokenResponse),
|
|
(status = 401, description = "Missing or invalid refresh token")
|
|
),
|
|
tag = "Auth"
|
|
)]
|
|
pub(super) fn refresh() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/auth/logout",
|
|
responses((status = 204, description = "Session revoked")),
|
|
tag = "Auth"
|
|
)]
|
|
pub(super) fn logout() {}
|
|
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/api/auth/me",
|
|
responses((status = 200, description = "Authenticated principal", body = AccessTokenResponse)),
|
|
tag = "Auth"
|
|
)]
|
|
pub(super) fn me() {}
|
|
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/api/auth/tenants",
|
|
responses((status = 200, description = "Available tenants", body = TenantListResponse)),
|
|
tag = "Auth"
|
|
)]
|
|
pub(super) fn list_tenants() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/auth/select-tenant",
|
|
request_body = TenantSelectionRequest,
|
|
responses((status = 200, description = "Tenant selected", body = AccessTokenResponse)),
|
|
tag = "Auth"
|
|
)]
|
|
pub(super) fn select_tenant() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/auth/passkeys/register/start",
|
|
responses((status = 200, description = "Passkey registration challenge", body = RegistrationChallengeResponse)),
|
|
tag = "Auth"
|
|
)]
|
|
pub(super) fn passkey_register_start() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/auth/passkeys/register/finish",
|
|
request_body = PasskeyRegistrationFinishPayload,
|
|
responses((status = 200, description = "Passkey registered", body = PasskeySummary)),
|
|
tag = "Auth"
|
|
)]
|
|
pub(super) fn passkey_register_finish() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/auth/passkeys/login/start",
|
|
request_body = PasskeyLoginStartPayload,
|
|
responses((status = 200, description = "Passkey authentication challenge", body = AuthenticationChallengeResponse)),
|
|
tag = "Auth"
|
|
)]
|
|
pub(super) fn passkey_login_start() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/auth/passkeys/login/finish",
|
|
request_body = PasskeyLoginFinishPayload,
|
|
responses(
|
|
(status = 200, description = "Passkey login successful", body = LoginResponseVariants),
|
|
(status = 401, description = "Authentication failed")
|
|
),
|
|
tag = "Auth"
|
|
)]
|
|
pub(super) fn passkey_login_finish() {}
|
|
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/api/documents",
|
|
params(DocumentListQuery),
|
|
responses((status = 200, description = "List documents", body = [DocumentResponse])),
|
|
tag = "Documents"
|
|
)]
|
|
pub(super) fn list_documents() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/documents",
|
|
request_body = UploadDocumentForm,
|
|
responses(
|
|
(status = 201, description = "Document created", body = DocumentDetailResponse),
|
|
(status = 200, description = "Existing document reused", body = DocumentDetailResponse),
|
|
(status = 204, description = "Upload skipped because the document already exists")
|
|
),
|
|
tag = "Documents"
|
|
)]
|
|
pub(super) fn upload_document() {}
|
|
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/api/documents/check",
|
|
params(DocumentCheckQuery),
|
|
responses((status = 200, description = "Checksum lookup", body = DocumentCheckResponse)),
|
|
tag = "Documents"
|
|
)]
|
|
pub(super) fn check_document() {}
|
|
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/api/documents/{id}",
|
|
params(("id" = Uuid, Path, description = "Document ID")),
|
|
responses((status = 200, description = "Document detail", body = DocumentDetailResponse)),
|
|
tag = "Documents"
|
|
)]
|
|
pub(super) fn get_document() {}
|
|
|
|
#[utoipa::path(
|
|
patch,
|
|
path = "/api/documents/{id}",
|
|
params(("id" = Uuid, Path, description = "Document ID")),
|
|
request_body = UpdateDocumentRequest,
|
|
responses((status = 200, description = "Updated document", body = DocumentDetailResponse)),
|
|
tag = "Documents"
|
|
)]
|
|
pub(super) fn update_document() {}
|
|
|
|
#[utoipa::path(
|
|
delete,
|
|
path = "/api/documents/{id}",
|
|
params(("id" = Uuid, Path, description = "Document ID")),
|
|
responses((status = 204, description = "Document deleted")),
|
|
tag = "Documents"
|
|
)]
|
|
pub(super) fn delete_document() {}
|
|
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/api/documents/{id}/versions",
|
|
params(("id" = Uuid, Path, description = "Document ID")),
|
|
responses((status = 200, description = "Document versions", body = [DocumentVersionResponse])),
|
|
tag = "Documents"
|
|
)]
|
|
pub(super) fn list_document_versions() {}
|
|
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/api/documents/{id}/versions/{version_id}",
|
|
params(
|
|
("id" = Uuid, Path, description = "Document ID"),
|
|
("version_id" = Uuid, Path, description = "Version ID"),
|
|
),
|
|
responses((status = 200, description = "Document version detail", body = DocumentVersionDetailResponse)),
|
|
tag = "Documents"
|
|
)]
|
|
pub(super) fn get_document_version() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/documents/{id}/restore",
|
|
params(("id" = Uuid, Path, description = "Document ID")),
|
|
request_body = RestoreDocumentRequest,
|
|
responses((status = 204, description = "Document restored")),
|
|
tag = "Documents"
|
|
)]
|
|
pub(super) fn restore_document() {}
|
|
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/download/{token}",
|
|
params(("token" = String, Path, description = "Download token")),
|
|
responses((status = 302, description = "Redirect to pre-signed URL")),
|
|
tag = "Documents"
|
|
)]
|
|
pub(super) fn download_with_token() {}
|
|
|
|
#[utoipa::path(
|
|
patch,
|
|
path = "/api/documents/{id}/folder",
|
|
params(("id" = Uuid, Path, description = "Document ID")),
|
|
request_body = MoveDocumentRequest,
|
|
responses((status = 204, description = "Document moved")),
|
|
tag = "Documents"
|
|
)]
|
|
pub(super) fn move_document() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/documents/{id}/tags",
|
|
params(("id" = Uuid, Path, description = "Document ID")),
|
|
request_body = AssignTagsRequest,
|
|
responses((status = 204, description = "Tags assigned")),
|
|
tag = "Documents"
|
|
)]
|
|
pub(super) fn assign_tags() {}
|
|
|
|
#[utoipa::path(
|
|
delete,
|
|
path = "/api/documents/{id}/tags/{tag_id}",
|
|
params(
|
|
("id" = Uuid, Path, description = "Document ID"),
|
|
("tag_id" = Uuid, Path, description = "Tag ID")
|
|
),
|
|
responses((status = 204, description = "Tag removed")),
|
|
tag = "Documents"
|
|
)]
|
|
pub(super) fn remove_tag() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/documents/bulk/move",
|
|
request_body = BulkMoveDocumentsRequest,
|
|
responses((status = 200, description = "Bulk move outcome", body = BulkMoveDocumentsResponse)),
|
|
tag = "Documents"
|
|
)]
|
|
pub(super) fn bulk_move_documents() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/documents/bulk/tags",
|
|
request_body = BulkTagRequest,
|
|
responses((status = 200, description = "Bulk tag outcome", body = BulkTagResponse)),
|
|
tag = "Documents"
|
|
)]
|
|
pub(super) fn bulk_update_tags() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/documents/bulk/correspondents",
|
|
request_body = BulkCorrespondentsRequest,
|
|
responses((status = 200, description = "Bulk correspondents outcome", body = BulkCorrespondentsResponse)),
|
|
tag = "Documents"
|
|
)]
|
|
pub(super) fn bulk_assign_correspondents() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/documents/{id}/correspondents",
|
|
params(("id" = Uuid, Path, description = "Document ID")),
|
|
request_body = AssignCorrespondentsRequest,
|
|
responses((status = 204, description = "Correspondents assigned")),
|
|
tag = "Documents"
|
|
)]
|
|
pub(super) fn assign_correspondents() {}
|
|
|
|
#[utoipa::path(
|
|
delete,
|
|
path = "/api/documents/{id}/correspondents/{correspondent_id}",
|
|
params(
|
|
("id" = Uuid, Path, description = "Document ID"),
|
|
("correspondent_id" = Uuid, Path, description = "Correspondent ID")
|
|
),
|
|
responses((status = 204, description = "Correspondent removed")),
|
|
tag = "Documents"
|
|
)]
|
|
pub(super) fn remove_correspondent() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/documents/bulk/reanalyze",
|
|
request_body = ReanalyzeRequest,
|
|
responses((status = 200, description = "Reanalyze queued", body = ReanalyzeResponse)),
|
|
tag = "Documents"
|
|
)]
|
|
pub(super) fn reanalyze_selected_documents() {}
|
|
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/api/documents/{id}/assets",
|
|
params(("id" = Uuid, Path, description = "Document ID")),
|
|
responses((status = 200, description = "Document assets", body = [DocumentAssetSummary])),
|
|
tag = "Assets"
|
|
)]
|
|
pub(super) fn list_document_assets() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/documents/{id}/assets",
|
|
params(
|
|
("id" = Uuid, Path, description = "Document ID"),
|
|
DocumentAssetRequestParams
|
|
),
|
|
responses((status = 202, description = "Asset generation requested")),
|
|
tag = "Assets"
|
|
)]
|
|
pub(super) fn request_document_assets() {}
|
|
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/api/assets/{asset_id}",
|
|
params(
|
|
("asset_id" = Uuid, Path, description = "Asset ID"),
|
|
AssetObjectsQuery
|
|
),
|
|
responses((status = 200, description = "Asset detail", body = DocumentAssetDetail)),
|
|
tag = "Assets"
|
|
)]
|
|
pub(super) fn get_document_asset() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/folders",
|
|
request_body = CreateFolderRequest,
|
|
responses(
|
|
(status = 201, description = "Folder created", body = FolderResponse),
|
|
(status = 200, description = "Folder already existed", body = FolderResponse)
|
|
),
|
|
tag = "Folders"
|
|
)]
|
|
pub(super) fn create_folder() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/folders/path",
|
|
request_body = EnsureFolderPathRequest,
|
|
responses((status = 200, description = "Folder path ensured", body = FolderResponse)),
|
|
tag = "Folders"
|
|
)]
|
|
pub(super) fn ensure_folder_path() {}
|
|
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/api/folders/{id}",
|
|
params(("id" = Uuid, Path, description = "Folder ID")),
|
|
responses((status = 200, description = "Folder detail", body = FolderResponse)),
|
|
tag = "Folders"
|
|
)]
|
|
pub(super) fn get_folder() {}
|
|
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/api/folders/{id}/contents",
|
|
params(
|
|
("id" = Uuid, Path, description = "Folder ID"),
|
|
FolderContentsParams
|
|
),
|
|
responses((status = 200, description = "Folder contents", body = FolderContentsResponse)),
|
|
tag = "Folders"
|
|
)]
|
|
pub(super) fn list_folder_contents() {}
|
|
|
|
#[utoipa::path(
|
|
delete,
|
|
path = "/api/folders/{id}",
|
|
params(("id" = Uuid, Path, description = "Folder ID")),
|
|
responses((status = 204, description = "Folder deleted")),
|
|
tag = "Folders"
|
|
)]
|
|
pub(super) fn delete_folder() {}
|
|
|
|
#[utoipa::path(
|
|
patch,
|
|
path = "/api/folders/{id}",
|
|
params(("id" = Uuid, Path, description = "Folder ID")),
|
|
request_body = UpdateFolderRequest,
|
|
responses((status = 204, description = "Folder updated")),
|
|
tag = "Folders"
|
|
)]
|
|
pub(super) fn update_folder() {}
|
|
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/api/tags",
|
|
responses((status = 200, description = "Tags", body = [TagCatalogEntry])),
|
|
tag = "Tags"
|
|
)]
|
|
pub(super) fn list_tags() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/tags",
|
|
request_body = CreateTagRequest,
|
|
responses((status = 200, description = "Tag created", body = TagCatalogEntry)),
|
|
tag = "Tags"
|
|
)]
|
|
pub(super) fn create_tag() {}
|
|
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/api/document-types",
|
|
responses((status = 200, description = "Document types", body = [DocumentTypeResponse])),
|
|
tag = "DocumentTypes"
|
|
)]
|
|
pub(super) fn list_document_types() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/document-types",
|
|
request_body = CreateDocumentTypeRequest,
|
|
responses((status = 201, description = "Document type created", body = DocumentTypeResponse)),
|
|
tag = "DocumentTypes"
|
|
)]
|
|
pub(super) fn create_document_type() {}
|
|
|
|
#[utoipa::path(
|
|
patch,
|
|
path = "/api/document-types/{id}",
|
|
params(("id" = Uuid, Path, description = "Document type ID")),
|
|
request_body = UpdateDocumentTypeRequest,
|
|
responses((status = 200, description = "Document type updated", body = DocumentTypeResponse)),
|
|
tag = "DocumentTypes"
|
|
)]
|
|
pub(super) fn update_document_type() {}
|
|
|
|
#[utoipa::path(
|
|
delete,
|
|
path = "/api/document-types/{id}",
|
|
params(("id" = Uuid, Path, description = "Document type ID")),
|
|
responses((status = 204, description = "Document type deleted")),
|
|
tag = "DocumentTypes"
|
|
)]
|
|
pub(super) fn delete_document_type() {}
|
|
|
|
#[utoipa::path(
|
|
patch,
|
|
path = "/api/tags/{id}",
|
|
params(("id" = Uuid, Path, description = "Tag ID")),
|
|
request_body = UpdateTagRequest,
|
|
responses((status = 200, description = "Tag updated", body = TagCatalogEntry)),
|
|
tag = "Tags"
|
|
)]
|
|
pub(super) fn update_tag() {}
|
|
|
|
#[utoipa::path(
|
|
delete,
|
|
path = "/api/tags/{id}",
|
|
params(("id" = Uuid, Path, description = "Tag ID")),
|
|
responses((status = 204, description = "Tag deleted")),
|
|
tag = "Tags"
|
|
)]
|
|
pub(super) fn delete_tag() {}
|
|
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/api/correspondents",
|
|
responses((status = 200, description = "Correspondents", body = [CorrespondentCatalogEntry])),
|
|
tag = "Correspondents"
|
|
)]
|
|
pub(super) fn list_correspondents() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/correspondents",
|
|
request_body = CreateCorrespondentRequest,
|
|
responses((status = 200, description = "Correspondent created", body = CorrespondentCatalogEntry)),
|
|
tag = "Correspondents"
|
|
)]
|
|
pub(super) fn create_correspondent() {}
|
|
|
|
#[utoipa::path(
|
|
patch,
|
|
path = "/api/correspondents/{id}",
|
|
params(("id" = Uuid, Path, description = "Correspondent ID")),
|
|
request_body = UpdateCorrespondentRequest,
|
|
responses((status = 200, description = "Correspondent updated", body = CorrespondentCatalogEntry)),
|
|
tag = "Correspondents"
|
|
)]
|
|
pub(super) fn update_correspondent() {}
|
|
|
|
#[utoipa::path(
|
|
delete,
|
|
path = "/api/correspondents/{id}",
|
|
params(("id" = Uuid, Path, description = "Correspondent ID")),
|
|
responses((status = 204, description = "Correspondent deleted")),
|
|
tag = "Correspondents"
|
|
)]
|
|
pub(super) fn delete_correspondent() {}
|
|
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/api/profile/webdav-tokens",
|
|
responses((status = 200, description = "List WebDAV tokens", body = [WebdavTokenResponse])),
|
|
tag = "Profile"
|
|
)]
|
|
pub(super) fn list_webdav_tokens() {}
|
|
|
|
#[utoipa::path(
|
|
post,
|
|
path = "/api/profile/webdav-tokens",
|
|
request_body = CreateWebdavTokenRequest,
|
|
responses((status = 201, description = "WebDAV token created", body = WebdavTokenCreatedResponse)),
|
|
tag = "Profile"
|
|
)]
|
|
pub(super) fn create_webdav_token() {}
|
|
|
|
#[utoipa::path(
|
|
delete,
|
|
path = "/api/profile/webdav-tokens/{id}",
|
|
params(("id" = Uuid, Path, description = "WebDAV token ID")),
|
|
responses((status = 204, description = "WebDAV token revoked")),
|
|
tag = "Profile"
|
|
)]
|
|
pub(super) fn delete_webdav_token() {}
|
|
|
|
#[utoipa::path(
|
|
get,
|
|
path = "/api/profile/passkeys",
|
|
responses((status = 200, description = "List registered passkeys", body = [PasskeySummary])),
|
|
tag = "Profile"
|
|
)]
|
|
pub(super) fn list_passkeys() {}
|
|
|
|
#[utoipa::path(
|
|
delete,
|
|
path = "/api/profile/passkeys/{id}",
|
|
params(
|
|
("id" = Uuid, Path, description = "Passkey ID"),
|
|
("reason" = Option<String>, Query, description = "Optional reason for revoking the passkey")
|
|
),
|
|
responses((status = 204, description = "Passkey revoked")),
|
|
tag = "Profile"
|
|
)]
|
|
pub(super) fn delete_passkey() {}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::ApiDoc;
|
|
use utoipa::OpenApi;
|
|
|
|
#[test]
|
|
fn openapi_serializes() {
|
|
let spec = ApiDoc::openapi();
|
|
let _ = serde_json::to_string(&spec).expect("serialize openapi");
|
|
}
|
|
}
|
|
|
|
pub mod schemas {
|
|
use super::*;
|
|
|
|
pub use crate::auth::passkeys::{
|
|
AuthenticationChallengeResponse, PasskeyLoginFinishPayload, PasskeyLoginStartPayload,
|
|
PasskeyRegistrationFinishPayload, PasskeySummary, RegistrationChallengeResponse,
|
|
};
|
|
use webauthn_rs::prelude::RegisterPublicKeyCredential;
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct LoginRequest {
|
|
pub username: String,
|
|
pub password: String,
|
|
#[schema(nullable)]
|
|
pub preferred_tenant_id: Option<Uuid>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct SignupStartRequest {
|
|
pub username: String,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct SignupStartResponse {
|
|
pub signup_token: String,
|
|
pub challenge: RegistrationChallengeResponse,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct SignupFinishRequest {
|
|
pub signup_token: String,
|
|
#[schema(value_type = Object)]
|
|
pub credential: RegisterPublicKeyCredential,
|
|
#[schema(nullable)]
|
|
pub nickname: Option<String>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct AccessTokenResponse {
|
|
pub access_token: String,
|
|
pub token_type: String,
|
|
pub expires_in: i64,
|
|
pub tenant: TenantSnippet,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct TenantSnippet {
|
|
pub id: Uuid,
|
|
pub name: String,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct TenantSelectionResponse {
|
|
pub access_token: String,
|
|
pub tenants: Vec<TenantSnippet>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct TenantSelectionRequest {
|
|
pub tenant_id: Uuid,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct TenantListResponse {
|
|
pub tenants: Vec<TenantSnippet>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
#[serde(untagged)]
|
|
pub enum LoginResponseVariants {
|
|
Token(AccessTokenResponse),
|
|
Selection(TenantSelectionResponse),
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, IntoParams, ToSchema)]
|
|
#[into_params(parameter_in = Query)]
|
|
pub struct DocumentListQuery {
|
|
pub folder_id: Option<Uuid>,
|
|
#[schema(nullable)]
|
|
pub include_descendants: Option<bool>,
|
|
pub query: Option<String>,
|
|
pub tags: Option<String>,
|
|
pub correspondents: Option<String>,
|
|
pub document_types: Option<String>,
|
|
#[serde(default = "default_document_status_filter")]
|
|
#[schema(default = "active")]
|
|
pub status: DocumentStatusFilter,
|
|
}
|
|
|
|
fn default_document_status_filter() -> DocumentStatusFilter {
|
|
DocumentStatusFilter::Active
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum DocumentStatusFilter {
|
|
Active,
|
|
Deleted,
|
|
All,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct DocumentTag {
|
|
pub id: Uuid,
|
|
pub label: String,
|
|
#[schema(nullable)]
|
|
pub color: Option<String>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct DocumentAssetObject {
|
|
pub id: Uuid,
|
|
pub ordinal: i32,
|
|
pub metadata: Value,
|
|
#[schema(nullable)]
|
|
pub url: Option<String>,
|
|
#[schema(nullable)]
|
|
pub expires_at: Option<i64>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct DocumentAssetSummary {
|
|
pub id: Uuid,
|
|
pub asset_type: String,
|
|
pub mime_type: String,
|
|
pub metadata: Value,
|
|
#[schema(nullable)]
|
|
pub cardinality: Option<i32>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct DocumentAssetDetail {
|
|
pub id: Uuid,
|
|
pub asset_type: String,
|
|
pub mime_type: String,
|
|
pub metadata: Value,
|
|
pub created_at: String,
|
|
#[schema(nullable)]
|
|
pub cardinality: Option<i32>,
|
|
pub objects: Vec<DocumentAssetObject>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct DocumentVersionResponse {
|
|
pub id: Uuid,
|
|
pub version_number: i32,
|
|
pub size_bytes: i64,
|
|
pub checksum: String,
|
|
pub created_at: String,
|
|
pub metadata: Value,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct DocumentVersionDetailResponse {
|
|
#[serde(flatten)]
|
|
pub version: DocumentVersionResponse,
|
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
pub assets: Vec<DocumentAssetSummary>,
|
|
pub download_path: String,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct DocumentCorrespondent {
|
|
pub id: Uuid,
|
|
pub name: String,
|
|
pub metadata: Value,
|
|
pub assigned_at: String,
|
|
}
|
|
|
|
pub use crate::routes::document_types::{CreateDocumentTypeRequest, UpdateDocumentTypeRequest};
|
|
pub use crate::routes::documents::DocumentTypeResponse;
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct DocumentResponse {
|
|
pub id: Uuid,
|
|
pub filename: String,
|
|
pub title: String,
|
|
pub original_name: String,
|
|
#[schema(nullable)]
|
|
pub content_type: Option<String>,
|
|
#[schema(nullable)]
|
|
pub folder_id: Option<Uuid>,
|
|
pub created_at: String,
|
|
pub updated_at: String,
|
|
#[schema(nullable)]
|
|
pub deleted_at: Option<String>,
|
|
#[schema(nullable)]
|
|
pub issued_at: Option<String>,
|
|
pub metadata: Value,
|
|
#[schema(nullable)]
|
|
pub document_type_id: Option<Uuid>,
|
|
#[schema(nullable)]
|
|
pub document_type: Option<DocumentTypeResponse>,
|
|
pub tags: Vec<DocumentTag>,
|
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
|
pub correspondents: Vec<DocumentCorrespondent>,
|
|
#[schema(nullable)]
|
|
pub current_version: Option<DocumentVersionDetailResponse>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct DocumentDetailResponse {
|
|
pub document: DocumentResponse,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct DocumentMetadataUpdate {
|
|
pub value: Value,
|
|
#[serde(default)]
|
|
#[schema(default = false)]
|
|
pub replace: bool,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct RestoreDocumentRequest {
|
|
#[schema(nullable)]
|
|
pub folder_id: Option<Uuid>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct UpdateDocumentRequest {
|
|
#[schema(nullable)]
|
|
pub title: Option<String>,
|
|
#[schema(nullable, value_type = Option<String>)]
|
|
pub issued_at: Option<Value>,
|
|
#[schema(nullable)]
|
|
pub metadata: Option<DocumentMetadataUpdate>,
|
|
#[schema(nullable)]
|
|
pub document_type_id: Option<Uuid>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct BulkMoveDocumentsRequest {
|
|
pub document_ids: Vec<Uuid>,
|
|
#[schema(nullable)]
|
|
pub folder_id: Option<Uuid>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct BulkMoveDocumentsResponse {
|
|
pub updated: usize,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct AssignTagsRequest {
|
|
pub tag_ids: Vec<Uuid>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct MoveDocumentRequest {
|
|
#[schema(nullable)]
|
|
pub folder_id: Option<Uuid>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum BulkTagAction {
|
|
Add,
|
|
Remove,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct BulkTagRequest {
|
|
pub document_ids: Vec<Uuid>,
|
|
pub tag_ids: Vec<Uuid>,
|
|
pub action: BulkTagAction,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct BulkTagResponse {
|
|
pub added: usize,
|
|
pub removed: usize,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct CorrespondentAssignment {
|
|
pub correspondent_id: Uuid,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct AssignCorrespondentsRequest {
|
|
pub assignments: Vec<CorrespondentAssignment>,
|
|
#[serde(default)]
|
|
pub replace: bool,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum BulkCorrespondentAction {
|
|
Add,
|
|
Remove,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct BulkCorrespondentsRequest {
|
|
pub document_ids: Vec<Uuid>,
|
|
pub assignments: Vec<CorrespondentAssignment>,
|
|
#[serde(default = "default_bulk_correspondent_action")]
|
|
pub action: BulkCorrespondentAction,
|
|
}
|
|
|
|
fn default_bulk_correspondent_action() -> BulkCorrespondentAction {
|
|
BulkCorrespondentAction::Add
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct BulkCorrespondentsResponse {
|
|
pub assigned: usize,
|
|
pub removed: usize,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct ReanalyzeRequest {
|
|
pub document_ids: Vec<Uuid>,
|
|
#[serde(default)]
|
|
pub force: bool,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct ReanalyzeResponse {
|
|
pub queued: usize,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, IntoParams, ToSchema)]
|
|
#[into_params(parameter_in = Query)]
|
|
pub struct DocumentAssetRequestParams {
|
|
#[serde(default)]
|
|
pub force: Option<bool>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, IntoParams, ToSchema)]
|
|
#[into_params(parameter_in = Query)]
|
|
pub struct AssetObjectsQuery {
|
|
#[serde(default)]
|
|
pub start: Option<i32>,
|
|
#[serde(default)]
|
|
pub limit: Option<i32>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, IntoParams, ToSchema)]
|
|
#[into_params(parameter_in = Query)]
|
|
pub struct DocumentCheckQuery {
|
|
pub checksum: String,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct DocumentCheckResponse {
|
|
pub exists: bool,
|
|
#[schema(nullable)]
|
|
pub document_id: Option<Uuid>,
|
|
#[schema(nullable)]
|
|
pub title: Option<String>,
|
|
#[schema(nullable)]
|
|
pub filename: Option<String>,
|
|
#[schema(nullable)]
|
|
pub version_id: Option<Uuid>,
|
|
#[schema(nullable)]
|
|
pub version_number: Option<i32>,
|
|
#[schema(nullable)]
|
|
pub created_at: Option<String>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct UploadDocumentForm {
|
|
#[schema(value_type = String, format = Binary)]
|
|
pub file: String,
|
|
#[schema(nullable)]
|
|
pub folder_id: Option<Uuid>,
|
|
#[schema(nullable)]
|
|
pub metadata: Option<Value>,
|
|
#[schema(nullable)]
|
|
pub title: Option<String>,
|
|
#[schema(nullable, value_type = Vec<Uuid>)]
|
|
pub tag_ids: Option<Vec<Uuid>>,
|
|
#[schema(nullable, value_type = Vec<CorrespondentAssignment>)]
|
|
pub correspondents: Option<Vec<CorrespondentAssignment>>,
|
|
#[schema(nullable, example = "2024-01-01T00:00:00Z")]
|
|
pub issued_at: Option<String>,
|
|
#[schema(nullable)]
|
|
pub skip_existing: Option<bool>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct CreateFolderRequest {
|
|
pub name: String,
|
|
#[schema(nullable)]
|
|
pub parent_id: Option<Uuid>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct EnsureFolderPathRequest {
|
|
#[schema(nullable)]
|
|
pub parent_id: Option<Uuid>,
|
|
pub segments: Vec<String>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct FolderInfo {
|
|
pub id: Uuid,
|
|
pub name: String,
|
|
#[schema(nullable)]
|
|
pub parent_id: Option<Uuid>,
|
|
pub created_at: String,
|
|
pub updated_at: String,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct FolderResponse {
|
|
pub folder: FolderInfo,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, IntoParams, ToSchema)]
|
|
#[into_params(parameter_in = Query)]
|
|
pub struct FolderContentsParams {
|
|
#[serde(default)]
|
|
pub include_documents: Option<bool>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct FolderDocumentSummary {
|
|
pub id: Uuid,
|
|
pub title: String,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct FolderContentsResponse {
|
|
#[schema(nullable)]
|
|
pub folder: Option<FolderInfo>,
|
|
pub subfolders: Vec<FolderInfo>,
|
|
pub documents: Vec<FolderDocumentSummary>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct UpdateFolderRequest {
|
|
#[schema(nullable)]
|
|
pub parent_id: Option<Option<Uuid>>,
|
|
#[schema(nullable)]
|
|
pub name: Option<String>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct TagCatalogEntry {
|
|
pub id: Uuid,
|
|
pub label: String,
|
|
#[schema(nullable)]
|
|
pub color: Option<String>,
|
|
pub usage_count: i64,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct CreateTagRequest {
|
|
pub label: String,
|
|
#[schema(nullable)]
|
|
pub color: Option<String>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct UpdateTagRequest {
|
|
#[schema(nullable)]
|
|
pub label: Option<Option<String>>,
|
|
#[schema(nullable)]
|
|
pub color: Option<Option<String>>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct CorrespondentUsage {
|
|
pub total: i64,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct CorrespondentCatalogEntry {
|
|
pub id: Uuid,
|
|
pub name: String,
|
|
pub metadata: Value,
|
|
pub created_at: String,
|
|
pub updated_at: String,
|
|
pub usage: CorrespondentUsage,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct CreateCorrespondentRequest {
|
|
pub name: String,
|
|
#[schema(default, value_type = Object)]
|
|
pub metadata: Value,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct UpdateCorrespondentRequest {
|
|
#[schema(nullable)]
|
|
pub name: Option<String>,
|
|
#[schema(nullable)]
|
|
pub metadata: Option<Value>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct WebdavTokenResponse {
|
|
pub id: Uuid,
|
|
pub tenant_id: Uuid,
|
|
#[schema(nullable)]
|
|
pub label: Option<String>,
|
|
pub created_at: String,
|
|
#[schema(nullable)]
|
|
pub last_used_at: Option<String>,
|
|
#[schema(nullable)]
|
|
pub expires_at: Option<String>,
|
|
#[schema(nullable)]
|
|
pub revoked_at: Option<String>,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct WebdavTokenCreatedResponse {
|
|
pub token: String,
|
|
pub token_info: WebdavTokenResponse,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, ToSchema)]
|
|
pub struct CreateWebdavTokenRequest {
|
|
#[schema(nullable)]
|
|
pub label: Option<String>,
|
|
#[schema(nullable, example = "2025-01-01T00:00:00Z")]
|
|
pub expires_at: Option<String>,
|
|
}
|
|
}
|