openapi cleanup
This commit is contained in:
@@ -18,8 +18,10 @@ pub struct DocumentAssetResponse {
|
|||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub asset_type: String,
|
pub asset_type: String,
|
||||||
pub mime_type: String,
|
pub mime_type: String,
|
||||||
|
#[schema(value_type = Object)]
|
||||||
pub metadata: Value,
|
pub metadata: Value,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[schema(nullable)]
|
||||||
pub cardinality: Option<i32>,
|
pub cardinality: Option<i32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,10 +29,13 @@ pub struct DocumentAssetResponse {
|
|||||||
pub struct DocumentAssetObjectResponse {
|
pub struct DocumentAssetObjectResponse {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub ordinal: i32,
|
pub ordinal: i32,
|
||||||
|
#[schema(value_type = Object)]
|
||||||
pub metadata: Value,
|
pub metadata: Value,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[schema(nullable)]
|
||||||
pub url: Option<String>,
|
pub url: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[schema(nullable)]
|
||||||
pub expires_at: Option<i64>,
|
pub expires_at: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,9 +44,11 @@ pub struct DocumentAssetDetailResponse {
|
|||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub asset_type: String,
|
pub asset_type: String,
|
||||||
pub mime_type: String,
|
pub mime_type: String,
|
||||||
|
#[schema(value_type = Object)]
|
||||||
pub metadata: Value,
|
pub metadata: Value,
|
||||||
pub created_at: String,
|
pub created_at: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[schema(nullable)]
|
||||||
pub cardinality: Option<i32>,
|
pub cardinality: Option<i32>,
|
||||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
pub objects: Vec<DocumentAssetObjectResponse>,
|
pub objects: Vec<DocumentAssetObjectResponse>,
|
||||||
@@ -54,6 +61,7 @@ pub struct DocumentVersionResponse {
|
|||||||
pub size_bytes: i64,
|
pub size_bytes: i64,
|
||||||
pub checksum: String,
|
pub checksum: String,
|
||||||
pub created_at: String,
|
pub created_at: String,
|
||||||
|
#[schema(value_type = Object)]
|
||||||
pub metadata: Value,
|
pub metadata: Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ use crate::utils::time::to_iso;
|
|||||||
pub struct DocumentCorrespondentResponse {
|
pub struct DocumentCorrespondentResponse {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
#[schema(value_type = Object)]
|
||||||
pub metadata: Value,
|
pub metadata: Value,
|
||||||
pub assigned_at: String,
|
pub assigned_at: String,
|
||||||
}
|
}
|
||||||
|
|||||||
+68
-550
@@ -1,7 +1,4 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use utoipa::OpenApi;
|
||||||
use serde_json::Value;
|
|
||||||
use utoipa::{IntoParams, OpenApi, ToSchema};
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
#[derive(OpenApi)]
|
#[derive(OpenApi)]
|
||||||
#[openapi(
|
#[openapi(
|
||||||
@@ -68,68 +65,72 @@ use uuid::Uuid;
|
|||||||
schemas::SignupStartRequest,
|
schemas::SignupStartRequest,
|
||||||
schemas::SignupStartResponse,
|
schemas::SignupStartResponse,
|
||||||
schemas::SignupFinishRequest,
|
schemas::SignupFinishRequest,
|
||||||
schemas::AccessTokenResponse,
|
schemas::LoginResponse,
|
||||||
|
schemas::LoginResponseVariants,
|
||||||
schemas::TenantSnippet,
|
schemas::TenantSnippet,
|
||||||
schemas::TenantSelectionResponse,
|
schemas::TenantSelectionResponse,
|
||||||
schemas::TenantSelectionRequest,
|
schemas::TenantSelectionRequest,
|
||||||
schemas::TenantListResponse,
|
schemas::TenantListResponse,
|
||||||
schemas::LoginResponseVariants,
|
|
||||||
schemas::RegistrationChallengeResponse,
|
schemas::RegistrationChallengeResponse,
|
||||||
schemas::AuthenticationChallengeResponse,
|
schemas::AuthenticationChallengeResponse,
|
||||||
schemas::PasskeySummary,
|
schemas::PasskeySummary,
|
||||||
schemas::PasskeyRegistrationFinishPayload,
|
schemas::PasskeyRegistrationFinishPayload,
|
||||||
schemas::PasskeyLoginStartPayload,
|
schemas::PasskeyLoginStartPayload,
|
||||||
schemas::PasskeyLoginFinishPayload,
|
schemas::PasskeyLoginFinishPayload,
|
||||||
|
schemas::DocumentListQuery,
|
||||||
|
schemas::DocumentStatusFilter,
|
||||||
schemas::DocumentResponse,
|
schemas::DocumentResponse,
|
||||||
schemas::DocumentDetailResponse,
|
schemas::DocumentDetailResponse,
|
||||||
|
schemas::DocumentMetadataUpdate,
|
||||||
schemas::DocumentVersionResponse,
|
schemas::DocumentVersionResponse,
|
||||||
schemas::DocumentVersionDetailResponse,
|
schemas::DocumentVersionDetailResponse,
|
||||||
schemas::DocumentAssetSummary,
|
schemas::DocumentAssetResponse,
|
||||||
schemas::DocumentAssetDetail,
|
schemas::DocumentAssetDetailResponse,
|
||||||
schemas::DocumentAssetObject,
|
schemas::DocumentAssetObjectResponse,
|
||||||
schemas::DocumentTypeResponse,
|
schemas::DocumentTypeResponse,
|
||||||
schemas::CreateDocumentTypeRequest,
|
schemas::CreateDocumentTypeRequest,
|
||||||
schemas::UpdateDocumentTypeRequest,
|
schemas::UpdateDocumentTypeRequest,
|
||||||
schemas::DocumentCorrespondent,
|
schemas::DocumentCorrespondentResponse,
|
||||||
schemas::DocumentTag,
|
schemas::TagResponse,
|
||||||
schemas::UpdateDocumentRequest,
|
schemas::UpdateDocumentRequest,
|
||||||
schemas::BulkMoveDocumentsRequest,
|
schemas::RestoreDocumentRequest,
|
||||||
schemas::BulkMoveDocumentsResponse,
|
schemas::BulkMoveRequest,
|
||||||
|
schemas::BulkMoveResponse,
|
||||||
schemas::AssignTagsRequest,
|
schemas::AssignTagsRequest,
|
||||||
schemas::MoveDocumentRequest,
|
schemas::MoveDocumentRequest,
|
||||||
|
schemas::BulkTagAction,
|
||||||
schemas::BulkTagRequest,
|
schemas::BulkTagRequest,
|
||||||
schemas::BulkTagResponse,
|
schemas::BulkTagResponse,
|
||||||
schemas::CorrespondentAssignment,
|
schemas::CorrespondentAssignmentInput,
|
||||||
schemas::BulkTagAction,
|
|
||||||
schemas::BulkCorrespondentsRequest,
|
|
||||||
schemas::BulkCorrespondentsResponse,
|
|
||||||
schemas::BulkCorrespondentAction,
|
|
||||||
schemas::AssignCorrespondentsRequest,
|
schemas::AssignCorrespondentsRequest,
|
||||||
schemas::ReanalyzeRequest,
|
schemas::BulkCorrespondentAction,
|
||||||
schemas::ReanalyzeResponse,
|
schemas::BulkCorrespondentsRequest,
|
||||||
schemas::DocumentAssetRequestParams,
|
schemas::BulkCorrespondentResponse,
|
||||||
|
schemas::BulkReanalyzeSelectionRequest,
|
||||||
|
schemas::BulkReanalyzeResponse,
|
||||||
|
schemas::AssetRequestQuery,
|
||||||
schemas::AssetObjectsQuery,
|
schemas::AssetObjectsQuery,
|
||||||
schemas::DocumentCheckQuery,
|
schemas::DocumentCheckQuery,
|
||||||
schemas::DocumentCheckResponse,
|
schemas::DocumentCheckResponse,
|
||||||
schemas::UploadDocumentForm,
|
schemas::UploadDocumentForm,
|
||||||
schemas::CreateFolderRequest,
|
schemas::CreateFolderRequest,
|
||||||
schemas::EnsureFolderPathRequest,
|
schemas::EnsureFolderPathRequest,
|
||||||
schemas::FolderResponse,
|
|
||||||
schemas::FolderInfo,
|
schemas::FolderInfo,
|
||||||
|
schemas::FolderResponse,
|
||||||
|
schemas::FolderContentsQuery,
|
||||||
schemas::FolderContentsResponse,
|
schemas::FolderContentsResponse,
|
||||||
schemas::FolderDocumentSummary,
|
|
||||||
schemas::UpdateFolderRequest,
|
schemas::UpdateFolderRequest,
|
||||||
schemas::FolderContentsParams,
|
|
||||||
schemas::TagCatalogEntry,
|
schemas::TagCatalogEntry,
|
||||||
schemas::CreateTagRequest,
|
schemas::CreateTagRequest,
|
||||||
schemas::UpdateTagRequest,
|
schemas::UpdateTagRequest,
|
||||||
schemas::CorrespondentUsage,
|
schemas::CorrespondentUsage,
|
||||||
schemas::CorrespondentCatalogEntry,
|
schemas::CorrespondentSummary,
|
||||||
schemas::CreateCorrespondentRequest,
|
schemas::CreateCorrespondentRequest,
|
||||||
schemas::UpdateCorrespondentRequest,
|
schemas::UpdateCorrespondentRequest,
|
||||||
schemas::WebdavTokenResponse,
|
schemas::WebdavTokenResponse,
|
||||||
schemas::WebdavTokenCreatedResponse,
|
schemas::WebdavTokenCreatedResponse,
|
||||||
schemas::CreateWebdavTokenRequest,
|
schemas::CreateWebdavTokenRequest,
|
||||||
|
schemas::RevokePasskeyQuery,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
tags(
|
tags(
|
||||||
@@ -418,8 +419,8 @@ mod doc {
|
|||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
post,
|
post,
|
||||||
path = "/api/documents/bulk/move",
|
path = "/api/documents/bulk/move",
|
||||||
request_body = BulkMoveDocumentsRequest,
|
request_body = BulkMoveRequest,
|
||||||
responses((status = 200, description = "Bulk move outcome", body = BulkMoveDocumentsResponse)),
|
responses((status = 200, description = "Bulk move outcome", body = BulkMoveResponse)),
|
||||||
tag = "Documents"
|
tag = "Documents"
|
||||||
)]
|
)]
|
||||||
pub(super) fn bulk_move_documents() {}
|
pub(super) fn bulk_move_documents() {}
|
||||||
@@ -437,7 +438,7 @@ mod doc {
|
|||||||
post,
|
post,
|
||||||
path = "/api/documents/bulk/correspondents",
|
path = "/api/documents/bulk/correspondents",
|
||||||
request_body = BulkCorrespondentsRequest,
|
request_body = BulkCorrespondentsRequest,
|
||||||
responses((status = 200, description = "Bulk correspondents outcome", body = BulkCorrespondentsResponse)),
|
responses((status = 200, description = "Bulk correspondents outcome", body = BulkCorrespondentResponse)),
|
||||||
tag = "Documents"
|
tag = "Documents"
|
||||||
)]
|
)]
|
||||||
pub(super) fn bulk_assign_correspondents() {}
|
pub(super) fn bulk_assign_correspondents() {}
|
||||||
@@ -467,8 +468,8 @@ mod doc {
|
|||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
post,
|
post,
|
||||||
path = "/api/documents/bulk/reanalyze",
|
path = "/api/documents/bulk/reanalyze",
|
||||||
request_body = ReanalyzeRequest,
|
request_body = BulkReanalyzeSelectionRequest,
|
||||||
responses((status = 200, description = "Reanalyze queued", body = ReanalyzeResponse)),
|
responses((status = 200, description = "Reanalyze queued", body = BulkReanalyzeResponse)),
|
||||||
tag = "Documents"
|
tag = "Documents"
|
||||||
)]
|
)]
|
||||||
pub(super) fn reanalyze_selected_documents() {}
|
pub(super) fn reanalyze_selected_documents() {}
|
||||||
@@ -477,7 +478,7 @@ mod doc {
|
|||||||
get,
|
get,
|
||||||
path = "/api/documents/{id}/assets",
|
path = "/api/documents/{id}/assets",
|
||||||
params(("id" = Uuid, Path, description = "Document ID")),
|
params(("id" = Uuid, Path, description = "Document ID")),
|
||||||
responses((status = 200, description = "Document assets", body = [DocumentAssetSummary])),
|
responses((status = 200, description = "Document assets", body = [DocumentAssetResponse])),
|
||||||
tag = "Assets"
|
tag = "Assets"
|
||||||
)]
|
)]
|
||||||
pub(super) fn list_document_assets() {}
|
pub(super) fn list_document_assets() {}
|
||||||
@@ -487,7 +488,7 @@ mod doc {
|
|||||||
path = "/api/documents/{id}/assets",
|
path = "/api/documents/{id}/assets",
|
||||||
params(
|
params(
|
||||||
("id" = Uuid, Path, description = "Document ID"),
|
("id" = Uuid, Path, description = "Document ID"),
|
||||||
DocumentAssetRequestParams
|
AssetRequestQuery
|
||||||
),
|
),
|
||||||
responses((status = 202, description = "Asset generation requested")),
|
responses((status = 202, description = "Asset generation requested")),
|
||||||
tag = "Assets"
|
tag = "Assets"
|
||||||
@@ -501,7 +502,7 @@ mod doc {
|
|||||||
("asset_id" = Uuid, Path, description = "Asset ID"),
|
("asset_id" = Uuid, Path, description = "Asset ID"),
|
||||||
AssetObjectsQuery
|
AssetObjectsQuery
|
||||||
),
|
),
|
||||||
responses((status = 200, description = "Asset detail", body = DocumentAssetDetail)),
|
responses((status = 200, description = "Asset detail", body = DocumentAssetDetailResponse)),
|
||||||
tag = "Assets"
|
tag = "Assets"
|
||||||
)]
|
)]
|
||||||
pub(super) fn get_document_asset() {}
|
pub(super) fn get_document_asset() {}
|
||||||
@@ -541,7 +542,7 @@ mod doc {
|
|||||||
path = "/api/folders/{id}/contents",
|
path = "/api/folders/{id}/contents",
|
||||||
params(
|
params(
|
||||||
("id" = Uuid, Path, description = "Folder ID"),
|
("id" = Uuid, Path, description = "Folder ID"),
|
||||||
FolderContentsParams
|
FolderContentsQuery
|
||||||
),
|
),
|
||||||
responses((status = 200, description = "Folder contents", body = FolderContentsResponse)),
|
responses((status = 200, description = "Folder contents", body = FolderContentsResponse)),
|
||||||
tag = "Folders"
|
tag = "Folders"
|
||||||
@@ -735,525 +736,42 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod schemas {
|
pub mod schemas {
|
||||||
use super::*;
|
|
||||||
|
|
||||||
pub use crate::auth::passkeys::{
|
pub use crate::auth::passkeys::{
|
||||||
AuthenticationChallengeResponse, PasskeyLoginFinishPayload, PasskeyLoginStartPayload,
|
AuthenticationChallengeResponse, PasskeyLoginFinishPayload, PasskeyLoginStartPayload,
|
||||||
PasskeyRegistrationFinishPayload, PasskeySummary, RegistrationChallengeResponse,
|
PasskeyRegistrationFinishPayload, PasskeySummary, RegistrationChallengeResponse,
|
||||||
};
|
};
|
||||||
use webauthn_rs::prelude::RegisterPublicKeyCredential;
|
pub use crate::documents::asset::{
|
||||||
|
DocumentAssetDetailResponse, DocumentAssetObjectResponse, DocumentAssetResponse,
|
||||||
#[derive(Serialize, Deserialize, ToSchema)]
|
DocumentVersionDetailResponse, DocumentVersionResponse,
|
||||||
pub struct LoginRequest {
|
};
|
||||||
pub username: String,
|
pub use crate::documents::correspondents::DocumentCorrespondentResponse;
|
||||||
pub password: String,
|
pub use crate::routes::auth::{
|
||||||
#[schema(nullable)]
|
LoginRequest, LoginResponse, LoginResponseVariants, SignupFinishRequest,
|
||||||
pub preferred_tenant_id: Option<Uuid>,
|
SignupStartRequest, SignupStartResponse, TenantListResponse, TenantSelectionRequest,
|
||||||
}
|
TenantSelectionResponse, TenantSnippet,
|
||||||
|
};
|
||||||
#[derive(Serialize, Deserialize, ToSchema)]
|
pub use crate::routes::correspondents::{
|
||||||
pub struct SignupStartRequest {
|
CorrespondentSummary, CorrespondentUsage, CreateCorrespondentRequest,
|
||||||
pub username: String,
|
UpdateCorrespondentRequest,
|
||||||
}
|
};
|
||||||
|
|
||||||
#[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::document_types::{CreateDocumentTypeRequest, UpdateDocumentTypeRequest};
|
||||||
pub use crate::routes::documents::DocumentTypeResponse;
|
pub use crate::routes::documents::{
|
||||||
|
AssetObjectsQuery, AssetRequestQuery, AssignCorrespondentsRequest, AssignTagsRequest,
|
||||||
#[derive(Serialize, Deserialize, ToSchema)]
|
BulkCorrespondentAction, BulkCorrespondentResponse, BulkCorrespondentsRequest,
|
||||||
pub struct DocumentResponse {
|
BulkMoveRequest, BulkMoveResponse, BulkReanalyzeResponse, BulkReanalyzeSelectionRequest,
|
||||||
pub id: Uuid,
|
BulkTagAction, BulkTagRequest, BulkTagResponse, CorrespondentAssignmentInput,
|
||||||
pub filename: String,
|
DocumentCheckQuery, DocumentCheckResponse, DocumentDetailResponse, DocumentListQuery,
|
||||||
pub title: String,
|
DocumentMetadataUpdate, DocumentResponse, DocumentStatusFilter, DocumentTypeResponse,
|
||||||
pub original_name: String,
|
MoveDocumentRequest, RestoreDocumentRequest, TagResponse, UpdateDocumentRequest,
|
||||||
#[schema(nullable)]
|
UploadDocumentForm,
|
||||||
pub content_type: Option<String>,
|
};
|
||||||
#[schema(nullable)]
|
pub use crate::routes::folders::{
|
||||||
pub folder_id: Option<Uuid>,
|
CreateFolderRequest, EnsureFolderPathRequest, FolderContentsQuery, FolderContentsResponse,
|
||||||
pub created_at: String,
|
FolderInfo, FolderResponse, UpdateFolderRequest,
|
||||||
pub updated_at: String,
|
};
|
||||||
#[schema(nullable)]
|
pub use crate::routes::profile::{
|
||||||
pub deleted_at: Option<String>,
|
CreateWebdavTokenRequest, RevokePasskeyQuery, WebdavTokenCreatedResponse,
|
||||||
#[schema(nullable)]
|
WebdavTokenResponse,
|
||||||
pub issued_at: Option<String>,
|
};
|
||||||
pub metadata: Value,
|
pub use crate::routes::tags::{CreateTagRequest, TagCatalogEntry, UpdateTagRequest};
|
||||||
#[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>,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ use diesel::{pg::PgConnection, prelude::*};
|
|||||||
use rand::{rngs::OsRng, RngCore};
|
use rand::{rngs::OsRng, RngCore};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
|
use utoipa::ToSchema;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -37,15 +38,16 @@ use webauthn_rs::prelude::RegisterPublicKeyCredential;
|
|||||||
|
|
||||||
const REFRESH_COOKIE_NAME: &str = "refresh_token";
|
const REFRESH_COOKIE_NAME: &str = "refresh_token";
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, ToSchema)]
|
||||||
pub struct LoginRequest {
|
pub struct LoginRequest {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
#[schema(nullable)]
|
||||||
pub preferred_tenant_id: Option<Uuid>,
|
pub preferred_tenant_id: Option<Uuid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize, ToSchema)]
|
||||||
pub struct LoginResponse {
|
pub struct LoginResponse {
|
||||||
pub access_token: String,
|
pub access_token: String,
|
||||||
pub token_type: String,
|
pub token_type: String,
|
||||||
@@ -53,46 +55,55 @@ pub struct LoginResponse {
|
|||||||
pub tenant: TenantSnippet,
|
pub tenant: TenantSnippet,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize, ToSchema)]
|
||||||
pub struct TenantSnippet {
|
pub struct TenantSnippet {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Deserialize, ToSchema)]
|
||||||
pub struct TenantSelectionResponse {
|
pub struct TenantSelectionResponse {
|
||||||
pub access_token: String,
|
pub access_token: String,
|
||||||
pub tenants: Vec<TenantSnippet>,
|
pub tenants: Vec<TenantSnippet>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Deserialize, ToSchema)]
|
||||||
pub struct TenantListResponse {
|
pub struct TenantListResponse {
|
||||||
pub tenants: Vec<TenantSnippet>,
|
pub tenants: Vec<TenantSnippet>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, ToSchema)]
|
||||||
pub struct TenantSelectionRequest {
|
pub struct TenantSelectionRequest {
|
||||||
pub tenant_id: Uuid,
|
pub tenant_id: Uuid,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, ToSchema)]
|
||||||
pub struct SignupStartRequest {
|
pub struct SignupStartRequest {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, Deserialize, ToSchema)]
|
||||||
pub struct SignupStartResponse {
|
pub struct SignupStartResponse {
|
||||||
pub signup_token: String,
|
pub signup_token: String,
|
||||||
pub challenge: RegistrationChallengeResponse,
|
pub challenge: RegistrationChallengeResponse,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, ToSchema)]
|
||||||
pub struct SignupFinishRequest {
|
pub struct SignupFinishRequest {
|
||||||
pub signup_token: String,
|
pub signup_token: String,
|
||||||
|
#[schema(value_type = Object)]
|
||||||
pub credential: RegisterPublicKeyCredential,
|
pub credential: RegisterPublicKeyCredential,
|
||||||
|
#[schema(nullable)]
|
||||||
pub nickname: Option<String>,
|
pub nickname: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, ToSchema)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum LoginResponseVariants {
|
||||||
|
Token(LoginResponse),
|
||||||
|
Selection(TenantSelectionResponse),
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn login(_state: State<AppState>, _payload: Json<LoginRequest>) -> AppResult<Response> {
|
pub async fn login(_state: State<AppState>, _payload: Json<LoginRequest>) -> AppResult<Response> {
|
||||||
Err(AppError::bad_request(
|
Err(AppError::bad_request(
|
||||||
"password authentication is no longer supported",
|
"password authentication is no longer supported",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use chrono::Utc;
|
|||||||
use diesel::{dsl::count_star, prelude::*, result::DatabaseErrorKind, PgConnection};
|
use diesel::{dsl::count_star, prelude::*, result::DatabaseErrorKind, PgConnection};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
use utoipa::ToSchema;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -18,31 +19,35 @@ use crate::{
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, ToSchema)]
|
||||||
pub struct CorrespondentUsage {
|
pub struct CorrespondentUsage {
|
||||||
pub total: i64,
|
pub total: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, ToSchema)]
|
||||||
pub struct CorrespondentSummary {
|
pub struct CorrespondentSummary {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
#[schema(value_type = Object)]
|
||||||
pub metadata: Value,
|
pub metadata: Value,
|
||||||
pub created_at: String,
|
pub created_at: String,
|
||||||
pub updated_at: String,
|
pub updated_at: String,
|
||||||
pub usage: CorrespondentUsage,
|
pub usage: CorrespondentUsage,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, ToSchema)]
|
||||||
pub struct CreateCorrespondentRequest {
|
pub struct CreateCorrespondentRequest {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
#[schema(nullable, value_type = Object)]
|
||||||
pub metadata: Option<Value>,
|
pub metadata: Option<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, ToSchema)]
|
||||||
pub struct UpdateCorrespondentRequest {
|
pub struct UpdateCorrespondentRequest {
|
||||||
|
#[schema(nullable)]
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
|
#[schema(nullable, value_type = Object)]
|
||||||
pub metadata: Option<Value>,
|
pub metadata: Option<Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -60,14 +60,17 @@ const PRESIGNED_URL_EXPIRY_SECONDS: u64 = 300;
|
|||||||
#[derive(Deserialize, IntoParams, ToSchema)]
|
#[derive(Deserialize, IntoParams, ToSchema)]
|
||||||
#[into_params(parameter_in = Query)]
|
#[into_params(parameter_in = Query)]
|
||||||
pub struct DocumentListQuery {
|
pub struct DocumentListQuery {
|
||||||
|
#[schema(nullable)]
|
||||||
pub folder_id: Option<Uuid>,
|
pub folder_id: Option<Uuid>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
#[schema(nullable)]
|
||||||
pub include_descendants: Option<bool>,
|
pub include_descendants: Option<bool>,
|
||||||
pub query: Option<String>,
|
pub query: Option<String>,
|
||||||
pub tags: Option<String>,
|
pub tags: Option<String>,
|
||||||
pub correspondents: Option<String>,
|
pub correspondents: Option<String>,
|
||||||
pub document_types: Option<String>,
|
pub document_types: Option<String>,
|
||||||
#[serde(default = "default_document_status_filter")]
|
#[serde(default = "default_document_status_filter")]
|
||||||
|
#[schema(default = "active")]
|
||||||
pub status: DocumentStatusFilter,
|
pub status: DocumentStatusFilter,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,6 +90,7 @@ pub enum DocumentStatusFilter {
|
|||||||
#[into_params(parameter_in = Query)]
|
#[into_params(parameter_in = Query)]
|
||||||
pub struct AssetRequestQuery {
|
pub struct AssetRequestQuery {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
#[schema(default = false)]
|
||||||
pub force: bool,
|
pub force: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,16 +104,22 @@ pub struct DocumentCheckQuery {
|
|||||||
pub struct DocumentCheckResponse {
|
pub struct DocumentCheckResponse {
|
||||||
pub exists: bool,
|
pub exists: bool,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[schema(nullable)]
|
||||||
pub document_id: Option<Uuid>,
|
pub document_id: Option<Uuid>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[schema(nullable)]
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[schema(nullable)]
|
||||||
pub filename: Option<String>,
|
pub filename: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[schema(nullable)]
|
||||||
pub version_id: Option<Uuid>,
|
pub version_id: Option<Uuid>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[schema(nullable)]
|
||||||
pub version_number: Option<i32>,
|
pub version_number: Option<i32>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[schema(nullable)]
|
||||||
pub created_at: Option<String>,
|
pub created_at: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,6 +127,7 @@ pub struct DocumentCheckResponse {
|
|||||||
pub struct TagResponse {
|
pub struct TagResponse {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub label: String,
|
pub label: String,
|
||||||
|
#[schema(nullable)]
|
||||||
pub color: Option<String>,
|
pub color: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,21 +162,29 @@ pub struct DocumentResponse {
|
|||||||
pub filename: String,
|
pub filename: String,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub original_name: String,
|
pub original_name: String,
|
||||||
|
#[schema(nullable)]
|
||||||
pub content_type: Option<String>,
|
pub content_type: Option<String>,
|
||||||
|
#[schema(nullable)]
|
||||||
pub folder_id: Option<Uuid>,
|
pub folder_id: Option<Uuid>,
|
||||||
pub created_at: String,
|
pub created_at: String,
|
||||||
pub updated_at: String,
|
pub updated_at: String,
|
||||||
|
#[schema(nullable)]
|
||||||
pub deleted_at: Option<String>,
|
pub deleted_at: Option<String>,
|
||||||
|
#[schema(nullable)]
|
||||||
pub issued_at: Option<String>,
|
pub issued_at: Option<String>,
|
||||||
|
#[schema(value_type = Object)]
|
||||||
pub metadata: Value,
|
pub metadata: Value,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[schema(nullable)]
|
||||||
pub document_type_id: Option<Uuid>,
|
pub document_type_id: Option<Uuid>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[schema(nullable)]
|
||||||
pub document_type: Option<DocumentTypeResponse>,
|
pub document_type: Option<DocumentTypeResponse>,
|
||||||
pub tags: Vec<TagResponse>,
|
pub tags: Vec<TagResponse>,
|
||||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
pub correspondents: Vec<DocumentCorrespondentResponse>,
|
pub correspondents: Vec<DocumentCorrespondentResponse>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[schema(nullable)]
|
||||||
pub current_version: Option<DocumentVersionDetailResponse>,
|
pub current_version: Option<DocumentVersionDetailResponse>,
|
||||||
}
|
}
|
||||||
#[derive(Serialize, ToSchema)]
|
#[derive(Serialize, ToSchema)]
|
||||||
@@ -181,11 +200,13 @@ pub struct BulkReanalyzeResponse {
|
|||||||
#[derive(Deserialize, ToSchema)]
|
#[derive(Deserialize, ToSchema)]
|
||||||
pub struct BulkMoveRequest {
|
pub struct BulkMoveRequest {
|
||||||
pub document_ids: Vec<Uuid>,
|
pub document_ids: Vec<Uuid>,
|
||||||
|
#[schema(nullable)]
|
||||||
pub folder_id: Option<Uuid>,
|
pub folder_id: Option<Uuid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, ToSchema)]
|
#[derive(Deserialize, ToSchema)]
|
||||||
pub struct DocumentMetadataUpdate {
|
pub struct DocumentMetadataUpdate {
|
||||||
|
#[schema(value_type = Object)]
|
||||||
pub value: Value,
|
pub value: Value,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[schema(default = false)]
|
#[schema(default = false)]
|
||||||
@@ -200,7 +221,7 @@ pub struct UpdateDocumentRequest {
|
|||||||
#[schema(nullable, value_type = Option<String>)]
|
#[schema(nullable, value_type = Option<String>)]
|
||||||
pub issued_at: Option<Value>,
|
pub issued_at: Option<Value>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[schema(nullable)]
|
#[schema(nullable, value_type = Object)]
|
||||||
pub metadata: Option<DocumentMetadataUpdate>,
|
pub metadata: Option<DocumentMetadataUpdate>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
#[schema(nullable, value_type = Option<Uuid>)]
|
#[schema(nullable, value_type = Option<Uuid>)]
|
||||||
@@ -247,6 +268,7 @@ pub struct CorrespondentAssignmentInput {
|
|||||||
pub struct AssignCorrespondentsRequest {
|
pub struct AssignCorrespondentsRequest {
|
||||||
pub assignments: Vec<CorrespondentAssignmentInput>,
|
pub assignments: Vec<CorrespondentAssignmentInput>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
#[schema(default = false)]
|
||||||
pub replace: bool,
|
pub replace: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,6 +306,7 @@ pub struct BulkCorrespondentsRequest {
|
|||||||
pub struct BulkReanalyzeSelectionRequest {
|
pub struct BulkReanalyzeSelectionRequest {
|
||||||
pub document_ids: Vec<Uuid>,
|
pub document_ids: Vec<Uuid>,
|
||||||
#[serde(default = "default_true")]
|
#[serde(default = "default_true")]
|
||||||
|
#[schema(default = true)]
|
||||||
pub force: bool,
|
pub force: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,7 +339,7 @@ pub struct UploadDocumentForm {
|
|||||||
pub file: String,
|
pub file: String,
|
||||||
#[schema(nullable)]
|
#[schema(nullable)]
|
||||||
pub folder_id: Option<Uuid>,
|
pub folder_id: Option<Uuid>,
|
||||||
#[schema(nullable)]
|
#[schema(nullable, value_type = Object)]
|
||||||
pub metadata: Option<Value>,
|
pub metadata: Option<Value>,
|
||||||
#[schema(nullable)]
|
#[schema(nullable)]
|
||||||
pub title: Option<String>,
|
pub title: Option<String>,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use axum::{
|
|||||||
};
|
};
|
||||||
use diesel::{dsl::exists, prelude::*, PgConnection};
|
use diesel::{dsl::exists, prelude::*, PgConnection};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
use utoipa::{IntoParams, ToSchema};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::models::{Document, DocumentType, Folder, NewFolder};
|
use crate::models::{Document, DocumentType, Folder, NewFolder};
|
||||||
@@ -22,38 +22,40 @@ use crate::documents::{
|
|||||||
asset::load_primary_assets, correspondents::load_correspondents_for_documents,
|
asset::load_primary_assets, correspondents::load_correspondents_for_documents,
|
||||||
tags::load_tags_for_documents,
|
tags::load_tags_for_documents,
|
||||||
};
|
};
|
||||||
use crate::utils::{
|
use crate::utils::time::to_iso;
|
||||||
json::{classify_nullable, NullableValue},
|
|
||||||
time::to_iso,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, ToSchema)]
|
||||||
pub struct CreateFolderRequest {
|
pub struct CreateFolderRequest {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
#[schema(nullable)]
|
||||||
pub parent_id: Option<Uuid>,
|
pub parent_id: Option<Uuid>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, ToSchema)]
|
||||||
pub struct EnsureFolderPathRequest {
|
pub struct EnsureFolderPathRequest {
|
||||||
|
#[schema(nullable)]
|
||||||
pub parent_id: Option<Uuid>,
|
pub parent_id: Option<Uuid>,
|
||||||
pub segments: Vec<String>,
|
pub segments: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, ToSchema)]
|
||||||
pub struct FolderResponse {
|
pub struct FolderResponse {
|
||||||
pub folder: FolderInfo,
|
pub folder: FolderInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, ToSchema)]
|
||||||
pub struct FolderContentsResponse {
|
pub struct FolderContentsResponse {
|
||||||
|
#[schema(nullable)]
|
||||||
pub folder: Option<FolderInfo>,
|
pub folder: Option<FolderInfo>,
|
||||||
pub subfolders: Vec<FolderInfo>,
|
pub subfolders: Vec<FolderInfo>,
|
||||||
pub documents: Vec<DocumentResponse>,
|
pub documents: Vec<DocumentResponse>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, IntoParams, ToSchema)]
|
||||||
|
#[into_params(parameter_in = Query)]
|
||||||
pub struct FolderContentsQuery {
|
pub struct FolderContentsQuery {
|
||||||
#[serde(default = "default_include_documents")]
|
#[serde(default = "default_include_documents")]
|
||||||
|
#[schema(default = true)]
|
||||||
pub include_documents: bool,
|
pub include_documents: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,15 +63,27 @@ const fn default_include_documents() -> bool {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, ToSchema)]
|
||||||
pub struct FolderInfo {
|
pub struct FolderInfo {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
#[schema(nullable)]
|
||||||
pub parent_id: Option<Uuid>,
|
pub parent_id: Option<Uuid>,
|
||||||
pub created_at: String,
|
pub created_at: String,
|
||||||
pub updated_at: String,
|
pub updated_at: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Deserialize, ToSchema)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct UpdateFolderRequest {
|
||||||
|
#[serde(default)]
|
||||||
|
#[schema(nullable, value_type = Option<Uuid>)]
|
||||||
|
pub parent_id: Option<Option<Uuid>>,
|
||||||
|
#[serde(default)]
|
||||||
|
#[schema(nullable)]
|
||||||
|
pub name: Option<Option<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_folder(
|
pub async fn get_folder(
|
||||||
Path(folder_id): Path<Uuid>,
|
Path(folder_id): Path<Uuid>,
|
||||||
TenantScopedConn {
|
TenantScopedConn {
|
||||||
@@ -435,15 +449,8 @@ pub async fn update_folder(
|
|||||||
tenant_id,
|
tenant_id,
|
||||||
..
|
..
|
||||||
}: TenantScopedConn,
|
}: TenantScopedConn,
|
||||||
Json(body): Json<Value>,
|
Json(payload): Json<UpdateFolderRequest>,
|
||||||
) -> AppResult<StatusCode> {
|
) -> AppResult<StatusCode> {
|
||||||
if !body.is_object() {
|
|
||||||
return Err(AppError::bad_request("request body must be a JSON object"));
|
|
||||||
}
|
|
||||||
|
|
||||||
let parent_class = classify_nullable(body.get("parent_id")).map_err(AppError::bad_request)?;
|
|
||||||
let name_class = classify_nullable(body.get("name")).map_err(AppError::bad_request)?;
|
|
||||||
|
|
||||||
conn.transaction::<(), AppError, _>(|conn| {
|
conn.transaction::<(), AppError, _>(|conn| {
|
||||||
let folder: Folder = folders::table
|
let folder: Folder = folders::table
|
||||||
.find(folder_id)
|
.find(folder_id)
|
||||||
@@ -452,21 +459,15 @@ pub async fn update_folder(
|
|||||||
|
|
||||||
let mut next_parent = folder.parent_id;
|
let mut next_parent = folder.parent_id;
|
||||||
let mut parent_changed = false;
|
let mut parent_changed = false;
|
||||||
match parent_class {
|
match payload.parent_id {
|
||||||
NullableValue::Omitted => {}
|
None => {}
|
||||||
NullableValue::Null => {
|
Some(None) => {
|
||||||
if folder.parent_id.is_some() {
|
if folder.parent_id.is_some() {
|
||||||
parent_changed = true;
|
parent_changed = true;
|
||||||
}
|
}
|
||||||
next_parent = None;
|
next_parent = None;
|
||||||
}
|
}
|
||||||
NullableValue::String(value) => {
|
Some(Some(parent_id)) => {
|
||||||
let trimmed = value.trim();
|
|
||||||
if trimmed.is_empty() {
|
|
||||||
return Err(AppError::bad_request("parent_id must not be empty"));
|
|
||||||
}
|
|
||||||
let parent_id = Uuid::parse_str(trimmed)
|
|
||||||
.map_err(|_| AppError::bad_request("parent_id must be a valid UUID or null"))?;
|
|
||||||
if parent_id == folder_id {
|
if parent_id == folder_id {
|
||||||
return Err(AppError::bad_request("folder cannot be its own parent"));
|
return Err(AppError::bad_request("folder cannot be its own parent"));
|
||||||
}
|
}
|
||||||
@@ -492,12 +493,12 @@ pub async fn update_folder(
|
|||||||
|
|
||||||
let mut new_name = folder.name.clone();
|
let mut new_name = folder.name.clone();
|
||||||
let mut name_changed = false;
|
let mut name_changed = false;
|
||||||
match name_class {
|
match payload.name {
|
||||||
NullableValue::Omitted => {}
|
None => {}
|
||||||
NullableValue::Null => {
|
Some(None) => {
|
||||||
return Err(AppError::bad_request("name cannot be null"));
|
return Err(AppError::bad_request("name cannot be null"));
|
||||||
}
|
}
|
||||||
NullableValue::String(value) => {
|
Some(Some(value)) => {
|
||||||
let trimmed = value.trim();
|
let trimmed = value.trim();
|
||||||
if trimmed.is_empty() {
|
if trimmed.is_empty() {
|
||||||
return Err(AppError::bad_request("name must not be empty"));
|
return Err(AppError::bad_request("name must not be empty"));
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use axum::{
|
|||||||
};
|
};
|
||||||
use chrono::{DateTime, NaiveDateTime};
|
use chrono::{DateTime, NaiveDateTime};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use utoipa::ToSchema;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::auth::{
|
use crate::auth::{
|
||||||
@@ -20,32 +21,39 @@ use crate::models::WebdavToken;
|
|||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
use crate::utils::{db::no_content, time::to_iso};
|
use crate::utils::{db::no_content, time::to_iso};
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize, ToSchema)]
|
||||||
pub struct WebdavTokenResponse {
|
pub struct WebdavTokenResponse {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub tenant_id: Uuid,
|
pub tenant_id: Uuid,
|
||||||
|
#[schema(nullable)]
|
||||||
pub label: Option<String>,
|
pub label: Option<String>,
|
||||||
pub created_at: String,
|
pub created_at: String,
|
||||||
|
#[schema(nullable)]
|
||||||
pub last_used_at: Option<String>,
|
pub last_used_at: Option<String>,
|
||||||
|
#[schema(nullable)]
|
||||||
pub expires_at: Option<String>,
|
pub expires_at: Option<String>,
|
||||||
|
#[schema(nullable)]
|
||||||
pub revoked_at: Option<String>,
|
pub revoked_at: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize, ToSchema)]
|
||||||
pub struct WebdavTokenCreatedResponse {
|
pub struct WebdavTokenCreatedResponse {
|
||||||
pub token: String,
|
pub token: String,
|
||||||
pub token_info: WebdavTokenResponse,
|
pub token_info: WebdavTokenResponse,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, ToSchema)]
|
||||||
pub struct CreateWebdavTokenRequest {
|
pub struct CreateWebdavTokenRequest {
|
||||||
|
#[schema(nullable)]
|
||||||
pub label: Option<String>,
|
pub label: Option<String>,
|
||||||
|
#[schema(nullable)]
|
||||||
pub expires_at: Option<String>,
|
pub expires_at: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, ToSchema)]
|
||||||
pub struct RevokePasskeyQuery {
|
pub struct RevokePasskeyQuery {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
#[schema(nullable)]
|
||||||
pub reason: Option<String>,
|
pub reason: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+27
-18
@@ -1,9 +1,8 @@
|
|||||||
use crate::utils::json::{classify_nullable, NullableValue};
|
|
||||||
use axum::{extract::Path, http::StatusCode, Json};
|
use axum::{extract::Path, http::StatusCode, Json};
|
||||||
use diesel::{dsl::count_star, prelude::*};
|
use diesel::{dsl::count_star, prelude::*};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::Value;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use utoipa::ToSchema;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::auth::TenantScopedConn;
|
use crate::auth::TenantScopedConn;
|
||||||
@@ -12,9 +11,10 @@ use crate::models::{NewTag, Tag};
|
|||||||
use crate::schema::{document_tags, tags};
|
use crate::schema::{document_tags, tags};
|
||||||
use crate::utils::db::{no_content, EnsureEntity, IntoJsonResponse};
|
use crate::utils::db::{no_content, EnsureEntity, IntoJsonResponse};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize, ToSchema)]
|
||||||
pub struct CreateTagRequest {
|
pub struct CreateTagRequest {
|
||||||
pub label: String,
|
pub label: String,
|
||||||
|
#[schema(nullable)]
|
||||||
pub color: Option<String>,
|
pub color: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,14 +25,26 @@ struct UpdateTagChangeset<'a> {
|
|||||||
color: Option<Option<&'a str>>,
|
color: Option<Option<&'a str>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize, ToSchema)]
|
||||||
pub struct TagCatalogEntry {
|
pub struct TagCatalogEntry {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub label: String,
|
pub label: String,
|
||||||
|
#[schema(nullable)]
|
||||||
pub color: Option<String>,
|
pub color: Option<String>,
|
||||||
pub usage_count: i64,
|
pub usage_count: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Deserialize, ToSchema)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct UpdateTagRequest {
|
||||||
|
#[serde(default)]
|
||||||
|
#[schema(nullable, value_type = Option<String>)]
|
||||||
|
pub label: Option<Option<String>>,
|
||||||
|
#[serde(default)]
|
||||||
|
#[schema(nullable, value_type = Option<String>)]
|
||||||
|
pub color: Option<Option<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn list_tags(
|
pub async fn list_tags(
|
||||||
TenantScopedConn {
|
TenantScopedConn {
|
||||||
mut conn,
|
mut conn,
|
||||||
@@ -121,19 +133,16 @@ pub async fn update_tag(
|
|||||||
tenant_id,
|
tenant_id,
|
||||||
..
|
..
|
||||||
}: TenantScopedConn,
|
}: TenantScopedConn,
|
||||||
Json(body): Json<Value>,
|
Json(payload): Json<UpdateTagRequest>,
|
||||||
) -> AppResult<Json<TagCatalogEntry>> {
|
) -> AppResult<Json<TagCatalogEntry>> {
|
||||||
let existing: Tag = tags::table
|
let existing: Tag = tags::table
|
||||||
.find(tag_id)
|
.find(tag_id)
|
||||||
.filter(tags::tenant_id.eq(tenant_id))
|
.filter(tags::tenant_id.eq(tenant_id))
|
||||||
.first(&mut conn)
|
.first(&mut conn)
|
||||||
.one()?;
|
.one()?;
|
||||||
let label_class = classify_nullable(body.get("label")).map_err(AppError::bad_request)?;
|
let UpdateTagRequest { label, color } = payload;
|
||||||
let color_class = classify_nullable(body.get("color")).map_err(AppError::bad_request)?;
|
|
||||||
|
|
||||||
if matches!(label_class, NullableValue::Omitted)
|
if label.is_none() && color.is_none() {
|
||||||
&& matches!(color_class, NullableValue::Omitted)
|
|
||||||
{
|
|
||||||
let usage_count: i64 = document_tags::table
|
let usage_count: i64 = document_tags::table
|
||||||
.filter(document_tags::tag_id.eq(tag_id))
|
.filter(document_tags::tag_id.eq(tag_id))
|
||||||
.select(count_star())
|
.select(count_star())
|
||||||
@@ -149,12 +158,12 @@ pub async fn update_tag(
|
|||||||
|
|
||||||
let mut new_label: Option<String> = None;
|
let mut new_label: Option<String> = None;
|
||||||
let mut label_changed = false;
|
let mut label_changed = false;
|
||||||
match label_class {
|
match label {
|
||||||
NullableValue::Omitted => {}
|
None => {}
|
||||||
NullableValue::Null => {
|
Some(None) => {
|
||||||
return Err(AppError::bad_request("label cannot be null"));
|
return Err(AppError::bad_request("label cannot be null"));
|
||||||
}
|
}
|
||||||
NullableValue::String(value) => {
|
Some(Some(value)) => {
|
||||||
let trimmed = value.trim();
|
let trimmed = value.trim();
|
||||||
if trimmed.is_empty() {
|
if trimmed.is_empty() {
|
||||||
return Err(AppError::bad_request("label must not be empty"));
|
return Err(AppError::bad_request("label must not be empty"));
|
||||||
@@ -177,13 +186,13 @@ pub async fn update_tag(
|
|||||||
|
|
||||||
let mut color_change: Option<Option<String>> = None;
|
let mut color_change: Option<Option<String>> = None;
|
||||||
let mut color_changed = false;
|
let mut color_changed = false;
|
||||||
match color_class {
|
match color {
|
||||||
NullableValue::Omitted => {}
|
None => {}
|
||||||
NullableValue::Null => {
|
Some(None) => {
|
||||||
color_change = Some(None);
|
color_change = Some(None);
|
||||||
color_changed = true;
|
color_changed = true;
|
||||||
}
|
}
|
||||||
NullableValue::String(value) => {
|
Some(Some(value)) => {
|
||||||
let trimmed = value.trim();
|
let trimmed = value.trim();
|
||||||
if trimmed.is_empty() {
|
if trimmed.is_empty() {
|
||||||
return Err(AppError::bad_request("color must not be empty"));
|
return Err(AppError::bad_request("color must not be empty"));
|
||||||
|
|||||||
Reference in New Issue
Block a user