initial commit

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I4a6b498153eccd5407510dd541b7f4816a6a6964
This commit is contained in:
raf 2026-01-30 22:05:46 +03:00
commit 6a73d11c4b
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
124 changed files with 34856 additions and 0 deletions

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