Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I9a5114addcab5fbff430ab2b919b83466a6a6964
981 lines
25 KiB
Rust
981 lines
25 KiB
Rust
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,
|
|
}
|
|
}
|
|
}
|
|
|
|
// Plugins
|
|
#[derive(Debug, Serialize)]
|
|
pub struct PluginResponse {
|
|
pub id: String,
|
|
pub name: String,
|
|
pub version: String,
|
|
pub author: String,
|
|
pub description: String,
|
|
pub api_version: String,
|
|
pub enabled: bool,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct InstallPluginRequest {
|
|
pub source: String, // URL or file path
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct TogglePluginRequest {
|
|
pub enabled: bool,
|
|
}
|
|
|
|
impl PluginResponse {
|
|
pub fn new(meta: pinakes_plugin_api::PluginMetadata, enabled: bool) -> Self {
|
|
Self {
|
|
id: meta.id,
|
|
name: meta.name,
|
|
version: meta.version,
|
|
author: meta.author,
|
|
description: meta.description,
|
|
api_version: meta.api_version,
|
|
enabled,
|
|
}
|
|
}
|
|
}
|
|
|
|
// Users
|
|
#[derive(Debug, Serialize)]
|
|
pub struct UserResponse {
|
|
pub id: String,
|
|
pub username: String,
|
|
pub role: String,
|
|
pub profile: UserProfileResponse,
|
|
pub created_at: DateTime<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: format!("{:?}", access.permission).to_lowercase(),
|
|
granted_at: access.granted_at,
|
|
}
|
|
}
|
|
}
|
|
|
|
// ===== Social (Ratings, Comments, Favorites, Shares) =====
|
|
|
|
#[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,
|
|
}
|
|
}
|
|
}
|
|
|
|
// ===== Playlists =====
|
|
|
|
#[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,
|
|
}
|
|
|
|
// ===== Analytics =====
|
|
|
|
#[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>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct MostViewedResponse {
|
|
pub media: MediaResponse,
|
|
pub view_count: u64,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct WatchProgressRequest {
|
|
pub progress_secs: f64,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct WatchProgressResponse {
|
|
pub progress_secs: f64,
|
|
}
|
|
|
|
// ===== Subtitles =====
|
|
|
|
#[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,
|
|
}
|
|
|
|
// ===== Enrichment =====
|
|
|
|
#[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,
|
|
}
|
|
}
|
|
}
|
|
|
|
// ===== Transcode =====
|
|
|
|
#[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,
|
|
}
|