pinakes-server: split dto module into submodules

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I1e9421d79010813992feb2b26c44d6796a6a6964
This commit is contained in:
raf 2026-03-07 19:57:37 +03:00
commit d77e5b9f2f
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
21 changed files with 1530 additions and 1443 deletions

File diff suppressed because it is too large Load diff

View file

@ -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<String>,
pub user_id: Option<String>,
pub event_type: String,
pub timestamp: DateTime<Utc>,
pub duration_secs: Option<f64>,
}
impl From<pinakes_core::analytics::UsageEvent> 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<Uuid>,
pub event_type: String,
pub duration_secs: Option<f64>,
pub context: Option<serde_json::Value>,
}

View file

@ -0,0 +1,23 @@
use chrono::{DateTime, Utc};
use serde::Serialize;
#[derive(Debug, Serialize)]
pub struct AuditEntryResponse {
pub id: String,
pub media_id: Option<String>,
pub action: String,
pub details: Option<String>,
pub timestamp: DateTime<Utc>,
}
impl From<pinakes_core::model::AuditEntry> 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,
}
}
}

View file

@ -0,0 +1,36 @@
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Deserialize)]
pub struct BatchTagRequest {
pub media_ids: Vec<Uuid>,
pub tag_ids: Vec<Uuid>,
}
#[derive(Debug, Deserialize)]
pub struct BatchCollectionRequest {
pub media_ids: Vec<Uuid>,
pub collection_id: Uuid,
}
#[derive(Debug, Deserialize)]
pub struct BatchDeleteRequest {
pub media_ids: Vec<Uuid>,
}
#[derive(Debug, Deserialize)]
pub struct BatchUpdateRequest {
pub media_ids: Vec<Uuid>,
pub title: Option<String>,
pub artist: Option<String>,
pub album: Option<String>,
pub genre: Option<String>,
pub year: Option<i32>,
pub description: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct BatchOperationResponse {
pub processed: usize,
pub errors: Vec<String>,
}

View file

@ -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<String>,
pub kind: String,
pub filter_query: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Deserialize)]
pub struct CreateCollectionRequest {
pub name: String,
pub kind: String,
pub description: Option<String>,
pub filter_query: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct AddMemberRequest {
pub media_id: Uuid,
pub position: Option<i32>,
}
impl From<pinakes_core::model::Collection> 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,
}
}
}

View file

@ -0,0 +1,75 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize)]
pub struct ConfigResponse {
pub backend: String,
pub database_path: Option<String>,
pub roots: Vec<String>,
pub scanning: ScanningConfigResponse,
pub server: ServerConfigResponse,
pub ui: UiConfigResponse,
pub config_path: Option<String>,
pub config_writable: bool,
}
#[derive(Debug, Serialize)]
pub struct ScanningConfigResponse {
pub watch: bool,
pub poll_interval_secs: u64,
pub ignore_patterns: Vec<String>,
}
#[derive(Debug, Serialize)]
pub struct ServerConfigResponse {
pub host: String,
pub port: u16,
}
#[derive(Debug, Deserialize)]
pub struct UpdateScanningRequest {
pub watch: Option<bool>,
pub poll_interval_secs: Option<u64>,
pub ignore_patterns: Option<Vec<String>>,
}
#[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<String>,
pub default_view: Option<String>,
pub default_page_size: Option<usize>,
pub default_view_mode: Option<String>,
pub auto_play_media: Option<bool>,
pub show_thumbnails: Option<bool>,
pub sidebar_collapsed: Option<bool>,
}
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,
}
}
}

View file

@ -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<String>,
pub metadata: serde_json::Value,
pub confidence: f64,
pub last_updated: DateTime<Utc>,
}
impl From<pinakes_core::enrichment::ExternalMetadata>
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,
}
}
}

View file

@ -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<String>,
pub artist: Option<String>,
pub album: Option<String>,
pub genre: Option<String>,
pub year: Option<i32>,
pub duration_secs: Option<f64>,
pub description: Option<String>,
pub has_thumbnail: bool,
pub custom_fields: HashMap<String, CustomFieldResponse>,
// Photo-specific metadata
pub date_taken: Option<DateTime<Utc>>,
pub latitude: Option<f64>,
pub longitude: Option<f64>,
pub camera_make: Option<String>,
pub camera_model: Option<String>,
pub rating: Option<i32>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
// Markdown links
pub links_extracted_at: Option<DateTime<Utc>>,
}
#[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<String>,
pub artist: Option<String>,
pub album: Option<String>,
pub genre: Option<String>,
pub year: Option<i32>,
pub description: Option<String>,
}
// 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<Uuid>,
pub destination: PathBuf,
}
#[derive(Debug, Serialize)]
pub struct TrashResponse {
pub items: Vec<MediaResponse>,
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<Vec<Uuid>>,
pub new_tags: Option<Vec<String>>,
pub collection_id: Option<Uuid>,
}
#[derive(Debug, Deserialize)]
pub struct BatchImportRequest {
pub paths: Vec<PathBuf>,
pub tag_ids: Option<Vec<Uuid>>,
pub new_tags: Option<Vec<String>>,
pub collection_id: Option<Uuid>,
}
#[derive(Debug, Serialize)]
pub struct BatchImportResponse {
pub results: Vec<BatchImportItemResult>,
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<String>,
pub was_duplicate: bool,
pub error: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct DirectoryImportRequest {
pub path: PathBuf,
pub tag_ids: Option<Vec<Uuid>>,
pub new_tags: Option<Vec<String>>,
pub collection_id: Option<Uuid>,
}
#[derive(Debug, Serialize)]
pub struct DirectoryPreviewResponse {
pub files: Vec<DirectoryPreviewFile>,
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<String>,
pub artist: Option<String>,
pub album: Option<String>,
pub genre: Option<String>,
pub year: Option<i32>,
pub description: Option<String>,
}
// 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<MediaResponse>,
}
// 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<pinakes_core::model::UploadResult> 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<pinakes_core::model::ManagedStorageStats>
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<pinakes_core::model::MediaItem> 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,
}

