initial commit
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I4a6b498153eccd5407510dd541b7f4816a6a6964
This commit is contained in:
commit
6a73d11c4b
124 changed files with 34856 additions and 0 deletions
553
crates/pinakes-server/src/dto.rs
Normal file
553
crates/pinakes-server/src/dto.rs
Normal file
|
|
@ -0,0 +1,553 @@
|
|||
use std::collections::HashMap;
|
||||
use std::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<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>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: 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>,
|
||||
}
|
||||
|
||||
// Tags
|
||||
#[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,
|
||||
}
|
||||
|
||||
// Collections
|
||||
#[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>,
|
||||
}
|
||||
|
||||
// Search
|
||||
#[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,
|
||||
}
|
||||
|
||||
// Audit
|
||||
#[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>,
|
||||
}
|
||||
|
||||
// Search (POST body)
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct SearchRequestBody {
|
||||
pub q: String,
|
||||
pub sort: Option<String>,
|
||||
pub offset: Option<u64>,
|
||||
pub limit: Option<u64>,
|
||||
}
|
||||
|
||||
// Scan
|
||||
#[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>,
|
||||
}
|
||||
|
||||
// Pagination
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct PaginationParams {
|
||||
pub offset: Option<u64>,
|
||||
pub limit: Option<u64>,
|
||||
pub sort: Option<String>,
|
||||
}
|
||||
|
||||
// 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<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,
|
||||
}
|
||||
|
||||
// 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>,
|
||||
}
|
||||
|
||||
// Batch operations
|
||||
#[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>,
|
||||
}
|
||||
|
||||
// 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<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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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>,
|
||||
}
|
||||
|
||||
// Duplicates
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct DuplicateGroupResponse {
|
||||
pub content_hash: String,
|
||||
pub items: Vec<MediaResponse>,
|
||||
}
|
||||
|
||||
// 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<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: format!("{:?}", v.field_type).to_lowercase(),
|
||||
value: v.value,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect(),
|
||||
created_at: item.created_at,
|
||||
updated_at: item.updated_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: format!("{:?}", col.kind).to_lowercase(),
|
||||
filter_query: col.filter_query,
|
||||
created_at: col.created_at,
|
||||
updated_at: col.updated_at,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue