feat: add OpenAPI spec generation with utoipa and justfile
- Add utoipa v5 dependency with ToSchema derives on all REST API DTOs - Annotate free-function handlers with #[utoipa::path] (trash, share, favorites, recent) - Create ApiDoc struct with OpenApi derive registering 37 schemas across 7 tags - Add generate-openapi binary outputting resources/gen/openapi.json - Serve OpenAPI spec at GET /api/openapi.json (public, no auth) - Add justfile with common dev commands (build, test, lint, check, openapi, db)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use super::display_helpers::{
|
||||
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
|
||||
/// 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 {
|
||||
/// Unique identifier for the favorite entry
|
||||
pub id: String,
|
||||
@@ -84,7 +85,7 @@ impl FavoriteItemDto {
|
||||
}
|
||||
|
||||
/// Result DTO for batch add-to-favorites.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct BatchFavoritesResult {
|
||||
/// Statistics about the batch operation
|
||||
pub stats: BatchFavoritesStats,
|
||||
@@ -93,7 +94,7 @@ pub struct BatchFavoritesResult {
|
||||
pub favorites: Vec<FavoriteItemDto>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct BatchFavoritesStats {
|
||||
/// How many items were requested
|
||||
pub requested: usize,
|
||||
|
||||
@@ -2,13 +2,14 @@ use std::sync::Arc;
|
||||
|
||||
use crate::domain::entities::file::File;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use super::display_helpers::{
|
||||
category_for, format_file_size, icon_class_for, icon_special_class_for,
|
||||
};
|
||||
|
||||
/// DTO for file responses
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct FileDto {
|
||||
/// File ID
|
||||
pub id: String,
|
||||
@@ -24,6 +25,7 @@ pub struct FileDto {
|
||||
|
||||
/// MIME type — `Arc<str>` because MIME values repeat across files
|
||||
/// and DTOs are cloned on every request (clone is O(1) atomic increment).
|
||||
#[schema(value_type = String)]
|
||||
pub mime_type: Arc<str>,
|
||||
|
||||
/// Parent folder ID
|
||||
@@ -37,12 +39,15 @@ pub struct FileDto {
|
||||
|
||||
// ── Pre-computed display fields (Arc<str>: values come from static tables) ──
|
||||
/// FontAwesome icon CSS class (e.g. "fas fa-file-image")
|
||||
#[schema(value_type = String)]
|
||||
pub icon_class: Arc<str>,
|
||||
|
||||
/// Extra CSS class for icon styling (e.g. "image-icon", "" when default)
|
||||
#[schema(value_type = String)]
|
||||
pub icon_special_class: Arc<str>,
|
||||
|
||||
/// Human-readable file category (e.g. "Image", "Document")
|
||||
#[schema(value_type = String)]
|
||||
pub category: Arc<str>,
|
||||
|
||||
/// Human-readable formatted size (e.g. "3.27 MB")
|
||||
|
||||
@@ -2,9 +2,10 @@ use std::sync::Arc;
|
||||
|
||||
use crate::domain::entities::folder::Folder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
/// DTO for folder creation requests
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct CreateFolderDto {
|
||||
/// Name of the folder to create
|
||||
pub name: String,
|
||||
@@ -14,21 +15,21 @@ pub struct CreateFolderDto {
|
||||
}
|
||||
|
||||
/// DTO for folder rename requests
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct RenameFolderDto {
|
||||
/// New name for the folder
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// DTO for folder move requests
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct MoveFolderDto {
|
||||
/// New parent folder ID (None for root level)
|
||||
pub parent_id: Option<String>,
|
||||
}
|
||||
|
||||
/// DTO for folder responses
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct FolderDto {
|
||||
/// Folder ID
|
||||
pub id: String,
|
||||
@@ -57,12 +58,15 @@ pub struct FolderDto {
|
||||
|
||||
// ── Pre-computed display fields (Arc<str>: always identical values) ──
|
||||
/// FontAwesome icon CSS class (always "fas fa-folder")
|
||||
#[schema(value_type = String)]
|
||||
pub icon_class: Arc<str>,
|
||||
|
||||
/// Extra CSS class for icon styling (always "folder-icon")
|
||||
#[schema(value_type = String)]
|
||||
pub icon_special_class: Arc<str>,
|
||||
|
||||
/// Human-readable category (always "Folder")
|
||||
#[schema(value_type = String)]
|
||||
pub category: Arc<str>,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use serde::Serialize;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use super::file_dto::FileDto;
|
||||
use super::folder_dto::FolderDto;
|
||||
|
||||
/// Combined DTO that returns both sub-folders and files for a given folder
|
||||
/// in a single response, eliminating the double-fetch on every navigation.
|
||||
#[derive(Debug, Serialize)]
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct FolderListingDto {
|
||||
/// Sub-folders inside the requested folder
|
||||
pub folders: Vec<FolderDto>,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::{IntoParams, ToSchema};
|
||||
|
||||
/// A DTO to represent pagination information
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct PaginationDto {
|
||||
/// Current page (starts at 0)
|
||||
pub page: usize,
|
||||
@@ -18,7 +19,7 @@ pub struct PaginationDto {
|
||||
}
|
||||
|
||||
/// A DTO to represent a pagination request
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema, IntoParams)]
|
||||
pub struct PaginationRequestDto {
|
||||
/// Requested page (starts at 0)
|
||||
#[serde(default)]
|
||||
@@ -29,7 +30,7 @@ pub struct PaginationRequestDto {
|
||||
}
|
||||
|
||||
/// A DTO to represent a paginated response
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct PaginatedResponseDto<T> {
|
||||
/// Data on the current page
|
||||
pub items: Vec<T>,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use super::display_helpers::{
|
||||
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
|
||||
/// 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 {
|
||||
/// Unique identifier for the recent item
|
||||
pub id: String,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* 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 {
|
||||
/// Optional text to search in file/folder names
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -98,7 +99,7 @@ impl Default for SearchCriteriaDto {
|
||||
}
|
||||
|
||||
/// A file search result enriched with server-computed metadata
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct SearchFileResultDto {
|
||||
/// File ID
|
||||
pub id: String,
|
||||
@@ -129,7 +130,7 @@ pub struct SearchFileResultDto {
|
||||
}
|
||||
|
||||
/// A folder search result enriched with server-computed metadata
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct SearchFolderResultDto {
|
||||
/// Folder ID
|
||||
pub id: String,
|
||||
@@ -156,7 +157,7 @@ pub struct SearchFolderResultDto {
|
||||
* both files and folders that match the search criteria, along with pagination
|
||||
* information and server-computed metadata.
|
||||
*/
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct SearchResultsDto {
|
||||
/// Files matching the search criteria (enriched with metadata)
|
||||
pub files: Vec<SearchFileResultDto>,
|
||||
@@ -227,7 +228,7 @@ impl SearchResultsDto {
|
||||
}
|
||||
|
||||
/// DTO for search suggestion results (quick prefix search)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct SearchSuggestionsDto {
|
||||
/// Suggested file/folder names matching the query prefix
|
||||
pub suggestions: Vec<SearchSuggestionItem>,
|
||||
@@ -236,7 +237,7 @@ pub struct SearchSuggestionsDto {
|
||||
}
|
||||
|
||||
/// Individual search suggestion item
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct SearchSuggestionItem {
|
||||
/// The suggested name
|
||||
pub name: String,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::domain::entities::share::{Share, SharePermissions};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct ShareDto {
|
||||
pub id: String,
|
||||
pub item_id: String,
|
||||
@@ -18,14 +19,14 @@ pub struct ShareDto {
|
||||
pub access_count: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct SharePermissionsDto {
|
||||
pub read: bool,
|
||||
pub write: bool,
|
||||
pub reshare: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct CreateShareDto {
|
||||
pub item_id: String,
|
||||
pub item_name: Option<String>,
|
||||
@@ -35,7 +36,7 @@ pub struct CreateShareDto {
|
||||
pub permissions: Option<SharePermissionsDto>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct UpdateShareDto {
|
||||
pub password: Option<String>,
|
||||
pub expires_at: Option<u64>,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
|
||||
/// DTO representing an item in the trash
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct TrashedItemDto {
|
||||
pub id: String,
|
||||
pub original_id: String,
|
||||
@@ -20,20 +21,20 @@ pub struct TrashedItemDto {
|
||||
}
|
||||
|
||||
/// Request to move an item to trash
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct MoveToTrashRequest {
|
||||
pub item_id: String,
|
||||
pub item_type: String, // "file" o "folder"
|
||||
}
|
||||
|
||||
/// Request to restore an item from trash
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct RestoreFromTrashRequest {
|
||||
pub trash_id: String,
|
||||
}
|
||||
|
||||
/// Request to permanently delete an item from trash
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct DeletePermanentlyRequest {
|
||||
pub trash_id: String,
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
use crate::domain::entities::user::User;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use utoipa::ToSchema;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct UserDto {
|
||||
pub id: 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 username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)]
|
||||
pub struct RegisterDto {
|
||||
pub username: String,
|
||||
pub email: String,
|
||||
@@ -51,14 +52,14 @@ pub struct RegisterDto {
|
||||
|
||||
/// DTO for the one-time initial admin setup endpoint (`/api/setup`).
|
||||
/// 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 username: String,
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct AuthResponseDto {
|
||||
pub user: UserDto,
|
||||
pub access_token: String,
|
||||
@@ -67,19 +68,19 @@ pub struct AuthResponseDto {
|
||||
pub expires_in: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct ChangePasswordDto {
|
||||
pub current_password: String,
|
||||
pub new_password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct RefreshTokenDto {
|
||||
pub refresh_token: String,
|
||||
}
|
||||
|
||||
/// Authenticated current user data (for use in application services)
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct CurrentUser {
|
||||
pub id: Uuid,
|
||||
pub username: String,
|
||||
@@ -91,19 +92,19 @@ pub struct CurrentUser {
|
||||
// App Password DTOs
|
||||
// ============================================================================
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct CreateAppPasswordDto {
|
||||
pub label: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct AppPasswordCreatedDto {
|
||||
pub id: String,
|
||||
pub label: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct AppPasswordDto {
|
||||
pub id: String,
|
||||
pub label: String,
|
||||
@@ -116,27 +117,27 @@ pub struct AppPasswordDto {
|
||||
// ============================================================================
|
||||
|
||||
/// Response with the OIDC authorization URL for client redirect
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct OidcAuthorizeResponseDto {
|
||||
pub authorize_url: String,
|
||||
pub state: String,
|
||||
}
|
||||
|
||||
/// Query parameters received on the OIDC callback
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct OidcCallbackQueryDto {
|
||||
pub code: String,
|
||||
pub state: String,
|
||||
}
|
||||
|
||||
/// Request body for the OIDC one-time code exchange endpoint
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct OidcExchangeDto {
|
||||
pub code: String,
|
||||
}
|
||||
|
||||
/// Information about available OIDC providers
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Serialize, Deserialize, ToSchema)]
|
||||
pub struct OidcProviderInfoDto {
|
||||
pub enabled: bool,
|
||||
pub provider_name: String,
|
||||
@@ -145,7 +146,7 @@ pub struct OidcProviderInfoDto {
|
||||
}
|
||||
|
||||
/// Claims extracted from the validated OIDC ID token
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct OidcUserInfoDto {
|
||||
pub sub: String,
|
||||
pub preferred_username: Option<String>,
|
||||
|
||||
Reference in New Issue
Block a user