View file

@ -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::*;

View file

@ -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<String>,
pub is_public: bool,
pub is_smart: bool,
pub filter_query: Option<String>,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
impl From<pinakes_core::playlists::Playlist> 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<String>,
pub is_public: Option<bool>,
pub is_smart: Option<bool>,
pub filter_query: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct UpdatePlaylistRequest {
pub name: Option<String>,
pub description: Option<String>,
pub is_public: Option<bool>,
}
#[derive(Debug, Deserialize)]
pub struct PlaylistItemRequest {
pub media_id: Uuid,
pub position: Option<i32>,
}
#[derive(Debug, Deserialize)]
pub struct ReorderPlaylistRequest {
pub media_id: Uuid,
pub new_position: i32,
}

View file

@ -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,
}
}
}

View file

@ -0,0 +1,29 @@
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
pub struct ScanRequest {
pub path: Option<PathBuf>,
}
#[derive(Debug, Serialize)]
pub struct ScanResponse {
pub files_found: usize,
pub files_processed: usize,
pub errors: Vec<String>,
}
#[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<String>,
}

View file

@ -0,0 +1,34 @@
use serde::{Deserialize, Serialize};
use super::media::MediaResponse;
#[derive(Debug, Deserialize)]
pub struct SearchParams {
pub q: String,
pub sort: Option<String>,
pub offset: Option<u64>,
pub limit: Option<u64>,
}
#[derive(Debug, Serialize)]
pub struct SearchResponse {
pub items: Vec<MediaResponse>,
pub total_count: u64,
}
// Search (POST body)
#[derive(Debug, Deserialize)]
pub struct SearchRequestBody {
pub q: String,
pub sort: Option<String>,
pub offset: Option<u64>,
pub limit: Option<u64>,
}
// Pagination
#[derive(Debug, Deserialize)]
pub struct PaginationParams {
pub offset: Option<u64>,
pub limit: Option<u64>,
pub sort: Option<String>,
}

View file

@ -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<Uuid>,
pub recipient_group_id: Option<Uuid>,
pub password: Option<String>,
pub permissions: Option<SharePermissionsRequest>,
pub note: Option<String>,
pub expires_in_hours: Option<u64>,
pub inherit_to_children: Option<bool>,
}
#[derive(Debug, Deserialize)]
pub struct SharePermissionsRequest {
pub can_view: Option<bool>,
pub can_download: Option<bool>,
pub can_edit: Option<bool>,
pub can_delete: Option<bool>,
pub can_reshare: Option<bool>,
pub can_add: Option<bool>,
}
#[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<String>,
pub recipient_group_id: Option<String>,
pub public_token: Option<String>,
pub permissions: SharePermissionsResponse,
pub note: Option<String>,
pub expires_at: Option<DateTime<Utc>>,
pub access_count: u64,
pub last_accessed: Option<DateTime<Utc>>,
pub inherit_to_children: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
#[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<pinakes_core::sharing::SharePermissions>
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<pinakes_core::sharing::Share> 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<SharePermissionsRequest>,
pub note: Option<String>,
pub expires_at: Option<DateTime<Utc>>,
pub inherit_to_children: Option<bool>,
}
#[derive(Debug, Serialize)]
pub struct ShareActivityResponse {
pub id: String,
pub share_id: String,
pub actor_id: Option<String>,
pub actor_ip: Option<String>,
pub action: String,
pub details: Option<String>,
pub timestamp: DateTime<Utc>,
}
impl From<pinakes_core::sharing::ShareActivity> 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<Utc>,
}
impl From<pinakes_core::sharing::ShareNotification>
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<Uuid>,
}
#[derive(Debug, Deserialize)]
pub struct AccessSharedRequest {
pub password: Option<String>,
}
/// 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<super::MediaResponse> },
}

View file

