From d77e5b9f2f2c3102ded533a805253213c0823a8f Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sat, 7 Mar 2026 19:57:37 +0300 Subject: [PATCH] pinakes-server: split dto module into submodules Signed-off-by: NotAShelf Change-Id: I1e9421d79010813992feb2b26c44d6796a6a6964 --- crates/pinakes-server/src/dto.rs | 1443 ------------------ crates/pinakes-server/src/dto/analytics.rs | 34 + crates/pinakes-server/src/dto/audit.rs | 23 + crates/pinakes-server/src/dto/batch.rs | 36 + crates/pinakes-server/src/dto/collections.rs | 42 + crates/pinakes-server/src/dto/config.rs | 75 + crates/pinakes-server/src/dto/enrichment.rs | 37 + crates/pinakes-server/src/dto/media.rs | 294 ++++ crates/pinakes-server/src/dto/mod.rs | 39 + crates/pinakes-server/src/dto/playlists.rs | 60 + crates/pinakes-server/src/dto/plugins.rs | 37 + crates/pinakes-server/src/dto/scan.rs | 29 + crates/pinakes-server/src/dto/search.rs | 34 + crates/pinakes-server/src/dto/sharing.rs | 202 +++ crates/pinakes-server/src/dto/social.rs | 96 ++ crates/pinakes-server/src/dto/statistics.rs | 84 + crates/pinakes-server/src/dto/subtitles.rs | 44 + crates/pinakes-server/src/dto/sync.rs | 197 +++ crates/pinakes-server/src/dto/tags.rs | 33 + crates/pinakes-server/src/dto/transcode.rs | 34 + crates/pinakes-server/src/dto/users.rs | 100 ++ 21 files changed, 1530 insertions(+), 1443 deletions(-) delete mode 100644 crates/pinakes-server/src/dto.rs create mode 100644 crates/pinakes-server/src/dto/analytics.rs create mode 100644 crates/pinakes-server/src/dto/audit.rs create mode 100644 crates/pinakes-server/src/dto/batch.rs create mode 100644 crates/pinakes-server/src/dto/collections.rs create mode 100644 crates/pinakes-server/src/dto/config.rs create mode 100644 crates/pinakes-server/src/dto/enrichment.rs create mode 100644 crates/pinakes-server/src/dto/media.rs create mode 100644 crates/pinakes-server/src/dto/mod.rs create mode 100644 crates/pinakes-server/src/dto/playlists.rs create mode 100644 crates/pinakes-server/src/dto/plugins.rs create mode 100644 crates/pinakes-server/src/dto/scan.rs create mode 100644 crates/pinakes-server/src/dto/search.rs create mode 100644 crates/pinakes-server/src/dto/sharing.rs create mode 100644 crates/pinakes-server/src/dto/social.rs create mode 100644 crates/pinakes-server/src/dto/statistics.rs create mode 100644 crates/pinakes-server/src/dto/subtitles.rs create mode 100644 crates/pinakes-server/src/dto/sync.rs create mode 100644 crates/pinakes-server/src/dto/tags.rs create mode 100644 crates/pinakes-server/src/dto/transcode.rs create mode 100644 crates/pinakes-server/src/dto/users.rs diff --git a/crates/pinakes-server/src/dto.rs b/crates/pinakes-server/src/dto.rs deleted file mode 100644 index 9811ffa..0000000 --- a/crates/pinakes-server/src/dto.rs +++ /dev/null @@ -1,1443 +0,0 @@ -use std::{collections::HashMap, path::PathBuf}; - -use chrono::{DateTime, Utc}; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -// Media -#[derive(Debug, Serialize)] -pub struct MediaResponse { - pub id: String, - pub path: String, - pub file_name: String, - pub media_type: String, - pub content_hash: String, - pub file_size: u64, - pub title: Option, - pub artist: Option, - pub album: Option, - pub genre: Option, - pub year: Option, - pub duration_secs: Option, - pub description: Option, - pub has_thumbnail: bool, - pub custom_fields: HashMap, - - // Photo-specific metadata - pub date_taken: Option>, - pub latitude: Option, - pub longitude: Option, - pub camera_make: Option, - pub camera_model: Option, - pub rating: Option, - - pub created_at: DateTime, - pub updated_at: DateTime, - - // Markdown links - pub links_extracted_at: Option>, -} - -#[derive(Debug, Serialize)] -pub struct CustomFieldResponse { - pub field_type: String, - pub value: String, -} - -#[derive(Debug, Deserialize)] -pub struct ImportRequest { - pub path: PathBuf, -} - -#[derive(Debug, Serialize)] -pub struct ImportResponse { - pub media_id: String, - pub was_duplicate: bool, -} - -#[derive(Debug, Deserialize)] -pub struct UpdateMediaRequest { - pub title: Option, - pub artist: Option, - pub album: Option, - pub genre: Option, - pub year: Option, - pub description: Option, -} - -// File Management -#[derive(Debug, Deserialize)] -pub struct RenameMediaRequest { - pub new_name: String, -} - -#[derive(Debug, Deserialize)] -pub struct MoveMediaRequest { - pub destination: PathBuf, -} - -#[derive(Debug, Deserialize)] -pub struct BatchMoveRequest { - pub media_ids: Vec, - pub destination: PathBuf, -} - -#[derive(Debug, Serialize)] -pub struct TrashResponse { - pub items: Vec, - pub total_count: u64, -} - -#[derive(Debug, Serialize)] -pub struct TrashInfoResponse { - pub count: u64, -} - -#[derive(Debug, Serialize)] -pub struct EmptyTrashResponse { - pub deleted_count: u64, -} - -// Tags -#[derive(Debug, Serialize)] -pub struct TagResponse { - pub id: String, - pub name: String, - pub parent_id: Option, - pub created_at: DateTime, -} - -#[derive(Debug, Deserialize)] -pub struct CreateTagRequest { - pub name: String, - pub parent_id: Option, -} - -#[derive(Debug, Deserialize)] -pub struct TagMediaRequest { - pub tag_id: Uuid, -} - -// Collections -#[derive(Debug, Serialize)] -pub struct CollectionResponse { - pub id: String, - pub name: String, - pub description: Option, - pub kind: String, - pub filter_query: Option, - pub created_at: DateTime, - pub updated_at: DateTime, -} - -#[derive(Debug, Deserialize)] -pub struct CreateCollectionRequest { - pub name: String, - pub kind: String, - pub description: Option, - pub filter_query: Option, -} - -#[derive(Debug, Deserialize)] -pub struct AddMemberRequest { - pub media_id: Uuid, - pub position: Option, -} - -// Search -#[derive(Debug, Deserialize)] -pub struct SearchParams { - pub q: String, - pub sort: Option, - pub offset: Option, - pub limit: Option, -} - -#[derive(Debug, Serialize)] -pub struct SearchResponse { - pub items: Vec, - pub total_count: u64, -} - -// Audit -#[derive(Debug, Serialize)] -pub struct AuditEntryResponse { - pub id: String, - pub media_id: Option, - pub action: String, - pub details: Option, - pub timestamp: DateTime, -} - -// Search (POST body) -#[derive(Debug, Deserialize)] -pub struct SearchRequestBody { - pub q: String, - pub sort: Option, - pub offset: Option, - pub limit: Option, -} - -// Scan -#[derive(Debug, Deserialize)] -pub struct ScanRequest { - pub path: Option, -} - -#[derive(Debug, Serialize)] -pub struct ScanResponse { - pub files_found: usize, - pub files_processed: usize, - pub errors: Vec, -} - -#[derive(Debug, Serialize)] -pub struct ScanJobResponse { - pub job_id: String, -} - -#[derive(Debug, Serialize)] -pub struct ScanStatusResponse { - pub scanning: bool, - pub files_found: usize, - pub files_processed: usize, - pub error_count: usize, - pub errors: Vec, -} - -// Pagination -#[derive(Debug, Deserialize)] -pub struct PaginationParams { - pub offset: Option, - pub limit: Option, - pub sort: Option, -} - -// Open -#[derive(Debug, Deserialize)] -pub struct OpenRequest { - pub media_id: Uuid, -} - -// Config -#[derive(Debug, Serialize)] -pub struct ConfigResponse { - pub backend: String, - pub database_path: Option, - pub roots: Vec, - pub scanning: ScanningConfigResponse, - pub server: ServerConfigResponse, - pub ui: UiConfigResponse, - pub config_path: Option, - pub config_writable: bool, -} - -#[derive(Debug, Serialize)] -pub struct ScanningConfigResponse { - pub watch: bool, - pub poll_interval_secs: u64, - pub ignore_patterns: Vec, -} - -#[derive(Debug, Serialize)] -pub struct ServerConfigResponse { - pub host: String, - pub port: u16, -} - -#[derive(Debug, Deserialize)] -pub struct UpdateScanningRequest { - pub watch: Option, - pub poll_interval_secs: Option, - pub ignore_patterns: Option>, -} - -#[derive(Debug, Deserialize)] -pub struct RootDirRequest { - pub path: String, -} - -// Enhanced Import -#[derive(Debug, Deserialize)] -pub struct ImportWithOptionsRequest { - pub path: PathBuf, - pub tag_ids: Option>, - pub new_tags: Option>, - pub collection_id: Option, -} - -#[derive(Debug, Deserialize)] -pub struct BatchImportRequest { - pub paths: Vec, - pub tag_ids: Option>, - pub new_tags: Option>, - pub collection_id: Option, -} - -#[derive(Debug, Serialize)] -pub struct BatchImportResponse { - pub results: Vec, - pub total: usize, - pub imported: usize, - pub duplicates: usize, - pub errors: usize, -} - -#[derive(Debug, Serialize)] -pub struct BatchImportItemResult { - pub path: String, - pub media_id: Option, - pub was_duplicate: bool, - pub error: Option, -} - -#[derive(Debug, Deserialize)] -pub struct DirectoryImportRequest { - pub path: PathBuf, - pub tag_ids: Option>, - pub new_tags: Option>, - pub collection_id: Option, -} - -#[derive(Debug, Serialize)] -pub struct DirectoryPreviewResponse { - pub files: Vec, - pub total_count: usize, - pub total_size: u64, -} - -#[derive(Debug, Serialize)] -pub struct DirectoryPreviewFile { - pub path: String, - pub file_name: String, - pub media_type: String, - pub file_size: u64, -} - -// Custom Fields -#[derive(Debug, Deserialize)] -pub struct SetCustomFieldRequest { - pub name: String, - pub field_type: String, - pub value: String, -} - -// Media update extended -#[derive(Debug, Deserialize)] -pub struct UpdateMediaFullRequest { - pub title: Option, - pub artist: Option, - pub album: Option, - pub genre: Option, - pub year: Option, - pub description: Option, -} - -// Batch operations -#[derive(Debug, Deserialize)] -pub struct BatchTagRequest { - pub media_ids: Vec, - pub tag_ids: Vec, -} - -#[derive(Debug, Deserialize)] -pub struct BatchCollectionRequest { - pub media_ids: Vec, - pub collection_id: Uuid, -} - -#[derive(Debug, Deserialize)] -pub struct BatchDeleteRequest { - pub media_ids: Vec, -} - -#[derive(Debug, Deserialize)] -pub struct BatchUpdateRequest { - pub media_ids: Vec, - pub title: Option, - pub artist: Option, - pub album: Option, - pub genre: Option, - pub year: Option, - pub description: Option, -} - -#[derive(Debug, Serialize)] -pub struct BatchOperationResponse { - pub processed: usize, - pub errors: Vec, -} - -// Search with sort -#[derive(Debug, Serialize)] -pub struct MediaCountResponse { - pub count: u64, -} - -// Database management -#[derive(Debug, Serialize)] -pub struct DatabaseStatsResponse { - pub media_count: u64, - pub tag_count: u64, - pub collection_count: u64, - pub audit_count: u64, - pub database_size_bytes: u64, - pub backend_name: String, -} - -// UI Config -#[derive(Debug, Serialize, Deserialize, Clone)] -pub struct UiConfigResponse { - pub theme: String, - pub default_view: String, - pub default_page_size: usize, - pub default_view_mode: String, - pub auto_play_media: bool, - pub show_thumbnails: bool, - pub sidebar_collapsed: bool, -} - -#[derive(Debug, Deserialize)] -pub struct UpdateUiConfigRequest { - pub theme: Option, - pub default_view: Option, - pub default_page_size: Option, - pub default_view_mode: Option, - pub auto_play_media: Option, - pub show_thumbnails: Option, - pub sidebar_collapsed: Option, -} - -impl From<&pinakes_core::config::UiConfig> for UiConfigResponse { - fn from(ui: &pinakes_core::config::UiConfig) -> Self { - Self { - theme: ui.theme.clone(), - default_view: ui.default_view.clone(), - default_page_size: ui.default_page_size, - default_view_mode: ui.default_view_mode.clone(), - auto_play_media: ui.auto_play_media, - show_thumbnails: ui.show_thumbnails, - sidebar_collapsed: ui.sidebar_collapsed, - } - } -} - -// Library Statistics -#[derive(Debug, Serialize)] -pub struct LibraryStatisticsResponse { - pub total_media: u64, - pub total_size_bytes: u64, - pub avg_file_size_bytes: u64, - pub media_by_type: Vec, - pub storage_by_type: Vec, - pub newest_item: Option, - pub oldest_item: Option, - pub top_tags: Vec, - pub top_collections: Vec, - pub total_tags: u64, - pub total_collections: u64, - pub total_duplicates: u64, -} - -#[derive(Debug, Serialize)] -pub struct TypeCountResponse { - pub name: String, - pub count: u64, -} - -impl From - for LibraryStatisticsResponse -{ - fn from(stats: pinakes_core::storage::LibraryStatistics) -> Self { - Self { - total_media: stats.total_media, - total_size_bytes: stats.total_size_bytes, - avg_file_size_bytes: stats.avg_file_size_bytes, - media_by_type: stats - .media_by_type - .into_iter() - .map(|(name, count)| TypeCountResponse { name, count }) - .collect(), - storage_by_type: stats - .storage_by_type - .into_iter() - .map(|(name, count)| TypeCountResponse { name, count }) - .collect(), - newest_item: stats.newest_item, - oldest_item: stats.oldest_item, - top_tags: stats - .top_tags - .into_iter() - .map(|(name, count)| TypeCountResponse { name, count }) - .collect(), - top_collections: stats - .top_collections - .into_iter() - .map(|(name, count)| TypeCountResponse { name, count }) - .collect(), - total_tags: stats.total_tags, - total_collections: stats.total_collections, - total_duplicates: stats.total_duplicates, - } - } -} - -// Scheduled Tasks -#[derive(Debug, Serialize)] -pub struct ScheduledTaskResponse { - pub id: String, - pub name: String, - pub schedule: String, - pub enabled: bool, - pub last_run: Option, - pub next_run: Option, - pub last_status: Option, -} - -// Duplicates -#[derive(Debug, Serialize)] -pub struct DuplicateGroupResponse { - pub content_hash: String, - pub items: Vec, -} - -// Auth -#[derive(Debug, Deserialize)] -pub struct LoginRequest { - pub username: String, - pub password: String, -} - -#[derive(Debug, Serialize)] -pub struct LoginResponse { - pub token: String, - pub username: String, - pub role: String, -} - -#[derive(Debug, Serialize)] -pub struct UserInfoResponse { - pub username: String, - pub role: String, -} - -// Conversion helpers -impl From for MediaResponse { - fn from(item: pinakes_core::model::MediaItem) -> Self { - Self { - id: item.id.0.to_string(), - path: item.path.to_string_lossy().to_string(), - file_name: item.file_name, - media_type: serde_json::to_value(item.media_type) - .ok() - .and_then(|v| v.as_str().map(String::from)) - .unwrap_or_default(), - content_hash: item.content_hash.0, - file_size: item.file_size, - title: item.title, - artist: item.artist, - album: item.album, - genre: item.genre, - year: item.year, - duration_secs: item.duration_secs, - description: item.description, - has_thumbnail: item.thumbnail_path.is_some(), - custom_fields: item - .custom_fields - .into_iter() - .map(|(k, v)| { - (k, CustomFieldResponse { - field_type: v.field_type.to_string(), - value: v.value, - }) - }) - .collect(), - - // Photo-specific metadata - date_taken: item.date_taken, - latitude: item.latitude, - longitude: item.longitude, - camera_make: item.camera_make, - camera_model: item.camera_model, - rating: item.rating, - - created_at: item.created_at, - updated_at: item.updated_at, - - // Markdown links - links_extracted_at: item.links_extracted_at, - } - } -} - -impl From for TagResponse { - fn from(tag: pinakes_core::model::Tag) -> Self { - Self { - id: tag.id.to_string(), - name: tag.name, - parent_id: tag.parent_id.map(|id| id.to_string()), - created_at: tag.created_at, - } - } -} - -impl From for CollectionResponse { - fn from(col: pinakes_core::model::Collection) -> Self { - Self { - id: col.id.to_string(), - name: col.name, - description: col.description, - kind: col.kind.to_string(), - filter_query: col.filter_query, - created_at: col.created_at, - updated_at: col.updated_at, - } - } -} - -impl From for AuditEntryResponse { - fn from(entry: pinakes_core::model::AuditEntry) -> Self { - Self { - id: entry.id.to_string(), - media_id: entry.media_id.map(|id| id.0.to_string()), - action: entry.action.to_string(), - details: entry.details, - timestamp: entry.timestamp, - } - } -} - -// Plugins -#[derive(Debug, Serialize)] -pub struct PluginResponse { - pub id: String, - pub name: String, - pub version: String, - pub author: String, - pub description: String, - pub api_version: String, - pub enabled: bool, -} - -#[derive(Debug, Deserialize)] -pub struct InstallPluginRequest { - pub source: String, // URL or file path -} - -#[derive(Debug, Deserialize)] -pub struct TogglePluginRequest { - pub enabled: bool, -} - -impl PluginResponse { - pub fn new(meta: pinakes_plugin_api::PluginMetadata, enabled: bool) -> Self { - Self { - id: meta.id, - name: meta.name, - version: meta.version, - author: meta.author, - description: meta.description, - api_version: meta.api_version, - enabled, - } - } -} - -// Users -#[derive(Debug, Serialize)] -pub struct UserResponse { - pub id: String, - pub username: String, - pub role: String, - pub profile: UserProfileResponse, - pub created_at: DateTime, - pub updated_at: DateTime, -} - -#[derive(Debug, Serialize)] -pub struct UserProfileResponse { - pub avatar_path: Option, - pub bio: Option, - pub preferences: UserPreferencesResponse, -} - -#[derive(Debug, Serialize)] -pub struct UserPreferencesResponse { - pub theme: Option, - pub language: Option, - pub default_video_quality: Option, - pub auto_play: bool, -} - -#[derive(Debug, Serialize)] -pub struct UserLibraryResponse { - pub user_id: String, - pub root_path: String, - pub permission: String, - pub granted_at: DateTime, -} - -#[derive(Debug, Deserialize)] -pub struct GrantLibraryAccessRequest { - pub root_path: String, - pub permission: pinakes_core::users::LibraryPermission, -} - -#[derive(Debug, Deserialize)] -pub struct RevokeLibraryAccessRequest { - pub root_path: String, -} - -impl From for UserResponse { - fn from(user: pinakes_core::users::User) -> Self { - Self { - id: user.id.0.to_string(), - username: user.username, - role: user.role.to_string(), - profile: UserProfileResponse { - avatar_path: user.profile.avatar_path, - bio: user.profile.bio, - preferences: UserPreferencesResponse { - theme: user.profile.preferences.theme, - language: user.profile.preferences.language, - default_video_quality: user.profile.preferences.default_video_quality, - auto_play: user.profile.preferences.auto_play, - }, - }, - created_at: user.created_at, - updated_at: user.updated_at, - } - } -} - -impl From for UserLibraryResponse { - fn from(access: pinakes_core::users::UserLibraryAccess) -> Self { - Self { - user_id: access.user_id.0.to_string(), - root_path: access.root_path, - permission: access.permission.to_string(), - granted_at: access.granted_at, - } - } -} - -#[derive(Debug, Serialize)] -pub struct RatingResponse { - pub id: String, - pub user_id: String, - pub media_id: String, - pub stars: u8, - pub review_text: Option, - pub created_at: DateTime, -} - -impl From for RatingResponse { - fn from(r: pinakes_core::social::Rating) -> Self { - Self { - id: r.id.to_string(), - user_id: r.user_id.0.to_string(), - media_id: r.media_id.0.to_string(), - stars: r.stars, - review_text: r.review_text, - created_at: r.created_at, - } - } -} - -#[derive(Debug, Deserialize)] -pub struct CreateRatingRequest { - pub stars: u8, - pub review_text: Option, -} - -#[derive(Debug, Serialize)] -pub struct CommentResponse { - pub id: String, - pub user_id: String, - pub media_id: String, - pub parent_comment_id: Option, - pub text: String, - pub created_at: DateTime, -} - -impl From for CommentResponse { - fn from(c: pinakes_core::social::Comment) -> Self { - Self { - id: c.id.to_string(), - user_id: c.user_id.0.to_string(), - media_id: c.media_id.0.to_string(), - parent_comment_id: c.parent_comment_id.map(|id| id.to_string()), - text: c.text, - created_at: c.created_at, - } - } -} - -#[derive(Debug, Deserialize)] -pub struct CreateCommentRequest { - pub text: String, - pub parent_id: Option, -} - -#[derive(Debug, Deserialize)] -pub struct FavoriteRequest { - pub media_id: Uuid, -} - -#[derive(Debug, Deserialize)] -pub struct CreateShareLinkRequest { - pub media_id: Uuid, - pub password: Option, - pub expires_in_hours: Option, -} - -#[derive(Debug, Serialize)] -pub struct ShareLinkResponse { - pub id: String, - pub media_id: String, - pub token: String, - pub expires_at: Option>, - pub view_count: u64, - pub created_at: DateTime, -} - -impl From for ShareLinkResponse { - fn from(s: pinakes_core::social::ShareLink) -> Self { - Self { - id: s.id.to_string(), - media_id: s.media_id.0.to_string(), - token: s.token, - expires_at: s.expires_at, - view_count: s.view_count, - created_at: s.created_at, - } - } -} - -#[derive(Debug, Serialize)] -pub struct PlaylistResponse { - pub id: String, - pub owner_id: String, - pub name: String, - pub description: Option, - pub is_public: bool, - pub is_smart: bool, - pub filter_query: Option, - pub created_at: DateTime, - pub updated_at: DateTime, -} - -impl From for PlaylistResponse { - fn from(p: pinakes_core::playlists::Playlist) -> Self { - Self { - id: p.id.to_string(), - owner_id: p.owner_id.0.to_string(), - name: p.name, - description: p.description, - is_public: p.is_public, - is_smart: p.is_smart, - filter_query: p.filter_query, - created_at: p.created_at, - updated_at: p.updated_at, - } - } -} - -#[derive(Debug, Deserialize)] -pub struct CreatePlaylistRequest { - pub name: String, - pub description: Option, - pub is_public: Option, - pub is_smart: Option, - pub filter_query: Option, -} - -#[derive(Debug, Deserialize)] -pub struct UpdatePlaylistRequest { - pub name: Option, - pub description: Option, - pub is_public: Option, -} - -#[derive(Debug, Deserialize)] -pub struct PlaylistItemRequest { - pub media_id: Uuid, - pub position: Option, -} - -#[derive(Debug, Deserialize)] -pub struct ReorderPlaylistRequest { - pub media_id: Uuid, - pub new_position: i32, -} - -#[derive(Debug, Serialize)] -pub struct UsageEventResponse { - pub id: String, - pub media_id: Option, - pub user_id: Option, - pub event_type: String, - pub timestamp: DateTime, - pub duration_secs: Option, -} - -impl From for UsageEventResponse { - fn from(e: pinakes_core::analytics::UsageEvent) -> Self { - Self { - id: e.id.to_string(), - media_id: e.media_id.map(|m| m.0.to_string()), - user_id: e.user_id.map(|u| u.0.to_string()), - event_type: e.event_type.to_string(), - timestamp: e.timestamp, - duration_secs: e.duration_secs, - } - } -} - -#[derive(Debug, Deserialize)] -pub struct RecordUsageEventRequest { - pub media_id: Option, - pub event_type: String, - pub duration_secs: Option, - pub context: Option, -} - -#[derive(Debug, Serialize)] -pub struct MostViewedResponse { - pub media: MediaResponse, - pub view_count: u64, -} - -#[derive(Debug, Deserialize)] -pub struct WatchProgressRequest { - pub progress_secs: f64, -} - -#[derive(Debug, Serialize)] -pub struct WatchProgressResponse { - pub progress_secs: f64, -} - -#[derive(Debug, Serialize)] -pub struct SubtitleResponse { - pub id: String, - pub media_id: String, - pub language: Option, - pub format: String, - pub is_embedded: bool, - pub track_index: Option, - pub offset_ms: i64, - pub created_at: DateTime, -} - -impl From for SubtitleResponse { - fn from(s: pinakes_core::subtitles::Subtitle) -> Self { - Self { - id: s.id.to_string(), - media_id: s.media_id.0.to_string(), - language: s.language, - format: s.format.to_string(), - is_embedded: s.is_embedded, - track_index: s.track_index, - offset_ms: s.offset_ms, - created_at: s.created_at, - } - } -} - -#[derive(Debug, Deserialize)] -pub struct AddSubtitleRequest { - pub language: Option, - pub format: String, - pub file_path: Option, - pub is_embedded: Option, - pub track_index: Option, - pub offset_ms: Option, -} - -#[derive(Debug, Deserialize)] -pub struct UpdateSubtitleOffsetRequest { - pub offset_ms: i64, -} - -#[derive(Debug, Serialize)] -pub struct ExternalMetadataResponse { - pub id: String, - pub media_id: String, - pub source: String, - pub external_id: Option, - pub metadata: serde_json::Value, - pub confidence: f64, - pub last_updated: DateTime, -} - -impl From - for ExternalMetadataResponse -{ - fn from(m: pinakes_core::enrichment::ExternalMetadata) -> Self { - let metadata = serde_json::from_str(&m.metadata_json).unwrap_or_else(|e| { - tracing::warn!( - "failed to deserialize external metadata JSON for media {}: {}", - m.media_id.0, - e - ); - serde_json::Value::Null - }); - Self { - id: m.id.to_string(), - media_id: m.media_id.0.to_string(), - source: m.source.to_string(), - external_id: m.external_id, - metadata, - confidence: m.confidence, - last_updated: m.last_updated, - } - } -} - -#[derive(Debug, Serialize)] -pub struct TranscodeSessionResponse { - pub id: String, - pub media_id: String, - pub profile: String, - pub status: String, - pub progress: f32, - pub created_at: DateTime, - pub expires_at: Option>, -} - -impl From - for TranscodeSessionResponse -{ - fn from(s: pinakes_core::transcode::TranscodeSession) -> Self { - Self { - id: s.id.to_string(), - media_id: s.media_id.0.to_string(), - profile: s.profile, - status: s.status.as_str().to_string(), - progress: s.progress, - created_at: s.created_at, - expires_at: s.expires_at, - } - } -} - -#[derive(Debug, Deserialize)] -pub struct CreateTranscodeRequest { - pub profile: String, -} - -#[derive(Debug, Serialize)] -pub struct UploadResponse { - pub media_id: String, - pub content_hash: String, - pub was_duplicate: bool, - pub file_size: u64, -} - -impl From for UploadResponse { - fn from(result: pinakes_core::model::UploadResult) -> Self { - Self { - media_id: result.media_id.0.to_string(), - content_hash: result.content_hash.0, - was_duplicate: result.was_duplicate, - file_size: result.file_size, - } - } -} - -#[derive(Debug, Serialize)] -pub struct ManagedStorageStatsResponse { - pub total_blobs: u64, - pub total_size_bytes: u64, - pub orphaned_blobs: u64, - pub deduplication_ratio: f64, -} - -impl From - for ManagedStorageStatsResponse -{ - fn from(stats: pinakes_core::model::ManagedStorageStats) -> Self { - Self { - total_blobs: stats.total_blobs, - total_size_bytes: stats.total_size_bytes, - orphaned_blobs: stats.orphaned_blobs, - deduplication_ratio: stats.deduplication_ratio, - } - } -} - -#[derive(Debug, Deserialize)] -pub struct RegisterDeviceRequest { - pub name: String, - pub device_type: String, - pub client_version: String, - pub os_info: Option, -} - -#[derive(Debug, Serialize)] -pub struct DeviceResponse { - pub id: String, - pub name: String, - pub device_type: String, - pub client_version: String, - pub os_info: Option, - pub last_sync_at: Option>, - pub last_seen_at: DateTime, - pub sync_cursor: Option, - pub enabled: bool, - pub created_at: DateTime, -} - -impl From for DeviceResponse { - fn from(d: pinakes_core::sync::SyncDevice) -> Self { - Self { - id: d.id.0.to_string(), - name: d.name, - device_type: d.device_type.to_string(), - client_version: d.client_version, - os_info: d.os_info, - last_sync_at: d.last_sync_at, - last_seen_at: d.last_seen_at, - sync_cursor: d.sync_cursor, - enabled: d.enabled, - created_at: d.created_at, - } - } -} - -#[derive(Debug, Serialize)] -pub struct DeviceRegistrationResponse { - pub device: DeviceResponse, - pub device_token: String, -} - -#[derive(Debug, Deserialize)] -pub struct UpdateDeviceRequest { - pub name: Option, - pub enabled: Option, -} - -#[derive(Debug, Deserialize)] -pub struct GetChangesParams { - pub cursor: Option, - pub limit: Option, -} - -#[derive(Debug, Serialize)] -pub struct SyncChangeResponse { - pub id: String, - pub sequence: i64, - pub change_type: String, - pub media_id: Option, - pub path: String, - pub content_hash: Option, - pub file_size: Option, - pub timestamp: DateTime, -} - -impl From for SyncChangeResponse { - fn from(e: pinakes_core::sync::SyncLogEntry) -> Self { - Self { - id: e.id.to_string(), - sequence: e.sequence, - change_type: e.change_type.to_string(), - media_id: e.media_id.map(|id| id.0.to_string()), - path: e.path, - content_hash: e.content_hash.map(|h| h.0), - file_size: e.file_size, - timestamp: e.timestamp, - } - } -} - -#[derive(Debug, Serialize)] -pub struct ChangesResponse { - pub changes: Vec, - pub cursor: i64, - pub has_more: bool, -} - -#[derive(Debug, Deserialize)] -pub struct ClientChangeReport { - pub path: String, - pub change_type: String, - pub content_hash: Option, - pub file_size: Option, - pub local_mtime: Option, -} - -#[derive(Debug, Deserialize)] -pub struct ReportChangesRequest { - pub changes: Vec, -} - -#[derive(Debug, Serialize)] -pub struct ReportChangesResponse { - pub accepted: Vec, - pub conflicts: Vec, - pub upload_required: Vec, -} - -#[derive(Debug, Serialize)] -pub struct ConflictResponse { - pub id: String, - pub path: String, - pub local_hash: String, - pub server_hash: String, - pub detected_at: DateTime, -} - -impl From for ConflictResponse { - fn from(c: pinakes_core::sync::SyncConflict) -> Self { - Self { - id: c.id.to_string(), - path: c.path, - local_hash: c.local_hash, - server_hash: c.server_hash, - detected_at: c.detected_at, - } - } -} - -#[derive(Debug, Deserialize)] -pub struct ResolveConflictRequest { - pub resolution: String, -} - -#[derive(Debug, Deserialize)] -pub struct CreateUploadSessionRequest { - pub target_path: String, - pub expected_hash: String, - pub expected_size: u64, - pub chunk_size: Option, -} - -#[derive(Debug, Serialize)] -pub struct UploadSessionResponse { - pub id: String, - pub target_path: String, - pub expected_hash: String, - pub expected_size: u64, - pub chunk_size: u64, - pub chunk_count: u64, - pub status: String, - pub created_at: DateTime, - pub expires_at: DateTime, -} - -impl From for UploadSessionResponse { - fn from(s: pinakes_core::sync::UploadSession) -> Self { - Self { - id: s.id.to_string(), - target_path: s.target_path, - expected_hash: s.expected_hash.0, - expected_size: s.expected_size, - chunk_size: s.chunk_size, - chunk_count: s.chunk_count, - status: s.status.to_string(), - created_at: s.created_at, - expires_at: s.expires_at, - } - } -} - -#[derive(Debug, Serialize)] -pub struct ChunkUploadedResponse { - pub chunk_index: u64, - pub received: bool, -} - -#[derive(Debug, Deserialize)] -pub struct AcknowledgeChangesRequest { - pub cursor: i64, -} - -#[derive(Debug, Deserialize)] -pub struct CreateShareRequest { - pub target_type: String, - pub target_id: String, - pub recipient_type: String, - pub recipient_user_id: Option, - pub recipient_group_id: Option, - pub password: Option, - pub permissions: Option, - pub note: Option, - pub expires_in_hours: Option, - pub inherit_to_children: Option, -} - -#[derive(Debug, Deserialize)] -pub struct SharePermissionsRequest { - pub can_view: Option, - pub can_download: Option, - pub can_edit: Option, - pub can_delete: Option, - pub can_reshare: Option, - pub can_add: Option, -} - -#[derive(Debug, Serialize)] -pub struct ShareResponse { - pub id: String, - pub target_type: String, - pub target_id: String, - pub owner_id: String, - pub recipient_type: String, - pub recipient_user_id: Option, - pub recipient_group_id: Option, - pub public_token: Option, - pub permissions: SharePermissionsResponse, - pub note: Option, - pub expires_at: Option>, - pub access_count: u64, - pub last_accessed: Option>, - pub inherit_to_children: bool, - pub created_at: DateTime, - pub updated_at: DateTime, -} - -#[derive(Debug, Serialize)] -pub struct SharePermissionsResponse { - pub can_view: bool, - pub can_download: bool, - pub can_edit: bool, - pub can_delete: bool, - pub can_reshare: bool, - pub can_add: bool, -} - -impl From - for SharePermissionsResponse -{ - fn from(p: pinakes_core::sharing::SharePermissions) -> Self { - Self { - can_view: p.can_view, - can_download: p.can_download, - can_edit: p.can_edit, - can_delete: p.can_delete, - can_reshare: p.can_reshare, - can_add: p.can_add, - } - } -} - -impl From for ShareResponse { - fn from(s: pinakes_core::sharing::Share) -> Self { - let (target_type, target_id) = match &s.target { - pinakes_core::sharing::ShareTarget::Media { media_id } => { - ("media".to_string(), media_id.0.to_string()) - }, - pinakes_core::sharing::ShareTarget::Collection { collection_id } => { - ("collection".to_string(), collection_id.to_string()) - }, - pinakes_core::sharing::ShareTarget::Tag { tag_id } => { - ("tag".to_string(), tag_id.to_string()) - }, - pinakes_core::sharing::ShareTarget::SavedSearch { search_id } => { - ("saved_search".to_string(), search_id.to_string()) - }, - }; - - let (recipient_type, recipient_user_id, recipient_group_id, public_token) = - match &s.recipient { - pinakes_core::sharing::ShareRecipient::PublicLink { token, .. } => { - ("public_link".to_string(), None, None, Some(token.clone())) - }, - pinakes_core::sharing::ShareRecipient::User { user_id } => { - ("user".to_string(), Some(user_id.0.to_string()), None, None) - }, - pinakes_core::sharing::ShareRecipient::Group { group_id } => { - ("group".to_string(), None, Some(group_id.to_string()), None) - }, - pinakes_core::sharing::ShareRecipient::Federated { .. } => { - ("federated".to_string(), None, None, None) - }, - }; - - Self { - id: s.id.0.to_string(), - target_type, - target_id, - owner_id: s.owner_id.0.to_string(), - recipient_type, - recipient_user_id, - recipient_group_id, - public_token, - permissions: s.permissions.into(), - note: s.note, - expires_at: s.expires_at, - access_count: s.access_count, - last_accessed: s.last_accessed, - inherit_to_children: s.inherit_to_children, - created_at: s.created_at, - updated_at: s.updated_at, - } - } -} - -#[derive(Debug, Deserialize)] -pub struct UpdateShareRequest { - pub permissions: Option, - pub note: Option, - pub expires_at: Option>, - pub inherit_to_children: Option, -} - -#[derive(Debug, Serialize)] -pub struct ShareActivityResponse { - pub id: String, - pub share_id: String, - pub actor_id: Option, - pub actor_ip: Option, - pub action: String, - pub details: Option, - pub timestamp: DateTime, -} - -impl From for ShareActivityResponse { - fn from(a: pinakes_core::sharing::ShareActivity) -> Self { - Self { - id: a.id.to_string(), - share_id: a.share_id.0.to_string(), - actor_id: a.actor_id.map(|id| id.0.to_string()), - actor_ip: a.actor_ip, - action: a.action.to_string(), - details: a.details, - timestamp: a.timestamp, - } - } -} - -#[derive(Debug, Serialize)] -pub struct ShareNotificationResponse { - pub id: String, - pub share_id: String, - pub notification_type: String, - pub is_read: bool, - pub created_at: DateTime, -} - -impl From - for ShareNotificationResponse -{ - fn from(n: pinakes_core::sharing::ShareNotification) -> Self { - Self { - id: n.id.to_string(), - share_id: n.share_id.0.to_string(), - notification_type: n.notification_type.to_string(), - is_read: n.is_read, - created_at: n.created_at, - } - } -} - -#[derive(Debug, Deserialize)] -pub struct BatchDeleteSharesRequest { - pub share_ids: Vec, -} - -#[derive(Debug, Deserialize)] -pub struct AccessSharedRequest { - pub password: Option, -} diff --git a/crates/pinakes-server/src/dto/analytics.rs b/crates/pinakes-server/src/dto/analytics.rs new file mode 100644 index 0000000..c68289d --- /dev/null +++ b/crates/pinakes-server/src/dto/analytics.rs @@ -0,0 +1,34 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Debug, Serialize)] +pub struct UsageEventResponse { + pub id: String, + pub media_id: Option, + pub user_id: Option, + pub event_type: String, + pub timestamp: DateTime, + pub duration_secs: Option, +} + +impl From for UsageEventResponse { + fn from(e: pinakes_core::analytics::UsageEvent) -> Self { + Self { + id: e.id.to_string(), + media_id: e.media_id.map(|m| m.0.to_string()), + user_id: e.user_id.map(|u| u.0.to_string()), + event_type: e.event_type.to_string(), + timestamp: e.timestamp, + duration_secs: e.duration_secs, + } + } +} + +#[derive(Debug, Deserialize)] +pub struct RecordUsageEventRequest { + pub media_id: Option, + pub event_type: String, + pub duration_secs: Option, + pub context: Option, +} diff --git a/crates/pinakes-server/src/dto/audit.rs b/crates/pinakes-server/src/dto/audit.rs new file mode 100644 index 0000000..7a71fd0 --- /dev/null +++ b/crates/pinakes-server/src/dto/audit.rs @@ -0,0 +1,23 @@ +use chrono::{DateTime, Utc}; +use serde::Serialize; + +#[derive(Debug, Serialize)] +pub struct AuditEntryResponse { + pub id: String, + pub media_id: Option, + pub action: String, + pub details: Option, + pub timestamp: DateTime, +} + +impl From for AuditEntryResponse { + fn from(entry: pinakes_core::model::AuditEntry) -> Self { + Self { + id: entry.id.to_string(), + media_id: entry.media_id.map(|id| id.0.to_string()), + action: entry.action.to_string(), + details: entry.details, + timestamp: entry.timestamp, + } + } +} diff --git a/crates/pinakes-server/src/dto/batch.rs b/crates/pinakes-server/src/dto/batch.rs new file mode 100644 index 0000000..0389762 --- /dev/null +++ b/crates/pinakes-server/src/dto/batch.rs @@ -0,0 +1,36 @@ +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Debug, Deserialize)] +pub struct BatchTagRequest { + pub media_ids: Vec, + pub tag_ids: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct BatchCollectionRequest { + pub media_ids: Vec, + pub collection_id: Uuid, +} + +#[derive(Debug, Deserialize)] +pub struct BatchDeleteRequest { + pub media_ids: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct BatchUpdateRequest { + pub media_ids: Vec, + pub title: Option, + pub artist: Option, + pub album: Option, + pub genre: Option, + pub year: Option, + pub description: Option, +} + +#[derive(Debug, Serialize)] +pub struct BatchOperationResponse { + pub processed: usize, + pub errors: Vec, +} diff --git a/crates/pinakes-server/src/dto/collections.rs b/crates/pinakes-server/src/dto/collections.rs new file mode 100644 index 0000000..04adcdb --- /dev/null +++ b/crates/pinakes-server/src/dto/collections.rs @@ -0,0 +1,42 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Debug, Serialize)] +pub struct CollectionResponse { + pub id: String, + pub name: String, + pub description: Option, + pub kind: String, + pub filter_query: Option, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(Debug, Deserialize)] +pub struct CreateCollectionRequest { + pub name: String, + pub kind: String, + pub description: Option, + pub filter_query: Option, +} + +#[derive(Debug, Deserialize)] +pub struct AddMemberRequest { + pub media_id: Uuid, + pub position: Option, +} + +impl From for CollectionResponse { + fn from(col: pinakes_core::model::Collection) -> Self { + Self { + id: col.id.to_string(), + name: col.name, + description: col.description, + kind: col.kind.to_string(), + filter_query: col.filter_query, + created_at: col.created_at, + updated_at: col.updated_at, + } + } +} diff --git a/crates/pinakes-server/src/dto/config.rs b/crates/pinakes-server/src/dto/config.rs new file mode 100644 index 0000000..024c683 --- /dev/null +++ b/crates/pinakes-server/src/dto/config.rs @@ -0,0 +1,75 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize)] +pub struct ConfigResponse { + pub backend: String, + pub database_path: Option, + pub roots: Vec, + pub scanning: ScanningConfigResponse, + pub server: ServerConfigResponse, + pub ui: UiConfigResponse, + pub config_path: Option, + pub config_writable: bool, +} + +#[derive(Debug, Serialize)] +pub struct ScanningConfigResponse { + pub watch: bool, + pub poll_interval_secs: u64, + pub ignore_patterns: Vec, +} + +#[derive(Debug, Serialize)] +pub struct ServerConfigResponse { + pub host: String, + pub port: u16, +} + +#[derive(Debug, Deserialize)] +pub struct UpdateScanningRequest { + pub watch: Option, + pub poll_interval_secs: Option, + pub ignore_patterns: Option>, +} + +#[derive(Debug, Deserialize)] +pub struct RootDirRequest { + pub path: String, +} + +// UI Config +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct UiConfigResponse { + pub theme: String, + pub default_view: String, + pub default_page_size: usize, + pub default_view_mode: String, + pub auto_play_media: bool, + pub show_thumbnails: bool, + pub sidebar_collapsed: bool, +} + +#[derive(Debug, Deserialize)] +pub struct UpdateUiConfigRequest { + pub theme: Option, + pub default_view: Option, + pub default_page_size: Option, + pub default_view_mode: Option, + pub auto_play_media: Option, + pub show_thumbnails: Option, + pub sidebar_collapsed: Option, +} + +impl From<&pinakes_core::config::UiConfig> for UiConfigResponse { + fn from(ui: &pinakes_core::config::UiConfig) -> Self { + Self { + theme: ui.theme.clone(), + default_view: ui.default_view.clone(), + default_page_size: ui.default_page_size, + default_view_mode: ui.default_view_mode.clone(), + auto_play_media: ui.auto_play_media, + show_thumbnails: ui.show_thumbnails, + sidebar_collapsed: ui.sidebar_collapsed, + } + } +} diff --git a/crates/pinakes-server/src/dto/enrichment.rs b/crates/pinakes-server/src/dto/enrichment.rs new file mode 100644 index 0000000..c1ec7b0 --- /dev/null +++ b/crates/pinakes-server/src/dto/enrichment.rs @@ -0,0 +1,37 @@ +use chrono::{DateTime, Utc}; +use serde::Serialize; + +#[derive(Debug, Serialize)] +pub struct ExternalMetadataResponse { + pub id: String, + pub media_id: String, + pub source: String, + pub external_id: Option, + pub metadata: serde_json::Value, + pub confidence: f64, + pub last_updated: DateTime, +} + +impl From + for ExternalMetadataResponse +{ + fn from(m: pinakes_core::enrichment::ExternalMetadata) -> Self { + let metadata = serde_json::from_str(&m.metadata_json).unwrap_or_else(|e| { + tracing::warn!( + "failed to deserialize external metadata JSON for media {}: {}", + m.media_id.0, + e + ); + serde_json::Value::Null + }); + Self { + id: m.id.to_string(), + media_id: m.media_id.0.to_string(), + source: m.source.to_string(), + external_id: m.external_id, + metadata, + confidence: m.confidence, + last_updated: m.last_updated, + } + } +} diff --git a/crates/pinakes-server/src/dto/media.rs b/crates/pinakes-server/src/dto/media.rs new file mode 100644 index 0000000..800f951 --- /dev/null +++ b/crates/pinakes-server/src/dto/media.rs @@ -0,0 +1,294 @@ +use std::{collections::HashMap, path::PathBuf}; + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Debug, Serialize)] +pub struct MediaResponse { + pub id: String, + pub path: String, + pub file_name: String, + pub media_type: String, + pub content_hash: String, + pub file_size: u64, + pub title: Option, + pub artist: Option, + pub album: Option, + pub genre: Option, + pub year: Option, + pub duration_secs: Option, + pub description: Option, + pub has_thumbnail: bool, + pub custom_fields: HashMap, + + // Photo-specific metadata + pub date_taken: Option>, + pub latitude: Option, + pub longitude: Option, + pub camera_make: Option, + pub camera_model: Option, + pub rating: Option, + + pub created_at: DateTime, + pub updated_at: DateTime, + + // Markdown links + pub links_extracted_at: Option>, +} + +#[derive(Debug, Serialize)] +pub struct CustomFieldResponse { + pub field_type: String, + pub value: String, +} + +#[derive(Debug, Deserialize)] +pub struct ImportRequest { + pub path: PathBuf, +} + +#[derive(Debug, Serialize)] +pub struct ImportResponse { + pub media_id: String, + pub was_duplicate: bool, +} + +#[derive(Debug, Deserialize)] +pub struct UpdateMediaRequest { + pub title: Option, + pub artist: Option, + pub album: Option, + pub genre: Option, + pub year: Option, + pub description: Option, +} + +// File Management +#[derive(Debug, Deserialize)] +pub struct RenameMediaRequest { + pub new_name: String, +} + +#[derive(Debug, Deserialize)] +pub struct MoveMediaRequest { + pub destination: PathBuf, +} + +#[derive(Debug, Deserialize)] +pub struct BatchMoveRequest { + pub media_ids: Vec, + pub destination: PathBuf, +} + +#[derive(Debug, Serialize)] +pub struct TrashResponse { + pub items: Vec, + pub total_count: u64, +} + +#[derive(Debug, Serialize)] +pub struct TrashInfoResponse { + pub count: u64, +} + +#[derive(Debug, Serialize)] +pub struct EmptyTrashResponse { + pub deleted_count: u64, +} + +// Enhanced Import +#[derive(Debug, Deserialize)] +pub struct ImportWithOptionsRequest { + pub path: PathBuf, + pub tag_ids: Option>, + pub new_tags: Option>, + pub collection_id: Option, +} + +#[derive(Debug, Deserialize)] +pub struct BatchImportRequest { + pub paths: Vec, + pub tag_ids: Option>, + pub new_tags: Option>, + pub collection_id: Option, +} + +#[derive(Debug, Serialize)] +pub struct BatchImportResponse { + pub results: Vec, + pub total: usize, + pub imported: usize, + pub duplicates: usize, + pub errors: usize, +} + +#[derive(Debug, Serialize)] +pub struct BatchImportItemResult { + pub path: String, + pub media_id: Option, + pub was_duplicate: bool, + pub error: Option, +} + +#[derive(Debug, Deserialize)] +pub struct DirectoryImportRequest { + pub path: PathBuf, + pub tag_ids: Option>, + pub new_tags: Option>, + pub collection_id: Option, +} + +#[derive(Debug, Serialize)] +pub struct DirectoryPreviewResponse { + pub files: Vec, + pub total_count: usize, + pub total_size: u64, +} + +#[derive(Debug, Serialize)] +pub struct DirectoryPreviewFile { + pub path: String, + pub file_name: String, + pub media_type: String, + pub file_size: u64, +} + +// Custom Fields +#[derive(Debug, Deserialize)] +pub struct SetCustomFieldRequest { + pub name: String, + pub field_type: String, + pub value: String, +} + +// Media update extended +#[derive(Debug, Deserialize)] +pub struct UpdateMediaFullRequest { + pub title: Option, + pub artist: Option, + pub album: Option, + pub genre: Option, + pub year: Option, + pub description: Option, +} + +// Search with sort +#[derive(Debug, Serialize)] +pub struct MediaCountResponse { + pub count: u64, +} + +// Duplicates +#[derive(Debug, Serialize)] +pub struct DuplicateGroupResponse { + pub content_hash: String, + pub items: Vec, +} + +// Open +#[derive(Debug, Deserialize)] +pub struct OpenRequest { + pub media_id: Uuid, +} + +// Upload +#[derive(Debug, Serialize)] +pub struct UploadResponse { + pub media_id: String, + pub content_hash: String, + pub was_duplicate: bool, + pub file_size: u64, +} + +impl From for UploadResponse { + fn from(result: pinakes_core::model::UploadResult) -> Self { + Self { + media_id: result.media_id.0.to_string(), + content_hash: result.content_hash.0, + was_duplicate: result.was_duplicate, + file_size: result.file_size, + } + } +} + +#[derive(Debug, Serialize)] +pub struct ManagedStorageStatsResponse { + pub total_blobs: u64, + pub total_size_bytes: u64, + pub orphaned_blobs: u64, + pub deduplication_ratio: f64, +} + +impl From + for ManagedStorageStatsResponse +{ + fn from(stats: pinakes_core::model::ManagedStorageStats) -> Self { + Self { + total_blobs: stats.total_blobs, + total_size_bytes: stats.total_size_bytes, + orphaned_blobs: stats.orphaned_blobs, + deduplication_ratio: stats.deduplication_ratio, + } + } +} + +// Conversion helpers +impl From for MediaResponse { + fn from(item: pinakes_core::model::MediaItem) -> Self { + Self { + id: item.id.0.to_string(), + path: item.path.to_string_lossy().to_string(), + file_name: item.file_name, + media_type: serde_json::to_value(item.media_type) + .ok() + .and_then(|v| v.as_str().map(String::from)) + .unwrap_or_default(), + content_hash: item.content_hash.0, + file_size: item.file_size, + title: item.title, + artist: item.artist, + album: item.album, + genre: item.genre, + year: item.year, + duration_secs: item.duration_secs, + description: item.description, + has_thumbnail: item.thumbnail_path.is_some(), + custom_fields: item + .custom_fields + .into_iter() + .map(|(k, v)| { + (k, CustomFieldResponse { + field_type: v.field_type.to_string(), + value: v.value, + }) + }) + .collect(), + + // Photo-specific metadata + date_taken: item.date_taken, + latitude: item.latitude, + longitude: item.longitude, + camera_make: item.camera_make, + camera_model: item.camera_model, + rating: item.rating, + + created_at: item.created_at, + updated_at: item.updated_at, + + // Markdown links + links_extracted_at: item.links_extracted_at, + } + } +} + +// Watch progress +#[derive(Debug, Deserialize)] +pub struct WatchProgressRequest { + pub progress_secs: f64, +} + +#[derive(Debug, Serialize)] +pub struct WatchProgressResponse { + pub progress_secs: f64, +} diff --git a/crates/pinakes-server/src/dto/mod.rs b/crates/pinakes-server/src/dto/mod.rs new file mode 100644 index 0000000..4f1f1c8 --- /dev/null +++ b/crates/pinakes-server/src/dto/mod.rs @@ -0,0 +1,39 @@ +mod analytics; +mod audit; +mod batch; +mod collections; +mod config; +mod enrichment; +mod media; +mod playlists; +mod plugins; +mod scan; +mod search; +mod sharing; +mod social; +mod statistics; +mod subtitles; +mod sync; +mod tags; +mod transcode; +mod users; + +pub use analytics::*; +pub use audit::*; +pub use batch::*; +pub use collections::*; +pub use config::*; +pub use enrichment::*; +pub use media::*; +pub use playlists::*; +pub use plugins::*; +pub use scan::*; +pub use search::*; +pub use sharing::*; +pub use social::*; +pub use statistics::*; +pub use subtitles::*; +pub use sync::*; +pub use tags::*; +pub use transcode::*; +pub use users::*; diff --git a/crates/pinakes-server/src/dto/playlists.rs b/crates/pinakes-server/src/dto/playlists.rs new file mode 100644 index 0000000..af387b1 --- /dev/null +++ b/crates/pinakes-server/src/dto/playlists.rs @@ -0,0 +1,60 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Debug, Serialize)] +pub struct PlaylistResponse { + pub id: String, + pub owner_id: String, + pub name: String, + pub description: Option, + pub is_public: bool, + pub is_smart: bool, + pub filter_query: Option, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +impl From for PlaylistResponse { + fn from(p: pinakes_core::playlists::Playlist) -> Self { + Self { + id: p.id.to_string(), + owner_id: p.owner_id.0.to_string(), + name: p.name, + description: p.description, + is_public: p.is_public, + is_smart: p.is_smart, + filter_query: p.filter_query, + created_at: p.created_at, + updated_at: p.updated_at, + } + } +} + +#[derive(Debug, Deserialize)] +pub struct CreatePlaylistRequest { + pub name: String, + pub description: Option, + pub is_public: Option, + pub is_smart: Option, + pub filter_query: Option, +} + +#[derive(Debug, Deserialize)] +pub struct UpdatePlaylistRequest { + pub name: Option, + pub description: Option, + pub is_public: Option, +} + +#[derive(Debug, Deserialize)] +pub struct PlaylistItemRequest { + pub media_id: Uuid, + pub position: Option, +} + +#[derive(Debug, Deserialize)] +pub struct ReorderPlaylistRequest { + pub media_id: Uuid, + pub new_position: i32, +} diff --git a/crates/pinakes-server/src/dto/plugins.rs b/crates/pinakes-server/src/dto/plugins.rs new file mode 100644 index 0000000..7872217 --- /dev/null +++ b/crates/pinakes-server/src/dto/plugins.rs @@ -0,0 +1,37 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize)] +pub struct PluginResponse { + pub id: String, + pub name: String, + pub version: String, + pub author: String, + pub description: String, + pub api_version: String, + pub enabled: bool, +} + +#[derive(Debug, Deserialize)] +pub struct InstallPluginRequest { + pub source: String, // URL or file path +} + +#[derive(Debug, Deserialize)] +pub struct TogglePluginRequest { + pub enabled: bool, +} + +impl PluginResponse { + #[must_use] + pub fn new(meta: pinakes_plugin_api::PluginMetadata, enabled: bool) -> Self { + Self { + id: meta.id, + name: meta.name, + version: meta.version, + author: meta.author, + description: meta.description, + api_version: meta.api_version, + enabled, + } + } +} diff --git a/crates/pinakes-server/src/dto/scan.rs b/crates/pinakes-server/src/dto/scan.rs new file mode 100644 index 0000000..86c4805 --- /dev/null +++ b/crates/pinakes-server/src/dto/scan.rs @@ -0,0 +1,29 @@ +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Deserialize)] +pub struct ScanRequest { + pub path: Option, +} + +#[derive(Debug, Serialize)] +pub struct ScanResponse { + pub files_found: usize, + pub files_processed: usize, + pub errors: Vec, +} + +#[derive(Debug, Serialize)] +pub struct ScanJobResponse { + pub job_id: String, +} + +#[derive(Debug, Serialize)] +pub struct ScanStatusResponse { + pub scanning: bool, + pub files_found: usize, + pub files_processed: usize, + pub error_count: usize, + pub errors: Vec, +} diff --git a/crates/pinakes-server/src/dto/search.rs b/crates/pinakes-server/src/dto/search.rs new file mode 100644 index 0000000..eb9260e --- /dev/null +++ b/crates/pinakes-server/src/dto/search.rs @@ -0,0 +1,34 @@ +use serde::{Deserialize, Serialize}; + +use super::media::MediaResponse; + +#[derive(Debug, Deserialize)] +pub struct SearchParams { + pub q: String, + pub sort: Option, + pub offset: Option, + pub limit: Option, +} + +#[derive(Debug, Serialize)] +pub struct SearchResponse { + pub items: Vec, + pub total_count: u64, +} + +// Search (POST body) +#[derive(Debug, Deserialize)] +pub struct SearchRequestBody { + pub q: String, + pub sort: Option, + pub offset: Option, + pub limit: Option, +} + +// Pagination +#[derive(Debug, Deserialize)] +pub struct PaginationParams { + pub offset: Option, + pub limit: Option, + pub sort: Option, +} diff --git a/crates/pinakes-server/src/dto/sharing.rs b/crates/pinakes-server/src/dto/sharing.rs new file mode 100644 index 0000000..60f60ab --- /dev/null +++ b/crates/pinakes-server/src/dto/sharing.rs @@ -0,0 +1,202 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Debug, Deserialize)] +pub struct CreateShareRequest { + pub target_type: String, + pub target_id: String, + pub recipient_type: String, + pub recipient_user_id: Option, + pub recipient_group_id: Option, + pub password: Option, + pub permissions: Option, + pub note: Option, + pub expires_in_hours: Option, + pub inherit_to_children: Option, +} + +#[derive(Debug, Deserialize)] +pub struct SharePermissionsRequest { + pub can_view: Option, + pub can_download: Option, + pub can_edit: Option, + pub can_delete: Option, + pub can_reshare: Option, + pub can_add: Option, +} + +#[derive(Debug, Serialize)] +pub struct ShareResponse { + pub id: String, + pub target_type: String, + pub target_id: String, + pub owner_id: String, + pub recipient_type: String, + pub recipient_user_id: Option, + pub recipient_group_id: Option, + pub public_token: Option, + pub permissions: SharePermissionsResponse, + pub note: Option, + pub expires_at: Option>, + pub access_count: u64, + pub last_accessed: Option>, + pub inherit_to_children: bool, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(Debug, Serialize)] +pub struct SharePermissionsResponse { + pub can_view: bool, + pub can_download: bool, + pub can_edit: bool, + pub can_delete: bool, + pub can_reshare: bool, + pub can_add: bool, +} + +impl From + for SharePermissionsResponse +{ + fn from(p: pinakes_core::sharing::SharePermissions) -> Self { + Self { + can_view: p.view.can_view, + can_download: p.view.can_download, + can_edit: p.mutate.can_edit, + can_delete: p.mutate.can_delete, + can_reshare: p.view.can_reshare, + can_add: p.mutate.can_add, + } + } +} + +impl From for ShareResponse { + fn from(s: pinakes_core::sharing::Share) -> Self { + let (target_type, target_id) = match &s.target { + pinakes_core::sharing::ShareTarget::Media { media_id } => { + ("media".to_string(), media_id.0.to_string()) + }, + pinakes_core::sharing::ShareTarget::Collection { collection_id } => { + ("collection".to_string(), collection_id.to_string()) + }, + pinakes_core::sharing::ShareTarget::Tag { tag_id } => { + ("tag".to_string(), tag_id.to_string()) + }, + pinakes_core::sharing::ShareTarget::SavedSearch { search_id } => { + ("saved_search".to_string(), search_id.to_string()) + }, + }; + + let (recipient_type, recipient_user_id, recipient_group_id, public_token) = + match &s.recipient { + pinakes_core::sharing::ShareRecipient::PublicLink { token, .. } => { + ("public_link".to_string(), None, None, Some(token.clone())) + }, + pinakes_core::sharing::ShareRecipient::User { user_id } => { + ("user".to_string(), Some(user_id.0.to_string()), None, None) + }, + pinakes_core::sharing::ShareRecipient::Group { group_id } => { + ("group".to_string(), None, Some(group_id.to_string()), None) + }, + pinakes_core::sharing::ShareRecipient::Federated { .. } => { + ("federated".to_string(), None, None, None) + }, + }; + + Self { + id: s.id.0.to_string(), + target_type, + target_id, + owner_id: s.owner_id.0.to_string(), + recipient_type, + recipient_user_id, + recipient_group_id, + public_token, + permissions: s.permissions.into(), + note: s.note, + expires_at: s.expires_at, + access_count: s.access_count, + last_accessed: s.last_accessed, + inherit_to_children: s.inherit_to_children, + created_at: s.created_at, + updated_at: s.updated_at, + } + } +} + +#[derive(Debug, Deserialize)] +pub struct UpdateShareRequest { + pub permissions: Option, + pub note: Option, + pub expires_at: Option>, + pub inherit_to_children: Option, +} + +#[derive(Debug, Serialize)] +pub struct ShareActivityResponse { + pub id: String, + pub share_id: String, + pub actor_id: Option, + pub actor_ip: Option, + pub action: String, + pub details: Option, + pub timestamp: DateTime, +} + +impl From for ShareActivityResponse { + fn from(a: pinakes_core::sharing::ShareActivity) -> Self { + Self { + id: a.id.to_string(), + share_id: a.share_id.0.to_string(), + actor_id: a.actor_id.map(|id| id.0.to_string()), + actor_ip: a.actor_ip, + action: a.action.to_string(), + details: a.details, + timestamp: a.timestamp, + } + } +} + +#[derive(Debug, Serialize)] +pub struct ShareNotificationResponse { + pub id: String, + pub share_id: String, + pub notification_type: String, + pub is_read: bool, + pub created_at: DateTime, +} + +impl From + for ShareNotificationResponse +{ + fn from(n: pinakes_core::sharing::ShareNotification) -> Self { + Self { + id: n.id.to_string(), + share_id: n.share_id.0.to_string(), + notification_type: n.notification_type.to_string(), + is_read: n.is_read, + created_at: n.created_at, + } + } +} + +#[derive(Debug, Deserialize)] +pub struct BatchDeleteSharesRequest { + pub share_ids: Vec, +} + +#[derive(Debug, Deserialize)] +pub struct AccessSharedRequest { + pub password: Option, +} + +/// Response for accessing shared content. +/// Single-media shares return the media object directly (backwards compatible). +/// Collection/Tag/SavedSearch shares return a list of items. +#[derive(Debug, Serialize)] +#[serde(untagged)] +pub enum SharedContentResponse { + Single(super::MediaResponse), + Multiple { items: Vec }, +} diff --git a/crates/pinakes-server/src/dto/social.rs b/crates/pinakes-server/src/dto/social.rs new file mode 100644 index 0000000..43f192f --- /dev/null +++ b/crates/pinakes-server/src/dto/social.rs @@ -0,0 +1,96 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Debug, Serialize)] +pub struct RatingResponse { + pub id: String, + pub user_id: String, + pub media_id: String, + pub stars: u8, + pub review_text: Option, + pub created_at: DateTime, +} + +impl From for RatingResponse { + fn from(r: pinakes_core::social::Rating) -> Self { + Self { + id: r.id.to_string(), + user_id: r.user_id.0.to_string(), + media_id: r.media_id.0.to_string(), + stars: r.stars, + review_text: r.review_text, + created_at: r.created_at, + } + } +} + +#[derive(Debug, Deserialize)] +pub struct CreateRatingRequest { + pub stars: u8, + pub review_text: Option, +} + +#[derive(Debug, Serialize)] +pub struct CommentResponse { + pub id: String, + pub user_id: String, + pub media_id: String, + pub parent_comment_id: Option, + pub text: String, + pub created_at: DateTime, +} + +impl From for CommentResponse { + fn from(c: pinakes_core::social::Comment) -> Self { + Self { + id: c.id.to_string(), + user_id: c.user_id.0.to_string(), + media_id: c.media_id.0.to_string(), + parent_comment_id: c.parent_comment_id.map(|id| id.to_string()), + text: c.text, + created_at: c.created_at, + } + } +} + +#[derive(Debug, Deserialize)] +pub struct CreateCommentRequest { + pub text: String, + pub parent_id: Option, +} + +#[derive(Debug, Deserialize)] +pub struct FavoriteRequest { + pub media_id: Uuid, +} + +#[derive(Debug, Deserialize)] +pub struct CreateShareLinkRequest { + pub media_id: Uuid, + pub password: Option, + pub expires_in_hours: Option, +} + +#[derive(Debug, Serialize)] +pub struct ShareLinkResponse { + pub id: String, + pub media_id: String, + pub token: String, + pub expires_at: Option>, + pub view_count: u64, + pub created_at: DateTime, +} + +impl From for ShareLinkResponse { + fn from(s: pinakes_core::social::ShareLink) -> Self { + Self { + id: s.id.to_string(), + media_id: s.media_id.0.to_string(), + token: s.token, + expires_at: s.expires_at, + view_count: s.view_count, + created_at: s.created_at, + } + } +} diff --git a/crates/pinakes-server/src/dto/statistics.rs b/crates/pinakes-server/src/dto/statistics.rs new file mode 100644 index 0000000..b5d573e --- /dev/null +++ b/crates/pinakes-server/src/dto/statistics.rs @@ -0,0 +1,84 @@ +use serde::Serialize; + +// Library Statistics +#[derive(Debug, Serialize)] +pub struct LibraryStatisticsResponse { + pub total_media: u64, + pub total_size_bytes: u64, + pub avg_file_size_bytes: u64, + pub media_by_type: Vec, + pub storage_by_type: Vec, + pub newest_item: Option, + pub oldest_item: Option, + pub top_tags: Vec, + pub top_collections: Vec, + pub total_tags: u64, + pub total_collections: u64, + pub total_duplicates: u64, +} + +#[derive(Debug, Serialize)] +pub struct TypeCountResponse { + pub name: String, + pub count: u64, +} + +impl From + for LibraryStatisticsResponse +{ + fn from(stats: pinakes_core::storage::LibraryStatistics) -> Self { + Self { + total_media: stats.total_media, + total_size_bytes: stats.total_size_bytes, + avg_file_size_bytes: stats.avg_file_size_bytes, + media_by_type: stats + .media_by_type + .into_iter() + .map(|(name, count)| TypeCountResponse { name, count }) + .collect(), + storage_by_type: stats + .storage_by_type + .into_iter() + .map(|(name, count)| TypeCountResponse { name, count }) + .collect(), + newest_item: stats.newest_item, + oldest_item: stats.oldest_item, + top_tags: stats + .top_tags + .into_iter() + .map(|(name, count)| TypeCountResponse { name, count }) + .collect(), + top_collections: stats + .top_collections + .into_iter() + .map(|(name, count)| TypeCountResponse { name, count }) + .collect(), + total_tags: stats.total_tags, + total_collections: stats.total_collections, + total_duplicates: stats.total_duplicates, + } + } +} + +// Database management +#[derive(Debug, Serialize)] +pub struct DatabaseStatsResponse { + pub media_count: u64, + pub tag_count: u64, + pub collection_count: u64, + pub audit_count: u64, + pub database_size_bytes: u64, + pub backend_name: String, +} + +// Scheduled Tasks +#[derive(Debug, Serialize)] +pub struct ScheduledTaskResponse { + pub id: String, + pub name: String, + pub schedule: String, + pub enabled: bool, + pub last_run: Option, + pub next_run: Option, + pub last_status: Option, +} diff --git a/crates/pinakes-server/src/dto/subtitles.rs b/crates/pinakes-server/src/dto/subtitles.rs new file mode 100644 index 0000000..a3203ef --- /dev/null +++ b/crates/pinakes-server/src/dto/subtitles.rs @@ -0,0 +1,44 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize)] +pub struct SubtitleResponse { + pub id: String, + pub media_id: String, + pub language: Option, + pub format: String, + pub is_embedded: bool, + pub track_index: Option, + pub offset_ms: i64, + pub created_at: DateTime, +} + +impl From for SubtitleResponse { + fn from(s: pinakes_core::subtitles::Subtitle) -> Self { + Self { + id: s.id.to_string(), + media_id: s.media_id.0.to_string(), + language: s.language, + format: s.format.to_string(), + is_embedded: s.is_embedded, + track_index: s.track_index, + offset_ms: s.offset_ms, + created_at: s.created_at, + } + } +} + +#[derive(Debug, Deserialize)] +pub struct AddSubtitleRequest { + pub language: Option, + pub format: String, + pub file_path: Option, + pub is_embedded: Option, + pub track_index: Option, + pub offset_ms: Option, +} + +#[derive(Debug, Deserialize)] +pub struct UpdateSubtitleOffsetRequest { + pub offset_ms: i64, +} diff --git a/crates/pinakes-server/src/dto/sync.rs b/crates/pinakes-server/src/dto/sync.rs new file mode 100644 index 0000000..95993ed --- /dev/null +++ b/crates/pinakes-server/src/dto/sync.rs @@ -0,0 +1,197 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use super::media::MediaResponse; + +#[derive(Debug, Deserialize)] +pub struct RegisterDeviceRequest { + pub name: String, + pub device_type: String, + pub client_version: String, + pub os_info: Option, +} + +#[derive(Debug, Serialize)] +pub struct DeviceResponse { + pub id: String, + pub name: String, + pub device_type: String, + pub client_version: String, + pub os_info: Option, + pub last_sync_at: Option>, + pub last_seen_at: DateTime, + pub sync_cursor: Option, + pub enabled: bool, + pub created_at: DateTime, +} + +impl From for DeviceResponse { + fn from(d: pinakes_core::sync::SyncDevice) -> Self { + Self { + id: d.id.0.to_string(), + name: d.name, + device_type: d.device_type.to_string(), + client_version: d.client_version, + os_info: d.os_info, + last_sync_at: d.last_sync_at, + last_seen_at: d.last_seen_at, + sync_cursor: d.sync_cursor, + enabled: d.enabled, + created_at: d.created_at, + } + } +} + +#[derive(Debug, Serialize)] +pub struct DeviceRegistrationResponse { + pub device: DeviceResponse, + pub device_token: String, +} + +#[derive(Debug, Deserialize)] +pub struct UpdateDeviceRequest { + pub name: Option, + pub enabled: Option, +} + +#[derive(Debug, Deserialize)] +pub struct GetChangesParams { + pub cursor: Option, + pub limit: Option, +} + +#[derive(Debug, Serialize)] +pub struct SyncChangeResponse { + pub id: String, + pub sequence: i64, + pub change_type: String, + pub media_id: Option, + pub path: String, + pub content_hash: Option, + pub file_size: Option, + pub timestamp: DateTime, +} + +impl From for SyncChangeResponse { + fn from(e: pinakes_core::sync::SyncLogEntry) -> Self { + Self { + id: e.id.to_string(), + sequence: e.sequence, + change_type: e.change_type.to_string(), + media_id: e.media_id.map(|id| id.0.to_string()), + path: e.path, + content_hash: e.content_hash.map(|h| h.0), + file_size: e.file_size, + timestamp: e.timestamp, + } + } +} + +#[derive(Debug, Serialize)] +pub struct ChangesResponse { + pub changes: Vec, + pub cursor: i64, + pub has_more: bool, +} + +#[derive(Debug, Deserialize)] +pub struct ClientChangeReport { + pub path: String, + pub change_type: String, + pub content_hash: Option, + pub file_size: Option, + pub local_mtime: Option, +} + +#[derive(Debug, Deserialize)] +pub struct ReportChangesRequest { + pub changes: Vec, +} + +#[derive(Debug, Serialize)] +pub struct ReportChangesResponse { + pub accepted: Vec, + pub conflicts: Vec, + pub upload_required: Vec, +} + +#[derive(Debug, Serialize)] +pub struct ConflictResponse { + pub id: String, + pub path: String, + pub local_hash: String, + pub server_hash: String, + pub detected_at: DateTime, +} + +impl From for ConflictResponse { + fn from(c: pinakes_core::sync::SyncConflict) -> Self { + Self { + id: c.id.to_string(), + path: c.path, + local_hash: c.local_hash, + server_hash: c.server_hash, + detected_at: c.detected_at, + } + } +} + +#[derive(Debug, Deserialize)] +pub struct ResolveConflictRequest { + pub resolution: String, +} + +#[derive(Debug, Deserialize)] +pub struct CreateUploadSessionRequest { + pub target_path: String, + pub expected_hash: String, + pub expected_size: u64, + pub chunk_size: Option, +} + +#[derive(Debug, Serialize)] +pub struct UploadSessionResponse { + pub id: String, + pub target_path: String, + pub expected_hash: String, + pub expected_size: u64, + pub chunk_size: u64, + pub chunk_count: u64, + pub status: String, + pub created_at: DateTime, + pub expires_at: DateTime, +} + +impl From for UploadSessionResponse { + fn from(s: pinakes_core::sync::UploadSession) -> Self { + Self { + id: s.id.to_string(), + target_path: s.target_path, + expected_hash: s.expected_hash.0, + expected_size: s.expected_size, + chunk_size: s.chunk_size, + chunk_count: s.chunk_count, + status: s.status.to_string(), + created_at: s.created_at, + expires_at: s.expires_at, + } + } +} + +#[derive(Debug, Serialize)] +pub struct ChunkUploadedResponse { + pub chunk_index: u64, + pub received: bool, +} + +#[derive(Debug, Deserialize)] +pub struct AcknowledgeChangesRequest { + pub cursor: i64, +} + +// Most viewed (uses MediaResponse) +#[derive(Debug, Serialize)] +pub struct MostViewedResponse { + pub media: MediaResponse, + pub view_count: u64, +} diff --git a/crates/pinakes-server/src/dto/tags.rs b/crates/pinakes-server/src/dto/tags.rs new file mode 100644 index 0000000..20f1bec --- /dev/null +++ b/crates/pinakes-server/src/dto/tags.rs @@ -0,0 +1,33 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; + +#[derive(Debug, Serialize)] +pub struct TagResponse { + pub id: String, + pub name: String, + pub parent_id: Option, + pub created_at: DateTime, +} + +#[derive(Debug, Deserialize)] +pub struct CreateTagRequest { + pub name: String, + pub parent_id: Option, +} + +#[derive(Debug, Deserialize)] +pub struct TagMediaRequest { + pub tag_id: Uuid, +} + +impl From for TagResponse { + fn from(tag: pinakes_core::model::Tag) -> Self { + Self { + id: tag.id.to_string(), + name: tag.name, + parent_id: tag.parent_id.map(|id| id.to_string()), + created_at: tag.created_at, + } + } +} diff --git a/crates/pinakes-server/src/dto/transcode.rs b/crates/pinakes-server/src/dto/transcode.rs new file mode 100644 index 0000000..4a828a2 --- /dev/null +++ b/crates/pinakes-server/src/dto/transcode.rs @@ -0,0 +1,34 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize)] +pub struct TranscodeSessionResponse { + pub id: String, + pub media_id: String, + pub profile: String, + pub status: String, + pub progress: f32, + pub created_at: DateTime, + pub expires_at: Option>, +} + +impl From + for TranscodeSessionResponse +{ + fn from(s: pinakes_core::transcode::TranscodeSession) -> Self { + Self { + id: s.id.to_string(), + media_id: s.media_id.0.to_string(), + profile: s.profile, + status: s.status.as_str().to_string(), + progress: s.progress, + created_at: s.created_at, + expires_at: s.expires_at, + } + } +} + +#[derive(Debug, Deserialize)] +pub struct CreateTranscodeRequest { + pub profile: String, +} diff --git a/crates/pinakes-server/src/dto/users.rs b/crates/pinakes-server/src/dto/users.rs new file mode 100644 index 0000000..f657dab --- /dev/null +++ b/crates/pinakes-server/src/dto/users.rs @@ -0,0 +1,100 @@ +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +// Auth +#[derive(Debug, Deserialize)] +pub struct LoginRequest { + pub username: String, + pub password: String, +} + +#[derive(Debug, Serialize)] +pub struct LoginResponse { + pub token: String, + pub username: String, + pub role: String, +} + +#[derive(Debug, Serialize)] +pub struct UserInfoResponse { + pub username: String, + pub role: String, +} + +// Users +#[derive(Debug, Serialize)] +pub struct UserResponse { + pub id: String, + pub username: String, + pub role: String, + pub profile: UserProfileResponse, + pub created_at: DateTime, + pub updated_at: DateTime, +} + +#[derive(Debug, Serialize)] +pub struct UserProfileResponse { + pub avatar_path: Option, + pub bio: Option, + pub preferences: UserPreferencesResponse, +} + +#[derive(Debug, Serialize)] +pub struct UserPreferencesResponse { + pub theme: Option, + pub language: Option, + pub default_video_quality: Option, + pub auto_play: bool, +} + +#[derive(Debug, Serialize)] +pub struct UserLibraryResponse { + pub user_id: String, + pub root_path: String, + pub permission: String, + pub granted_at: DateTime, +} + +#[derive(Debug, Deserialize)] +pub struct GrantLibraryAccessRequest { + pub root_path: String, + pub permission: pinakes_core::users::LibraryPermission, +} + +#[derive(Debug, Deserialize)] +pub struct RevokeLibraryAccessRequest { + pub root_path: String, +} + +impl From for UserResponse { + fn from(user: pinakes_core::users::User) -> Self { + Self { + id: user.id.0.to_string(), + username: user.username, + role: user.role.to_string(), + profile: UserProfileResponse { + avatar_path: user.profile.avatar_path, + bio: user.profile.bio, + preferences: UserPreferencesResponse { + theme: user.profile.preferences.theme, + language: user.profile.preferences.language, + default_video_quality: user.profile.preferences.default_video_quality, + auto_play: user.profile.preferences.auto_play, + }, + }, + created_at: user.created_at, + updated_at: user.updated_at, + } + } +} + +impl From for UserLibraryResponse { + fn from(access: pinakes_core::users::UserLibraryAccess) -> Self { + Self { + user_id: access.user_id.0.to_string(), + root_path: access.root_path, + permission: access.permission.to_string(), + granted_at: access.granted_at, + } + } +}