Merge pull request #245 from iltumio/feat/utoipa-openapi
This commit is contained in:
@@ -78,5 +78,8 @@ storage/
|
|||||||
*.swo
|
*.swo
|
||||||
nohup.out
|
nohup.out
|
||||||
|
|
||||||
|
# Generated files (OpenAPI spec, etc.)
|
||||||
|
resources/gen/
|
||||||
|
|
||||||
# Helm chart dependencies
|
# Helm chart dependencies
|
||||||
charts/*/charts/*
|
charts/*/charts/*
|
||||||
@@ -8,15 +8,19 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
cargo build # Dev build
|
cargo build # Dev build
|
||||||
cargo build --release # Optimized release build
|
cargo build --release # Optimized release build
|
||||||
cargo run # Run server (port 8086)
|
cargo run # Run server (port 8086)
|
||||||
cargo test --workspace # Run all tests (~112)
|
cargo test --workspace # Run all tests (~208)
|
||||||
cargo test <test_name> # Run a single test by name
|
cargo test <test_name> # Run a single test by name
|
||||||
cargo test --features test_utils # Run tests that use mockall mocks
|
cargo test --features test_utils # Run tests that use mockall mocks
|
||||||
cargo clippy -- -D warnings # Lint (zero warnings policy)
|
cargo clippy -- -D warnings # Lint (zero warnings policy)
|
||||||
cargo fmt --all --check # Format check
|
cargo fmt --all --check # Format check
|
||||||
cargo fmt --all # Auto-format
|
cargo fmt --all # Auto-format
|
||||||
RUST_LOG=debug cargo run # Run with debug logging
|
RUST_LOG=debug cargo run # Run with debug logging
|
||||||
|
cargo run --bin generate-openapi # Regenerate resources/gen/openapi.json
|
||||||
```
|
```
|
||||||
|
|
||||||
|
A `justfile` is available for common tasks (`just --list` to see all). Key recipes: `just check` (fmt + clippy), `just test`, `just openapi`.
|
||||||
|
|
||||||
|
|
||||||
Requires **Rust 1.93+** (edition 2024) and **PostgreSQL 13+** (with `pg_trgm` and `ltree` extensions).
|
Requires **Rust 1.93+** (edition 2024) and **PostgreSQL 13+** (with `pg_trgm` and `ltree` extensions).
|
||||||
|
|
||||||
Database setup: `docker compose up -d postgres` — schema is applied automatically via sqlx migrations on app startup. Migration files live in `migrations/`. For local dev, set `DATABASE_URL` in `.env` (see `example.env`).
|
Database setup: `docker compose up -d postgres` — schema is applied automatically via sqlx migrations on app startup. Migration files live in `migrations/`. For local dev, set `DATABASE_URL` in `.env` (see `example.env`).
|
||||||
|
|||||||
Generated
+39
-1
@@ -2527,7 +2527,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxicloud"
|
name = "oxicloud"
|
||||||
version = "0.5.2"
|
version = "0.5.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argon2",
|
"argon2",
|
||||||
"async-compression",
|
"async-compression",
|
||||||
@@ -2582,6 +2582,7 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
|
"utoipa",
|
||||||
"uuid",
|
"uuid",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -3202,6 +3203,18 @@ dependencies = [
|
|||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.12.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.13"
|
version = "0.4.13"
|
||||||
@@ -4351,6 +4364,31 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utoipa"
|
||||||
|
version = "5.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2fcc29c80c21c31608227e0912b2d7fddba57ad76b606890627ba8ee7964e993"
|
||||||
|
dependencies = [
|
||||||
|
"indexmap",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"utoipa-gen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utoipa-gen"
|
||||||
|
version = "5.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6d79d08d92ab8af4c5e8a6da20c47ae3f61a0f1dabc1997cdf2d082b757ca08b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"regex",
|
||||||
|
"syn 2.0.117",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.21.0"
|
version = "1.21.0"
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ async_zip = { version = "0.0.18", features = ["tokio", "deflate"] }
|
|||||||
dashmap = "6"
|
dashmap = "6"
|
||||||
socket2 = { version = "0.6.2", features = ["all"] }
|
socket2 = { version = "0.6.2", features = ["all"] }
|
||||||
urlencoding = "2.1.3"
|
urlencoding = "2.1.3"
|
||||||
|
utoipa = { version = "5", features = ["axum_extras", "uuid", "chrono"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
@@ -63,6 +64,10 @@ integration_tests = []
|
|||||||
[lints.rust]
|
[lints.rust]
|
||||||
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(integration_tests)'] }
|
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(integration_tests)'] }
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "generate-openapi"
|
||||||
|
path = "src/bin/generate-openapi.rs"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
oxc_allocator = "0.116"
|
oxc_allocator = "0.116"
|
||||||
oxc_parser = "0.116"
|
oxc_parser = "0.116"
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
set dotenv-load
|
||||||
|
|
||||||
|
default:
|
||||||
|
@just --list
|
||||||
|
|
||||||
|
build:
|
||||||
|
cargo build
|
||||||
|
|
||||||
|
release:
|
||||||
|
cargo build --release
|
||||||
|
|
||||||
|
run:
|
||||||
|
cargo run
|
||||||
|
|
||||||
|
run-debug:
|
||||||
|
RUST_LOG=debug cargo run
|
||||||
|
|
||||||
|
test:
|
||||||
|
cargo test --workspace
|
||||||
|
|
||||||
|
test-mocks:
|
||||||
|
cargo test --features test_utils
|
||||||
|
|
||||||
|
test-one name:
|
||||||
|
cargo test {{name}}
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
cargo fmt --all
|
||||||
|
|
||||||
|
fmt-check:
|
||||||
|
cargo fmt --all --check
|
||||||
|
|
||||||
|
lint:
|
||||||
|
cargo clippy -- -D warnings
|
||||||
|
|
||||||
|
check:
|
||||||
|
cargo fmt --all
|
||||||
|
cargo clippy -- -D warnings
|
||||||
|
|
||||||
|
openapi:
|
||||||
|
cargo run --bin generate-openapi
|
||||||
|
|
||||||
|
db:
|
||||||
|
docker compose up -d postgres
|
||||||
|
|
||||||
|
db-down:
|
||||||
|
docker compose down
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use super::display_helpers::{
|
use super::display_helpers::{
|
||||||
category_for, format_file_size, icon_class_for, icon_special_class_for,
|
category_for, format_file_size, icon_class_for, icon_special_class_for,
|
||||||
@@ -7,7 +8,7 @@ use super::display_helpers::{
|
|||||||
|
|
||||||
/// DTO for favorites item, enriched with item metadata via SQL JOIN
|
/// DTO for favorites item, enriched with item metadata via SQL JOIN
|
||||||
/// so the frontend does not need N+1 requests to resolve names/sizes.
|
/// so the frontend does not need N+1 requests to resolve names/sizes.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct FavoriteItemDto {
|
pub struct FavoriteItemDto {
|
||||||
/// Unique identifier for the favorite entry
|
/// Unique identifier for the favorite entry
|
||||||
pub id: String,
|
pub id: String,
|
||||||
@@ -84,7 +85,7 @@ impl FavoriteItemDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Result DTO for batch add-to-favorites.
|
/// Result DTO for batch add-to-favorites.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct BatchFavoritesResult {
|
pub struct BatchFavoritesResult {
|
||||||
/// Statistics about the batch operation
|
/// Statistics about the batch operation
|
||||||
pub stats: BatchFavoritesStats,
|
pub stats: BatchFavoritesStats,
|
||||||
@@ -93,7 +94,7 @@ pub struct BatchFavoritesResult {
|
|||||||
pub favorites: Vec<FavoriteItemDto>,
|
pub favorites: Vec<FavoriteItemDto>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct BatchFavoritesStats {
|
pub struct BatchFavoritesStats {
|
||||||
/// How many items were requested
|
/// How many items were requested
|
||||||
pub requested: usize,
|
pub requested: usize,
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use crate::domain::entities::file::File;
|
use crate::domain::entities::file::File;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use super::display_helpers::{
|
use super::display_helpers::{
|
||||||
category_for, format_file_size, icon_class_for, icon_special_class_for,
|
category_for, format_file_size, icon_class_for, icon_special_class_for,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// DTO for file responses
|
/// DTO for file responses
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct FileDto {
|
pub struct FileDto {
|
||||||
/// File ID
|
/// File ID
|
||||||
pub id: String,
|
pub id: String,
|
||||||
@@ -24,6 +25,7 @@ pub struct FileDto {
|
|||||||
|
|
||||||
/// MIME type — `Arc<str>` because MIME values repeat across files
|
/// MIME type — `Arc<str>` because MIME values repeat across files
|
||||||
/// and DTOs are cloned on every request (clone is O(1) atomic increment).
|
/// and DTOs are cloned on every request (clone is O(1) atomic increment).
|
||||||
|
#[schema(value_type = String)]
|
||||||
pub mime_type: Arc<str>,
|
pub mime_type: Arc<str>,
|
||||||
|
|
||||||
/// Parent folder ID
|
/// Parent folder ID
|
||||||
@@ -37,12 +39,15 @@ pub struct FileDto {
|
|||||||
|
|
||||||
// ── Pre-computed display fields (Arc<str>: values come from static tables) ──
|
// ── Pre-computed display fields (Arc<str>: values come from static tables) ──
|
||||||
/// FontAwesome icon CSS class (e.g. "fas fa-file-image")
|
/// FontAwesome icon CSS class (e.g. "fas fa-file-image")
|
||||||
|
#[schema(value_type = String)]
|
||||||
pub icon_class: Arc<str>,
|
pub icon_class: Arc<str>,
|
||||||
|
|
||||||
/// Extra CSS class for icon styling (e.g. "image-icon", "" when default)
|
/// Extra CSS class for icon styling (e.g. "image-icon", "" when default)
|
||||||
|
#[schema(value_type = String)]
|
||||||
pub icon_special_class: Arc<str>,
|
pub icon_special_class: Arc<str>,
|
||||||
|
|
||||||
/// Human-readable file category (e.g. "Image", "Document")
|
/// Human-readable file category (e.g. "Image", "Document")
|
||||||
|
#[schema(value_type = String)]
|
||||||
pub category: Arc<str>,
|
pub category: Arc<str>,
|
||||||
|
|
||||||
/// Human-readable formatted size (e.g. "3.27 MB")
|
/// Human-readable formatted size (e.g. "3.27 MB")
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use crate::domain::entities::folder::Folder;
|
use crate::domain::entities::folder::Folder;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
/// DTO for folder creation requests
|
/// DTO for folder creation requests
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, ToSchema)]
|
||||||
pub struct CreateFolderDto {
|
pub struct CreateFolderDto {
|
||||||
/// Name of the folder to create
|
/// Name of the folder to create
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@@ -14,21 +15,21 @@ pub struct CreateFolderDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// DTO for folder rename requests
|
/// DTO for folder rename requests
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, ToSchema)]
|
||||||
pub struct RenameFolderDto {
|
pub struct RenameFolderDto {
|
||||||
/// New name for the folder
|
/// New name for the folder
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// DTO for folder move requests
|
/// DTO for folder move requests
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, ToSchema)]
|
||||||
pub struct MoveFolderDto {
|
pub struct MoveFolderDto {
|
||||||
/// New parent folder ID (None for root level)
|
/// New parent folder ID (None for root level)
|
||||||
pub parent_id: Option<String>,
|
pub parent_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// DTO for folder responses
|
/// DTO for folder responses
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct FolderDto {
|
pub struct FolderDto {
|
||||||
/// Folder ID
|
/// Folder ID
|
||||||
pub id: String,
|
pub id: String,
|
||||||
@@ -57,12 +58,15 @@ pub struct FolderDto {
|
|||||||
|
|
||||||
// ── Pre-computed display fields (Arc<str>: always identical values) ──
|
// ── Pre-computed display fields (Arc<str>: always identical values) ──
|
||||||
/// FontAwesome icon CSS class (always "fas fa-folder")
|
/// FontAwesome icon CSS class (always "fas fa-folder")
|
||||||
|
#[schema(value_type = String)]
|
||||||
pub icon_class: Arc<str>,
|
pub icon_class: Arc<str>,
|
||||||
|
|
||||||
/// Extra CSS class for icon styling (always "folder-icon")
|
/// Extra CSS class for icon styling (always "folder-icon")
|
||||||
|
#[schema(value_type = String)]
|
||||||
pub icon_special_class: Arc<str>,
|
pub icon_special_class: Arc<str>,
|
||||||
|
|
||||||
/// Human-readable category (always "Folder")
|
/// Human-readable category (always "Folder")
|
||||||
|
#[schema(value_type = String)]
|
||||||
pub category: Arc<str>,
|
pub category: Arc<str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use super::file_dto::FileDto;
|
use super::file_dto::FileDto;
|
||||||
use super::folder_dto::FolderDto;
|
use super::folder_dto::FolderDto;
|
||||||
|
|
||||||
/// Combined DTO that returns both sub-folders and files for a given folder
|
/// Combined DTO that returns both sub-folders and files for a given folder
|
||||||
/// in a single response, eliminating the double-fetch on every navigation.
|
/// in a single response, eliminating the double-fetch on every navigation.
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize, ToSchema)]
|
||||||
pub struct FolderListingDto {
|
pub struct FolderListingDto {
|
||||||
/// Sub-folders inside the requested folder
|
/// Sub-folders inside the requested folder
|
||||||
pub folders: Vec<FolderDto>,
|
pub folders: Vec<FolderDto>,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use utoipa::{IntoParams, ToSchema};
|
||||||
|
|
||||||
/// A DTO to represent pagination information
|
/// A DTO to represent pagination information
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct PaginationDto {
|
pub struct PaginationDto {
|
||||||
/// Current page (starts at 0)
|
/// Current page (starts at 0)
|
||||||
pub page: usize,
|
pub page: usize,
|
||||||
@@ -18,7 +19,7 @@ pub struct PaginationDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A DTO to represent a pagination request
|
/// A DTO to represent a pagination request
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, IntoParams)]
|
||||||
pub struct PaginationRequestDto {
|
pub struct PaginationRequestDto {
|
||||||
/// Requested page (starts at 0)
|
/// Requested page (starts at 0)
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
@@ -29,7 +30,7 @@ pub struct PaginationRequestDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A DTO to represent a paginated response
|
/// A DTO to represent a paginated response
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct PaginatedResponseDto<T> {
|
pub struct PaginatedResponseDto<T> {
|
||||||
/// Data on the current page
|
/// Data on the current page
|
||||||
pub items: Vec<T>,
|
pub items: Vec<T>,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use super::display_helpers::{
|
use super::display_helpers::{
|
||||||
category_for, format_file_size, icon_class_for, icon_special_class_for,
|
category_for, format_file_size, icon_class_for, icon_special_class_for,
|
||||||
@@ -7,7 +8,7 @@ use super::display_helpers::{
|
|||||||
|
|
||||||
/// DTO for recent items, enriched with item metadata via SQL JOIN
|
/// DTO for recent items, enriched with item metadata via SQL JOIN
|
||||||
/// so the frontend does not need N+1 requests to resolve names/sizes.
|
/// so the frontend does not need N+1 requests to resolve names/sizes.
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct RecentItemDto {
|
pub struct RecentItemDto {
|
||||||
/// Unique identifier for the recent item
|
/// Unique identifier for the recent item
|
||||||
pub id: String,
|
pub id: String,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data Transfer Object for file search criteria.
|
* Data Transfer Object for file search criteria.
|
||||||
@@ -7,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
* to filter files and folders in the system. It supports various filter types
|
* to filter files and folders in the system. It supports various filter types
|
||||||
* including name matching, file types, date ranges, and size constraints.
|
* including name matching, file types, date ranges, and size constraints.
|
||||||
*/
|
*/
|
||||||
#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Hash, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct SearchCriteriaDto {
|
pub struct SearchCriteriaDto {
|
||||||
/// Optional text to search in file/folder names
|
/// Optional text to search in file/folder names
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
@@ -98,7 +99,7 @@ impl Default for SearchCriteriaDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A file search result enriched with server-computed metadata
|
/// A file search result enriched with server-computed metadata
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct SearchFileResultDto {
|
pub struct SearchFileResultDto {
|
||||||
/// File ID
|
/// File ID
|
||||||
pub id: String,
|
pub id: String,
|
||||||
@@ -129,7 +130,7 @@ pub struct SearchFileResultDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A folder search result enriched with server-computed metadata
|
/// A folder search result enriched with server-computed metadata
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct SearchFolderResultDto {
|
pub struct SearchFolderResultDto {
|
||||||
/// Folder ID
|
/// Folder ID
|
||||||
pub id: String,
|
pub id: String,
|
||||||
@@ -156,7 +157,7 @@ pub struct SearchFolderResultDto {
|
|||||||
* both files and folders that match the search criteria, along with pagination
|
* both files and folders that match the search criteria, along with pagination
|
||||||
* information and server-computed metadata.
|
* information and server-computed metadata.
|
||||||
*/
|
*/
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct SearchResultsDto {
|
pub struct SearchResultsDto {
|
||||||
/// Files matching the search criteria (enriched with metadata)
|
/// Files matching the search criteria (enriched with metadata)
|
||||||
pub files: Vec<SearchFileResultDto>,
|
pub files: Vec<SearchFileResultDto>,
|
||||||
@@ -227,7 +228,7 @@ impl SearchResultsDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// DTO for search suggestion results (quick prefix search)
|
/// DTO for search suggestion results (quick prefix search)
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct SearchSuggestionsDto {
|
pub struct SearchSuggestionsDto {
|
||||||
/// Suggested file/folder names matching the query prefix
|
/// Suggested file/folder names matching the query prefix
|
||||||
pub suggestions: Vec<SearchSuggestionItem>,
|
pub suggestions: Vec<SearchSuggestionItem>,
|
||||||
@@ -236,7 +237,7 @@ pub struct SearchSuggestionsDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Individual search suggestion item
|
/// Individual search suggestion item
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct SearchSuggestionItem {
|
pub struct SearchSuggestionItem {
|
||||||
/// The suggested name
|
/// The suggested name
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use crate::domain::entities::share::{Share, SharePermissions};
|
use crate::domain::entities::share::{Share, SharePermissions};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct ShareDto {
|
pub struct ShareDto {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub item_id: String,
|
pub item_id: String,
|
||||||
@@ -18,14 +19,14 @@ pub struct ShareDto {
|
|||||||
pub access_count: u64,
|
pub access_count: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct SharePermissionsDto {
|
pub struct SharePermissionsDto {
|
||||||
pub read: bool,
|
pub read: bool,
|
||||||
pub write: bool,
|
pub write: bool,
|
||||||
pub reshare: bool,
|
pub reshare: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct CreateShareDto {
|
pub struct CreateShareDto {
|
||||||
pub item_id: String,
|
pub item_id: String,
|
||||||
pub item_name: Option<String>,
|
pub item_name: Option<String>,
|
||||||
@@ -35,7 +36,7 @@ pub struct CreateShareDto {
|
|||||||
pub permissions: Option<SharePermissionsDto>,
|
pub permissions: Option<SharePermissionsDto>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct UpdateShareDto {
|
pub struct UpdateShareDto {
|
||||||
pub password: Option<String>,
|
pub password: Option<String>,
|
||||||
pub expires_at: Option<u64>,
|
pub expires_at: Option<u64>,
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
/// DTO representing an item in the trash
|
/// DTO representing an item in the trash
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct TrashedItemDto {
|
pub struct TrashedItemDto {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub original_id: String,
|
pub original_id: String,
|
||||||
@@ -20,20 +21,20 @@ pub struct TrashedItemDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Request to move an item to trash
|
/// Request to move an item to trash
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, ToSchema)]
|
||||||
pub struct MoveToTrashRequest {
|
pub struct MoveToTrashRequest {
|
||||||
pub item_id: String,
|
pub item_id: String,
|
||||||
pub item_type: String, // "file" o "folder"
|
pub item_type: String, // "file" o "folder"
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request to restore an item from trash
|
/// Request to restore an item from trash
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, ToSchema)]
|
||||||
pub struct RestoreFromTrashRequest {
|
pub struct RestoreFromTrashRequest {
|
||||||
pub trash_id: String,
|
pub trash_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request to permanently delete an item from trash
|
/// Request to permanently delete an item from trash
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, ToSchema)]
|
||||||
pub struct DeletePermanentlyRequest {
|
pub struct DeletePermanentlyRequest {
|
||||||
pub trash_id: String,
|
pub trash_id: String,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
use crate::domain::entities::user::User;
|
use crate::domain::entities::user::User;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use utoipa::ToSchema;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct UserDto {
|
pub struct UserDto {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
@@ -36,13 +37,13 @@ impl From<User> for UserDto {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
|
||||||
pub struct LoginDto {
|
pub struct LoginDto {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
|
||||||
pub struct RegisterDto {
|
pub struct RegisterDto {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
@@ -51,14 +52,14 @@ pub struct RegisterDto {
|
|||||||
|
|
||||||
/// DTO for the one-time initial admin setup endpoint (`/api/setup`).
|
/// DTO for the one-time initial admin setup endpoint (`/api/setup`).
|
||||||
/// Available only when the system is not yet initialized (no admin exists).
|
/// Available only when the system is not yet initialized (no admin exists).
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
|
||||||
pub struct SetupAdminDto {
|
pub struct SetupAdminDto {
|
||||||
pub username: String,
|
pub username: String,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct AuthResponseDto {
|
pub struct AuthResponseDto {
|
||||||
pub user: UserDto,
|
pub user: UserDto,
|
||||||
pub access_token: String,
|
pub access_token: String,
|
||||||
@@ -67,19 +68,19 @@ pub struct AuthResponseDto {
|
|||||||
pub expires_in: i64,
|
pub expires_in: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct ChangePasswordDto {
|
pub struct ChangePasswordDto {
|
||||||
pub current_password: String,
|
pub current_password: String,
|
||||||
pub new_password: String,
|
pub new_password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct RefreshTokenDto {
|
pub struct RefreshTokenDto {
|
||||||
pub refresh_token: String,
|
pub refresh_token: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Authenticated current user data (for use in application services)
|
/// Authenticated current user data (for use in application services)
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct CurrentUser {
|
pub struct CurrentUser {
|
||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub username: String,
|
pub username: String,
|
||||||
@@ -91,19 +92,19 @@ pub struct CurrentUser {
|
|||||||
// App Password DTOs
|
// App Password DTOs
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct CreateAppPasswordDto {
|
pub struct CreateAppPasswordDto {
|
||||||
pub label: String,
|
pub label: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct AppPasswordCreatedDto {
|
pub struct AppPasswordCreatedDto {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub label: String,
|
pub label: String,
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct AppPasswordDto {
|
pub struct AppPasswordDto {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub label: String,
|
pub label: String,
|
||||||
@@ -116,27 +117,27 @@ pub struct AppPasswordDto {
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
/// Response with the OIDC authorization URL for client redirect
|
/// Response with the OIDC authorization URL for client redirect
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct OidcAuthorizeResponseDto {
|
pub struct OidcAuthorizeResponseDto {
|
||||||
pub authorize_url: String,
|
pub authorize_url: String,
|
||||||
pub state: String,
|
pub state: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Query parameters received on the OIDC callback
|
/// Query parameters received on the OIDC callback
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct OidcCallbackQueryDto {
|
pub struct OidcCallbackQueryDto {
|
||||||
pub code: String,
|
pub code: String,
|
||||||
pub state: String,
|
pub state: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request body for the OIDC one-time code exchange endpoint
|
/// Request body for the OIDC one-time code exchange endpoint
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct OidcExchangeDto {
|
pub struct OidcExchangeDto {
|
||||||
pub code: String,
|
pub code: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Information about available OIDC providers
|
/// Information about available OIDC providers
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct OidcProviderInfoDto {
|
pub struct OidcProviderInfoDto {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
pub provider_name: String,
|
pub provider_name: String,
|
||||||
@@ -145,7 +146,7 @@ pub struct OidcProviderInfoDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Claims extracted from the validated OIDC ID token
|
/// Claims extracted from the validated OIDC ID token
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct OidcUserInfoDto {
|
pub struct OidcUserInfoDto {
|
||||||
pub sub: String,
|
pub sub: String,
|
||||||
pub preferred_username: Option<String>,
|
pub preferred_username: Option<String>,
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
use oxicloud::interfaces::api::ApiDoc;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use utoipa::OpenApi;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let openapi = ApiDoc::openapi();
|
||||||
|
|
||||||
|
let json =
|
||||||
|
serde_json::to_string_pretty(&openapi).expect("Failed to serialize OpenAPI spec to JSON");
|
||||||
|
|
||||||
|
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||||||
|
|
||||||
|
let resources_gen_dir = manifest_dir.join("resources").join("gen");
|
||||||
|
fs::create_dir_all(&resources_gen_dir).expect("Failed to create resources/gen directory");
|
||||||
|
|
||||||
|
let output_path = resources_gen_dir.join("openapi.json");
|
||||||
|
fs::write(&output_path, json).expect("Failed to write OpenAPI spec to file");
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"OpenAPI spec generated successfully at: {}",
|
||||||
|
output_path.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,25 +7,34 @@ use axum::{
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tracing::{error, info};
|
use tracing::{error, info};
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use crate::application::ports::favorites_ports::FavoritesUseCase;
|
use crate::application::ports::favorites_ports::FavoritesUseCase;
|
||||||
use crate::application::services::favorites_service::FavoritesService;
|
use crate::application::services::favorites_service::FavoritesService;
|
||||||
use crate::interfaces::middleware::auth::AuthUser;
|
use crate::interfaces::middleware::auth::AuthUser;
|
||||||
|
|
||||||
/// Single item in a batch-add-favorites request.
|
/// Single item in a batch-add-favorites request.
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, ToSchema)]
|
||||||
pub struct BatchFavoriteItem {
|
pub struct BatchFavoriteItem {
|
||||||
pub item_id: String,
|
pub item_id: String,
|
||||||
pub item_type: String,
|
pub item_type: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Request body for POST /api/favorites/batch
|
/// Request body for POST /api/favorites/batch
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, ToSchema)]
|
||||||
pub struct BatchFavoritesRequest {
|
pub struct BatchFavoritesRequest {
|
||||||
pub items: Vec<BatchFavoriteItem>,
|
pub items: Vec<BatchFavoriteItem>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handler for favorite-related API endpoints
|
/// Handler for favorite-related API endpoints
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/api/favorites",
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "List of favorites", body = Vec<crate::application::dtos::favorites_dto::FavoriteItemDto>)
|
||||||
|
),
|
||||||
|
tag = "favorites"
|
||||||
|
)]
|
||||||
pub async fn get_favorites(
|
pub async fn get_favorites(
|
||||||
State(favorites_service): State<Arc<FavoritesService>>,
|
State(favorites_service): State<Arc<FavoritesService>>,
|
||||||
auth_user: AuthUser,
|
auth_user: AuthUser,
|
||||||
@@ -51,6 +60,19 @@ pub async fn get_favorites(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add an item to user's favorites
|
/// Add an item to user's favorites
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/api/favorites/{item_type}/{item_id}",
|
||||||
|
params(
|
||||||
|
("item_type" = String, Path, description = "Item type (file or folder)"),
|
||||||
|
("item_id" = String, Path, description = "Item ID")
|
||||||
|
),
|
||||||
|
responses(
|
||||||
|
(status = 201, description = "Item added to favorites"),
|
||||||
|
(status = 400, description = "Invalid item type")
|
||||||
|
),
|
||||||
|
tag = "favorites"
|
||||||
|
)]
|
||||||
pub async fn add_favorite(
|
pub async fn add_favorite(
|
||||||
State(favorites_service): State<Arc<FavoritesService>>,
|
State(favorites_service): State<Arc<FavoritesService>>,
|
||||||
auth_user: AuthUser,
|
auth_user: AuthUser,
|
||||||
@@ -94,6 +116,19 @@ pub async fn add_favorite(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Remove an item from user's favorites
|
/// Remove an item from user's favorites
|
||||||
|
#[utoipa::path(
|
||||||
|
delete,
|
||||||
|
path = "/api/favorites/{item_type}/{item_id}",
|
||||||
|
params(
|
||||||
|
("item_type" = String, Path, description = "Item type (file or folder)"),
|
||||||
|
("item_id" = String, Path, description = "Item ID")
|
||||||
|
),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Item removed from favorites"),
|
||||||
|
(status = 404, description = "Item not in favorites")
|
||||||
|
),
|
||||||
|
tag = "favorites"
|
||||||
|
)]
|
||||||
pub async fn remove_favorite(
|
pub async fn remove_favorite(
|
||||||
State(favorites_service): State<Arc<FavoritesService>>,
|
State(favorites_service): State<Arc<FavoritesService>>,
|
||||||
auth_user: AuthUser,
|
auth_user: AuthUser,
|
||||||
@@ -138,6 +173,15 @@ pub async fn remove_favorite(
|
|||||||
|
|
||||||
/// Add multiple items to favourites in a single transaction.
|
/// Add multiple items to favourites in a single transaction.
|
||||||
/// POST /api/favorites/batch
|
/// POST /api/favorites/batch
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/api/favorites/batch",
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Batch add result", body = crate::application::dtos::favorites_dto::BatchFavoritesResult),
|
||||||
|
(status = 400, description = "Invalid request")
|
||||||
|
),
|
||||||
|
tag = "favorites"
|
||||||
|
)]
|
||||||
pub async fn batch_add_favorites(
|
pub async fn batch_add_favorites(
|
||||||
State(favorites_service): State<Arc<FavoritesService>>,
|
State(favorites_service): State<Arc<FavoritesService>>,
|
||||||
auth_user: AuthUser,
|
auth_user: AuthUser,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use bytes::Bytes;
|
|||||||
use http_range_header::parse_range_header;
|
use http_range_header::parse_range_header;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use crate::application::ports::file_ports::OptimizedFileContent;
|
use crate::application::ports::file_ports::OptimizedFileContent;
|
||||||
use crate::application::ports::file_ports::{
|
use crate::application::ports::file_ports::{
|
||||||
@@ -1005,7 +1006,7 @@ impl FileHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Payload for moving a file
|
/// Payload for moving a file
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, ToSchema)]
|
||||||
pub struct MoveFilePayload {
|
pub struct MoveFilePayload {
|
||||||
/// Target folder ID (None means root)
|
/// Target folder ID (None means root)
|
||||||
pub folder_id: Option<String>,
|
pub folder_id: Option<String>,
|
||||||
|
|||||||
@@ -20,6 +20,14 @@ pub struct GetRecentParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get user's recent items
|
/// Get user's recent items
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/api/recent",
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "List of recent items", body = Vec<crate::application::dtos::recent_dto::RecentItemDto>)
|
||||||
|
),
|
||||||
|
tag = "recent"
|
||||||
|
)]
|
||||||
pub async fn get_recent_items(
|
pub async fn get_recent_items(
|
||||||
State(recent_service): State<Arc<RecentService>>,
|
State(recent_service): State<Arc<RecentService>>,
|
||||||
auth_user: AuthUser,
|
auth_user: AuthUser,
|
||||||
@@ -46,6 +54,19 @@ pub async fn get_recent_items(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Record access to an item
|
/// Record access to an item
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/api/recent/{item_type}/{item_id}",
|
||||||
|
params(
|
||||||
|
("item_type" = String, Path, description = "Item type (file or folder)"),
|
||||||
|
("item_id" = String, Path, description = "Item ID")
|
||||||
|
),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Access recorded"),
|
||||||
|
(status = 400, description = "Invalid item type")
|
||||||
|
),
|
||||||
|
tag = "recent"
|
||||||
|
)]
|
||||||
pub async fn record_item_access(
|
pub async fn record_item_access(
|
||||||
State(recent_service): State<Arc<RecentService>>,
|
State(recent_service): State<Arc<RecentService>>,
|
||||||
auth_user: AuthUser,
|
auth_user: AuthUser,
|
||||||
@@ -92,6 +113,19 @@ pub async fn record_item_access(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Remove an item from recents
|
/// Remove an item from recents
|
||||||
|
#[utoipa::path(
|
||||||
|
delete,
|
||||||
|
path = "/api/recent/{item_type}/{item_id}",
|
||||||
|
params(
|
||||||
|
("item_type" = String, Path, description = "Item type (file or folder)"),
|
||||||
|
("item_id" = String, Path, description = "Item ID")
|
||||||
|
),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Item removed from recents"),
|
||||||
|
(status = 404, description = "Item not in recents")
|
||||||
|
),
|
||||||
|
tag = "recent"
|
||||||
|
)]
|
||||||
pub async fn remove_from_recent(
|
pub async fn remove_from_recent(
|
||||||
State(recent_service): State<Arc<RecentService>>,
|
State(recent_service): State<Arc<RecentService>>,
|
||||||
auth_user: AuthUser,
|
auth_user: AuthUser,
|
||||||
@@ -138,6 +172,14 @@ pub async fn remove_from_recent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Clear all recent items
|
/// Clear all recent items
|
||||||
|
#[utoipa::path(
|
||||||
|
delete,
|
||||||
|
path = "/api/recent/clear",
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Recent items cleared")
|
||||||
|
),
|
||||||
|
tag = "recent"
|
||||||
|
)]
|
||||||
pub async fn clear_recent_items(
|
pub async fn clear_recent_items(
|
||||||
State(recent_service): State<Arc<RecentService>>,
|
State(recent_service): State<Arc<RecentService>>,
|
||||||
auth_user: AuthUser,
|
auth_user: AuthUser,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use axum::{
|
|||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
use utoipa::ToSchema;
|
||||||
|
|
||||||
use crate::application::services::share_service::ShareService;
|
use crate::application::services::share_service::ShareService;
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -30,12 +31,22 @@ pub struct GetSharesQuery {
|
|||||||
pub item_type: Option<String>,
|
pub item_type: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize, ToSchema)]
|
||||||
pub struct VerifyPasswordRequest {
|
pub struct VerifyPasswordRequest {
|
||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new shared link
|
/// Create a new shared link
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/api/shares",
|
||||||
|
request_body = CreateShareDto,
|
||||||
|
responses(
|
||||||
|
(status = 201, description = "Share created", body = crate::application::dtos::share_dto::ShareDto),
|
||||||
|
(status = 400, description = "Bad request")
|
||||||
|
),
|
||||||
|
tag = "shares"
|
||||||
|
)]
|
||||||
pub async fn create_shared_link(
|
pub async fn create_shared_link(
|
||||||
State(share_use_case): State<Arc<ShareService>>,
|
State(share_use_case): State<Arc<ShareService>>,
|
||||||
auth_user: AuthUser,
|
auth_user: AuthUser,
|
||||||
@@ -48,6 +59,16 @@ pub async fn create_shared_link(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get information about a specific shared link by ID
|
/// Get information about a specific shared link by ID
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/api/shares/{id}",
|
||||||
|
params(("id" = String, Path, description = "Share ID")),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Share details", body = crate::application::dtos::share_dto::ShareDto),
|
||||||
|
(status = 404, description = "Share not found")
|
||||||
|
),
|
||||||
|
tag = "shares"
|
||||||
|
)]
|
||||||
pub async fn get_shared_link(
|
pub async fn get_shared_link(
|
||||||
State(share_use_case): State<Arc<ShareService>>,
|
State(share_use_case): State<Arc<ShareService>>,
|
||||||
auth_user: AuthUser,
|
auth_user: AuthUser,
|
||||||
@@ -65,6 +86,14 @@ pub async fn get_shared_link(
|
|||||||
|
|
||||||
/// Get all shared links created by the current user.
|
/// Get all shared links created by the current user.
|
||||||
/// Supports optional filtering by item_id + item_type query params.
|
/// Supports optional filtering by item_id + item_type query params.
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/api/shares",
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "List of shares", body = Vec<crate::application::dtos::share_dto::ShareDto>)
|
||||||
|
),
|
||||||
|
tag = "shares"
|
||||||
|
)]
|
||||||
pub async fn get_user_shares(
|
pub async fn get_user_shares(
|
||||||
State(share_use_case): State<Arc<ShareService>>,
|
State(share_use_case): State<Arc<ShareService>>,
|
||||||
auth_user: AuthUser,
|
auth_user: AuthUser,
|
||||||
@@ -107,6 +136,17 @@ pub async fn get_user_shares(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Update a shared link's properties
|
/// Update a shared link's properties
|
||||||
|
#[utoipa::path(
|
||||||
|
put,
|
||||||
|
path = "/api/shares/{id}",
|
||||||
|
params(("id" = String, Path, description = "Share ID")),
|
||||||
|
request_body = UpdateShareDto,
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Share updated", body = crate::application::dtos::share_dto::ShareDto),
|
||||||
|
(status = 404, description = "Share not found")
|
||||||
|
),
|
||||||
|
tag = "shares"
|
||||||
|
)]
|
||||||
pub async fn update_shared_link(
|
pub async fn update_shared_link(
|
||||||
State(share_use_case): State<Arc<ShareService>>,
|
State(share_use_case): State<Arc<ShareService>>,
|
||||||
auth_user: AuthUser,
|
auth_user: AuthUser,
|
||||||
@@ -127,6 +167,16 @@ pub async fn update_shared_link(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Delete a shared link
|
/// Delete a shared link
|
||||||
|
#[utoipa::path(
|
||||||
|
delete,
|
||||||
|
path = "/api/shares/{id}",
|
||||||
|
params(("id" = String, Path, description = "Share ID")),
|
||||||
|
responses(
|
||||||
|
(status = 204, description = "Share deleted"),
|
||||||
|
(status = 404, description = "Share not found")
|
||||||
|
),
|
||||||
|
tag = "shares"
|
||||||
|
)]
|
||||||
pub async fn delete_shared_link(
|
pub async fn delete_shared_link(
|
||||||
State(share_use_case): State<Arc<ShareService>>,
|
State(share_use_case): State<Arc<ShareService>>,
|
||||||
auth_user: AuthUser,
|
auth_user: AuthUser,
|
||||||
@@ -143,6 +193,17 @@ pub async fn delete_shared_link(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Access a shared item via its token
|
/// Access a shared item via its token
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/api/s/{token}",
|
||||||
|
params(("token" = String, Path, description = "Share token")),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Shared item details"),
|
||||||
|
(status = 401, description = "Password required"),
|
||||||
|
(status = 410, description = "Share expired")
|
||||||
|
),
|
||||||
|
tag = "shares"
|
||||||
|
)]
|
||||||
pub async fn access_shared_item(
|
pub async fn access_shared_item(
|
||||||
State(share_use_case): State<Arc<ShareService>>,
|
State(share_use_case): State<Arc<ShareService>>,
|
||||||
Path(token): Path<String>,
|
Path(token): Path<String>,
|
||||||
@@ -176,6 +237,17 @@ pub async fn access_shared_item(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Verify password for a password-protected shared item
|
/// Verify password for a password-protected shared item
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/api/s/{token}/verify",
|
||||||
|
params(("token" = String, Path, description = "Share token")),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Password verified, item details returned"),
|
||||||
|
(status = 401, description = "Invalid password"),
|
||||||
|
(status = 410, description = "Share expired")
|
||||||
|
),
|
||||||
|
tag = "shares"
|
||||||
|
)]
|
||||||
pub async fn verify_shared_item_password(
|
pub async fn verify_shared_item_password(
|
||||||
State(share_use_case): State<Arc<ShareService>>,
|
State(share_use_case): State<Arc<ShareService>>,
|
||||||
Path(token): Path<String>,
|
Path(token): Path<String>,
|
||||||
|
|||||||
@@ -10,6 +10,15 @@ use crate::interfaces::middleware::auth::AuthUser;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Gets all items in the trash for the current user
|
/// Gets all items in the trash for the current user
|
||||||
|
#[utoipa::path(
|
||||||
|
get,
|
||||||
|
path = "/api/trash",
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "List of trashed items"),
|
||||||
|
(status = 501, description = "Trash feature not enabled")
|
||||||
|
),
|
||||||
|
tag = "trash"
|
||||||
|
)]
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn get_trash_items(
|
pub async fn get_trash_items(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
@@ -54,6 +63,16 @@ pub async fn get_trash_items(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Moves a file to the trash
|
/// Moves a file to the trash
|
||||||
|
#[utoipa::path(
|
||||||
|
delete,
|
||||||
|
path = "/api/trash/files/{id}",
|
||||||
|
params(("id" = String, Path, description = "File ID")),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "File moved to trash"),
|
||||||
|
(status = 501, description = "Trash feature not enabled")
|
||||||
|
),
|
||||||
|
tag = "trash"
|
||||||
|
)]
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn move_file_to_trash(
|
pub async fn move_file_to_trash(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
@@ -105,6 +124,16 @@ pub async fn move_file_to_trash(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Moves a folder to the trash
|
/// Moves a folder to the trash
|
||||||
|
#[utoipa::path(
|
||||||
|
delete,
|
||||||
|
path = "/api/trash/folders/{id}",
|
||||||
|
params(("id" = String, Path, description = "Folder ID")),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Folder moved to trash"),
|
||||||
|
(status = 501, description = "Trash feature not enabled")
|
||||||
|
),
|
||||||
|
tag = "trash"
|
||||||
|
)]
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn move_folder_to_trash(
|
pub async fn move_folder_to_trash(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
@@ -158,6 +187,16 @@ pub async fn move_folder_to_trash(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Restores an item from the trash to its original location
|
/// Restores an item from the trash to its original location
|
||||||
|
#[utoipa::path(
|
||||||
|
post,
|
||||||
|
path = "/api/trash/{id}/restore",
|
||||||
|
params(("id" = String, Path, description = "Trash item ID")),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Item restored from trash"),
|
||||||
|
(status = 501, description = "Trash feature not enabled")
|
||||||
|
),
|
||||||
|
tag = "trash"
|
||||||
|
)]
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn restore_from_trash(
|
pub async fn restore_from_trash(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
@@ -219,6 +258,16 @@ pub async fn restore_from_trash(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Permanently deletes an item from the trash
|
/// Permanently deletes an item from the trash
|
||||||
|
#[utoipa::path(
|
||||||
|
delete,
|
||||||
|
path = "/api/trash/{id}",
|
||||||
|
params(("id" = String, Path, description = "Trash item ID")),
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Item permanently deleted"),
|
||||||
|
(status = 501, description = "Trash feature not enabled")
|
||||||
|
),
|
||||||
|
tag = "trash"
|
||||||
|
)]
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn delete_permanently(
|
pub async fn delete_permanently(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
@@ -282,6 +331,15 @@ pub async fn delete_permanently(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Empties the trash completely for the current user
|
/// Empties the trash completely for the current user
|
||||||
|
#[utoipa::path(
|
||||||
|
delete,
|
||||||
|
path = "/api/trash/empty",
|
||||||
|
responses(
|
||||||
|
(status = 200, description = "Trash emptied successfully"),
|
||||||
|
(status = 501, description = "Trash feature not enabled")
|
||||||
|
),
|
||||||
|
tag = "trash"
|
||||||
|
)]
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
pub async fn empty_trash(
|
pub async fn empty_trash(
|
||||||
State(state): State<Arc<AppState>>,
|
State(state): State<Arc<AppState>>,
|
||||||
|
|||||||
@@ -4,3 +4,176 @@ pub mod routes;
|
|||||||
|
|
||||||
pub use routes::create_api_routes;
|
pub use routes::create_api_routes;
|
||||||
pub use routes::create_public_api_routes;
|
pub use routes::create_public_api_routes;
|
||||||
|
|
||||||
|
use utoipa::OpenApi;
|
||||||
|
|
||||||
|
use crate::application::dtos::favorites_dto::{
|
||||||
|
BatchFavoritesResult, BatchFavoritesStats, FavoriteItemDto,
|
||||||
|
};
|
||||||
|
use crate::application::dtos::file_dto::FileDto;
|
||||||
|
use crate::application::dtos::folder_dto::{
|
||||||
|
CreateFolderDto, FolderDto, MoveFolderDto, RenameFolderDto,
|
||||||
|
};
|
||||||
|
use crate::application::dtos::folder_listing_dto::FolderListingDto;
|
||||||
|
use crate::application::dtos::pagination::{PaginationDto, PaginationRequestDto};
|
||||||
|
use crate::application::dtos::recent_dto::RecentItemDto;
|
||||||
|
use crate::application::dtos::search_dto::{
|
||||||
|
SearchCriteriaDto, SearchFileResultDto, SearchFolderResultDto, SearchResultsDto,
|
||||||
|
SearchSuggestionItem, SearchSuggestionsDto,
|
||||||
|
};
|
||||||
|
use crate::application::dtos::share_dto::{
|
||||||
|
CreateShareDto, ShareDto, SharePermissionsDto, UpdateShareDto,
|
||||||
|
};
|
||||||
|
use crate::application::dtos::trash_dto::{
|
||||||
|
DeletePermanentlyRequest, MoveToTrashRequest, RestoreFromTrashRequest, TrashedItemDto,
|
||||||
|
};
|
||||||
|
use crate::application::dtos::user_dto::{
|
||||||
|
AuthResponseDto, ChangePasswordDto, LoginDto, RefreshTokenDto, RegisterDto, SetupAdminDto,
|
||||||
|
UserDto,
|
||||||
|
};
|
||||||
|
use crate::interfaces::api::handlers::file_handler::MoveFilePayload;
|
||||||
|
|
||||||
|
#[derive(OpenApi)]
|
||||||
|
#[openapi(
|
||||||
|
paths(
|
||||||
|
handlers::trash_handler::get_trash_items,
|
||||||
|
handlers::trash_handler::move_file_to_trash,
|
||||||
|
handlers::trash_handler::move_folder_to_trash,
|
||||||
|
handlers::trash_handler::restore_from_trash,
|
||||||
|
handlers::trash_handler::delete_permanently,
|
||||||
|
handlers::trash_handler::empty_trash,
|
||||||
|
handlers::share_handler::create_shared_link,
|
||||||
|
handlers::share_handler::get_shared_link,
|
||||||
|
handlers::share_handler::get_user_shares,
|
||||||
|
handlers::share_handler::update_shared_link,
|
||||||
|
handlers::share_handler::delete_shared_link,
|
||||||
|
handlers::share_handler::access_shared_item,
|
||||||
|
handlers::share_handler::verify_shared_item_password,
|
||||||
|
handlers::favorites_handler::get_favorites,
|
||||||
|
handlers::favorites_handler::add_favorite,
|
||||||
|
handlers::favorites_handler::remove_favorite,
|
||||||
|
handlers::favorites_handler::batch_add_favorites,
|
||||||
|
handlers::recent_handler::get_recent_items,
|
||||||
|
handlers::recent_handler::record_item_access,
|
||||||
|
handlers::recent_handler::remove_from_recent,
|
||||||
|
handlers::recent_handler::clear_recent_items,
|
||||||
|
),
|
||||||
|
components(
|
||||||
|
schemas(
|
||||||
|
// Folder schemas
|
||||||
|
FolderDto,
|
||||||
|
CreateFolderDto,
|
||||||
|
RenameFolderDto,
|
||||||
|
MoveFolderDto,
|
||||||
|
FolderListingDto,
|
||||||
|
// File schemas
|
||||||
|
FileDto,
|
||||||
|
MoveFilePayload,
|
||||||
|
PaginationDto,
|
||||||
|
PaginationRequestDto,
|
||||||
|
// User / Auth schemas
|
||||||
|
UserDto,
|
||||||
|
LoginDto,
|
||||||
|
RegisterDto,
|
||||||
|
SetupAdminDto,
|
||||||
|
AuthResponseDto,
|
||||||
|
ChangePasswordDto,
|
||||||
|
RefreshTokenDto,
|
||||||
|
// Share schemas
|
||||||
|
ShareDto,
|
||||||
|
SharePermissionsDto,
|
||||||
|
CreateShareDto,
|
||||||
|
UpdateShareDto,
|
||||||
|
// Trash schemas
|
||||||
|
TrashedItemDto,
|
||||||
|
MoveToTrashRequest,
|
||||||
|
RestoreFromTrashRequest,
|
||||||
|
DeletePermanentlyRequest,
|
||||||
|
// Search schemas
|
||||||
|
SearchCriteriaDto,
|
||||||
|
SearchResultsDto,
|
||||||
|
SearchFileResultDto,
|
||||||
|
SearchFolderResultDto,
|
||||||
|
SearchSuggestionsDto,
|
||||||
|
SearchSuggestionItem,
|
||||||
|
// Favorites schemas
|
||||||
|
FavoriteItemDto,
|
||||||
|
BatchFavoritesResult,
|
||||||
|
BatchFavoritesStats,
|
||||||
|
// Recent schemas
|
||||||
|
RecentItemDto,
|
||||||
|
)
|
||||||
|
),
|
||||||
|
tags(
|
||||||
|
(name = "folders", description = "Folder management endpoints"),
|
||||||
|
(name = "files", description = "File management endpoints"),
|
||||||
|
(name = "trash", description = "Trash / recycle bin endpoints"),
|
||||||
|
(name = "search", description = "Search endpoints"),
|
||||||
|
(name = "shares", description = "Shared links endpoints"),
|
||||||
|
(name = "favorites", description = "Favorites management endpoints"),
|
||||||
|
(name = "recent", description = "Recent items endpoints"),
|
||||||
|
),
|
||||||
|
info(
|
||||||
|
title = "OxiCloud API",
|
||||||
|
version = env!("CARGO_PKG_VERSION"),
|
||||||
|
description = "REST API for OxiCloud — self-hosted cloud storage, calendar & contacts",
|
||||||
|
license(name = "MIT")
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
pub struct ApiDoc;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn openapi_spec_is_valid_and_has_expected_structure() {
|
||||||
|
let spec = ApiDoc::openapi();
|
||||||
|
|
||||||
|
assert_eq!(spec.info.title, "OxiCloud API");
|
||||||
|
assert!(!spec.info.version.is_empty());
|
||||||
|
|
||||||
|
let paths = &spec.paths;
|
||||||
|
assert!(
|
||||||
|
paths.paths.len() >= 10,
|
||||||
|
"expected at least 10 paths, got {}",
|
||||||
|
paths.paths.len()
|
||||||
|
);
|
||||||
|
assert!(paths.paths.contains_key("/api/trash"), "missing /api/trash");
|
||||||
|
assert!(
|
||||||
|
paths.paths.contains_key("/api/shares"),
|
||||||
|
"missing /api/shares"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
paths.paths.contains_key("/api/favorites"),
|
||||||
|
"missing /api/favorites"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
paths.paths.contains_key("/api/recent"),
|
||||||
|
"missing /api/recent"
|
||||||
|
);
|
||||||
|
|
||||||
|
let schemas = &spec
|
||||||
|
.components
|
||||||
|
.as_ref()
|
||||||
|
.expect("components missing")
|
||||||
|
.schemas;
|
||||||
|
assert!(
|
||||||
|
schemas.len() >= 25,
|
||||||
|
"expected at least 25 schemas, got {}",
|
||||||
|
schemas.len()
|
||||||
|
);
|
||||||
|
for name in [
|
||||||
|
"FileDto",
|
||||||
|
"FolderDto",
|
||||||
|
"ShareDto",
|
||||||
|
"TrashedItemDto",
|
||||||
|
"UserDto",
|
||||||
|
] {
|
||||||
|
assert!(schemas.contains_key(name), "missing schema: {name}");
|
||||||
|
}
|
||||||
|
|
||||||
|
let json = serde_json::to_string(&spec).expect("spec should serialise to JSON");
|
||||||
|
assert!(json.len() > 1000, "spec JSON suspiciously small");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ use axum::{
|
|||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tower_http::{compression::CompressionLayer, trace::TraceLayer};
|
use tower_http::{compression::CompressionLayer, trace::TraceLayer};
|
||||||
|
use utoipa::OpenApi;
|
||||||
|
|
||||||
/// Returns the application version from Cargo.toml (compile-time constant)
|
/// Returns the application version from Cargo.toml (compile-time constant)
|
||||||
async fn get_version() -> AxumJson<serde_json::Value> {
|
async fn get_version() -> AxumJson<serde_json::Value> {
|
||||||
@@ -18,6 +19,10 @@ async fn get_version() -> AxumJson<serde_json::Value> {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_openapi_spec() -> AxumJson<utoipa::openapi::OpenApi> {
|
||||||
|
AxumJson(super::ApiDoc::openapi())
|
||||||
|
}
|
||||||
|
|
||||||
use crate::interfaces::api::handlers::admin_handler;
|
use crate::interfaces::api::handlers::admin_handler;
|
||||||
use crate::interfaces::api::handlers::batch_handler::{self, BatchHandlerState};
|
use crate::interfaces::api::handlers::batch_handler::{self, BatchHandlerState};
|
||||||
use crate::interfaces::api::handlers::chunked_upload_handler::ChunkedUploadHandler;
|
use crate::interfaces::api::handlers::chunked_upload_handler::ChunkedUploadHandler;
|
||||||
@@ -64,6 +69,7 @@ pub fn create_public_api_routes(app_state: &Arc<AppState>) -> Router<Arc<AppStat
|
|||||||
|
|
||||||
// Version endpoint — public, no auth required
|
// Version endpoint — public, no auth required
|
||||||
router = router.route("/version", get(get_version));
|
router = router.route("/version", get(get_version));
|
||||||
|
router = router.route("/openapi.json", get(get_openapi_spec));
|
||||||
|
|
||||||
router
|
router
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user