@ -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<String>,
pub created_at: DateTime<Utc>,
}
impl From<pinakes_core::social::Rating> 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<String>,
}
#[derive(Debug, Serialize)]
pub struct CommentResponse {
pub id: String,
pub user_id: String,
pub media_id: String,
pub parent_comment_id: Option<String>,
pub text: String,
pub created_at: DateTime<Utc>,
}
impl From<pinakes_core::social::Comment> 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<Uuid>,
}
#[derive(Debug, Deserialize)]
pub struct FavoriteRequest {
pub media_id: Uuid,
}
#[derive(Debug, Deserialize)]
pub struct CreateShareLinkRequest {
pub media_id: Uuid,
pub password: Option<String>,
pub expires_in_hours: Option<u64>,
}
#[derive(Debug, Serialize)]
pub struct ShareLinkResponse {
pub id: String,
pub media_id: String,
pub token: String,
pub expires_at: Option<DateTime<Utc>>,
pub view_count: u64,
pub created_at: DateTime<Utc>,
}
impl From<pinakes_core::social::ShareLink> 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,
}
}
}

View file

@ -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<TypeCountResponse>,
pub storage_by_type: Vec<TypeCountResponse>,
pub newest_item: Option<String>,
pub oldest_item: Option<String>,
pub top_tags: Vec<TypeCountResponse>,
pub top_collections: Vec<TypeCountResponse>,
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<pinakes_core::storage::LibraryStatistics>
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<String>,
pub next_run: Option<String>,
pub last_status: Option<String>,
}

View file

@ -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<String>,
pub format: String,
pub is_embedded: bool,
pub track_index: Option<usize>,
pub offset_ms: i64,
pub created_at: DateTime<Utc>,
}
impl From<pinakes_core::subtitles::Subtitle> 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<String>,
pub format: String,
pub file_path: Option<String>,
pub is_embedded: Option<bool>,
pub track_index: Option<usize>,
pub offset_ms: Option<i64>,
}
#[derive(Debug, Deserialize)]
pub struct UpdateSubtitleOffsetRequest {
pub offset_ms: i64,
}

View file

@ -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<String>,
}
#[derive(Debug, Serialize)]
pub struct DeviceResponse {
pub id: String,
pub name: String,
pub device_type: String,
pub client_version: String,
pub os_info: Option<String>,
pub last_sync_at: Option<DateTime<Utc>>,
pub last_seen_at: DateTime<Utc>,
pub sync_cursor: Option<i64>,
pub enabled: bool,
pub created_at: DateTime<Utc>,
}
impl From<pinakes_core::sync::SyncDevice> 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<String>,
pub enabled: Option<bool>,
}
#[derive(Debug, Deserialize)]
pub struct GetChangesParams {
pub cursor: Option<i64>,
pub limit: Option<u64>,
}
#[derive(Debug, Serialize)]
pub struct SyncChangeResponse {
pub id: String,
pub sequence: i64,
pub change_type: String,
pub media_id: Option<String>,
pub path: String,
pub content_hash: Option<String>,
pub file_size: Option<u64>,
pub timestamp: DateTime<Utc>,
}
impl From<pinakes_core::sync::SyncLogEntry> 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<SyncChangeResponse>,
pub cursor: i64,
pub has_more: bool,
}
#[derive(Debug, Deserialize)]
pub struct ClientChangeReport {
pub path: String,
pub change_type: String,
pub content_hash: Option<String>,
pub file_size: Option<u64>,
pub local_mtime: Option<i64>,
}
#[derive(Debug, Deserialize)]
pub struct ReportChangesRequest {
pub changes: Vec<ClientChangeReport>,
}
#[derive(Debug, Serialize)]
pub struct ReportChangesResponse {
pub accepted: Vec<String>,
pub conflicts: Vec<ConflictResponse>,
pub upload_required: Vec<String>,
}
#[derive(Debug, Serialize)]
pub struct ConflictResponse {
pub id: String,
pub path: String,
pub local_hash: String,
pub server_hash: String,
pub detected_at: DateTime<Utc>,
}
impl From<pinakes_core::sync::SyncConflict> 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<u64>,
}
#[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<Utc>,
pub expires_at: DateTime<Utc>,
}
impl From<pinakes_core::sync::UploadSession> 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,
}

View file

@ -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<String>,
pub created_at: DateTime<Utc>,
}
#[derive(Debug, Deserialize)]
pub struct CreateTagRequest {
pub name: String,
pub parent_id: Option<Uuid>,
}
#[derive(Debug, Deserialize)]
pub struct TagMediaRequest {
pub tag_id: Uuid,
}
impl From<pinakes_core::model::Tag> 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,
}
}
}

View file

@ -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<Utc>,
pub expires_at: Option<DateTime<Utc>>,
}
impl From<pinakes_core::transcode::TranscodeSession>
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,
}

View file

@ -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<Utc>,
pub updated_at: DateTime<Utc>,
}
#[derive(Debug, Serialize)]
pub struct UserProfileResponse {
pub avatar_path: Option<String>,
pub bio: Option<String>,
pub preferences: UserPreferencesResponse,
}
#[derive(Debug, Serialize)]
pub struct UserPreferencesResponse {
pub theme: Option<String>,
pub language: Option<String>,
pub default_video_quality: Option<String>,
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<Utc>,
}
#[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<pinakes_core::users::User> 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<pinakes_core::users::UserLibraryAccess> 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,
}
}
}