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:
iltumio
2026-03-29 18:49:10 +02:00
parent dd5328175e
commit bf7e030cd6
22 changed files with 2537 additions and 49 deletions
+4 -3
View File
@@ -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,
+6 -1
View File
@@ -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")
+8 -4
View File
@@ -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>,
}
+2 -1
View File
@@ -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>,
+4 -3
View File
@@ -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>,
+2 -1
View File
@@ -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,
+7 -6
View File
@@ -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,
+5 -4
View File
@@ -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>,
+5 -4
View File
@@ -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,
}
+17 -16
View File
@@ -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>,