pinakes-server: split dto module into submodules
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I1e9421d79010813992feb2b26c44d6796a6a6964
This commit is contained in:
parent
e66a2231b7
commit
d77e5b9f2f
21 changed files with 1530 additions and 1443 deletions
File diff suppressed because it is too large
Load diff
34
crates/pinakes-server/src/dto/analytics.rs
Normal file
34
crates/pinakes-server/src/dto/analytics.rs
Normal 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>,
|
||||||
|
}
|
||||||
23
crates/pinakes-server/src/dto/audit.rs
Normal file
23
crates/pinakes-server/src/dto/audit.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
crates/pinakes-server/src/dto/batch.rs
Normal file
36
crates/pinakes-server/src/dto/batch.rs
Normal 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>,
|
||||||
|
}
|
||||||
42
crates/pinakes-server/src/dto/collections.rs
Normal file
42
crates/pinakes-server/src/dto/collections.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
75
crates/pinakes-server/src/dto/config.rs
Normal file
75
crates/pinakes-server/src/dto/config.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
crates/pinakes-server/src/dto/enrichment.rs
Normal file
37
crates/pinakes-server/src/dto/enrichment.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
294
crates/pinakes-server/src/dto/media.rs
Normal file
294
crates/pinakes-server/src/dto/media.rs
Normal 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,
|
||||||
|
}
|
||||||
39
crates/pinakes-server/src/dto/mod.rs
Normal file
39
crates/pinakes-server/src/dto/mod.rs
Normal 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::*;
|
||||||
60
crates/pinakes-server/src/dto/playlists.rs
Normal file
60
crates/pinakes-server/src/dto/playlists.rs
Normal 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,
|
||||||
|
}
|
||||||
37
crates/pinakes-server/src/dto/plugins.rs
Normal file
37
crates/pinakes-server/src/dto/plugins.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
29
crates/pinakes-server/src/dto/scan.rs
Normal file
29
crates/pinakes-server/src/dto/scan.rs
Normal 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>,
|
||||||
|
}
|
||||||
34
crates/pinakes-server/src/dto/search.rs
Normal file
34
crates/pinakes-server/src/dto/search.rs
Normal 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>,
|
||||||
|
}
|
||||||
202
crates/pinakes-server/src/dto/sharing.rs
Normal file
202
crates/pinakes-server/src/dto/sharing.rs
Normal 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> },
|
||||||
|
}
|
||||||
96
crates/pinakes-server/src/dto/social.rs
Normal file
96
crates/pinakes-server/src/dto/social.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
84
crates/pinakes-server/src/dto/statistics.rs
Normal file
84
crates/pinakes-server/src/dto/statistics.rs
Normal 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>,
|
||||||
|
}
|
||||||
44
crates/pinakes-server/src/dto/subtitles.rs
Normal file
44
crates/pinakes-server/src/dto/subtitles.rs
Normal 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,
|
||||||
|
}
|
||||||
197
crates/pinakes-server/src/dto/sync.rs
Normal file
197
crates/pinakes-server/src/dto/sync.rs
Normal 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,
|
||||||
|
}
|
||||||
33
crates/pinakes-server/src/dto/tags.rs
Normal file
33
crates/pinakes-server/src/dto/tags.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
34
crates/pinakes-server/src/dto/transcode.rs
Normal file
34
crates/pinakes-server/src/dto/transcode.rs
Normal 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,
|
||||||
|
}
|
||||||
100
crates/pinakes-server/src/dto/users.rs
Normal file
100
crates/pinakes-server/src/dto/users.rs
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue