Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I379005c29a79a637a8e1fc3709907cd36a6a6964
383 lines
9.5 KiB
Rust
383 lines
9.5 KiB
Rust
use std::collections::HashMap;
|
|
use std::fmt;
|
|
use std::path::PathBuf;
|
|
|
|
use chrono::{DateTime, Utc};
|
|
use serde::{Deserialize, Serialize};
|
|
use uuid::Uuid;
|
|
|
|
use crate::media_type::MediaType;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
pub struct MediaId(pub Uuid);
|
|
|
|
impl MediaId {
|
|
pub fn new() -> Self {
|
|
Self(Uuid::now_v7())
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for MediaId {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}", self.0)
|
|
}
|
|
}
|
|
|
|
impl Default for MediaId {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
pub struct ContentHash(pub String);
|
|
|
|
impl ContentHash {
|
|
pub fn new(hex: String) -> Self {
|
|
Self(hex)
|
|
}
|
|
}
|
|
|
|
impl fmt::Display for ContentHash {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}", self.0)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct MediaItem {
|
|
pub id: MediaId,
|
|
pub path: PathBuf,
|
|
pub file_name: String,
|
|
pub media_type: MediaType,
|
|
pub content_hash: ContentHash,
|
|
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 thumbnail_path: Option<PathBuf>,
|
|
pub custom_fields: HashMap<String, CustomField>,
|
|
/// File modification time (Unix timestamp in seconds), used for incremental scanning
|
|
pub file_mtime: Option<i64>,
|
|
pub created_at: DateTime<Utc>,
|
|
pub updated_at: DateTime<Utc>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CustomField {
|
|
pub field_type: CustomFieldType,
|
|
pub value: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum CustomFieldType {
|
|
Text,
|
|
Number,
|
|
Date,
|
|
Boolean,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Tag {
|
|
pub id: Uuid,
|
|
pub name: String,
|
|
pub parent_id: Option<Uuid>,
|
|
pub created_at: DateTime<Utc>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Collection {
|
|
pub id: Uuid,
|
|
pub name: String,
|
|
pub description: Option<String>,
|
|
pub kind: CollectionKind,
|
|
pub filter_query: Option<String>,
|
|
pub created_at: DateTime<Utc>,
|
|
pub updated_at: DateTime<Utc>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "lowercase")]
|
|
pub enum CollectionKind {
|
|
Manual,
|
|
Virtual,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct CollectionMember {
|
|
pub collection_id: Uuid,
|
|
pub media_id: MediaId,
|
|
pub position: i32,
|
|
pub added_at: DateTime<Utc>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct AuditEntry {
|
|
pub id: Uuid,
|
|
pub media_id: Option<MediaId>,
|
|
pub action: AuditAction,
|
|
pub details: Option<String>,
|
|
pub timestamp: DateTime<Utc>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum AuditAction {
|
|
// Media actions
|
|
Imported,
|
|
Updated,
|
|
Deleted,
|
|
Tagged,
|
|
Untagged,
|
|
AddedToCollection,
|
|
RemovedFromCollection,
|
|
Opened,
|
|
Scanned,
|
|
|
|
// Authentication actions
|
|
LoginSuccess,
|
|
LoginFailed,
|
|
Logout,
|
|
SessionExpired,
|
|
|
|
// Authorization actions
|
|
PermissionDenied,
|
|
RoleChanged,
|
|
LibraryAccessGranted,
|
|
LibraryAccessRevoked,
|
|
|
|
// User management
|
|
UserCreated,
|
|
UserUpdated,
|
|
UserDeleted,
|
|
|
|
// Plugin actions
|
|
PluginInstalled,
|
|
PluginUninstalled,
|
|
PluginEnabled,
|
|
PluginDisabled,
|
|
|
|
// Configuration actions
|
|
ConfigChanged,
|
|
RootDirectoryAdded,
|
|
RootDirectoryRemoved,
|
|
|
|
// Social/Sharing actions
|
|
ShareLinkCreated,
|
|
ShareLinkAccessed,
|
|
|
|
// System actions
|
|
DatabaseVacuumed,
|
|
DatabaseCleared,
|
|
ExportCompleted,
|
|
IntegrityCheckCompleted,
|
|
}
|
|
|
|
impl fmt::Display for AuditAction {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
let s = match self {
|
|
// Media actions
|
|
Self::Imported => "imported",
|
|
Self::Updated => "updated",
|
|
Self::Deleted => "deleted",
|
|
Self::Tagged => "tagged",
|
|
Self::Untagged => "untagged",
|
|
Self::AddedToCollection => "added_to_collection",
|
|
Self::RemovedFromCollection => "removed_from_collection",
|
|
Self::Opened => "opened",
|
|
Self::Scanned => "scanned",
|
|
|
|
// Authentication actions
|
|
Self::LoginSuccess => "login_success",
|
|
Self::LoginFailed => "login_failed",
|
|
Self::Logout => "logout",
|
|
Self::SessionExpired => "session_expired",
|
|
|
|
// Authorization actions
|
|
Self::PermissionDenied => "permission_denied",
|
|
Self::RoleChanged => "role_changed",
|
|
Self::LibraryAccessGranted => "library_access_granted",
|
|
Self::LibraryAccessRevoked => "library_access_revoked",
|
|
|
|
// User management
|
|
Self::UserCreated => "user_created",
|
|
Self::UserUpdated => "user_updated",
|
|
Self::UserDeleted => "user_deleted",
|
|
|
|
// Plugin actions
|
|
Self::PluginInstalled => "plugin_installed",
|
|
Self::PluginUninstalled => "plugin_uninstalled",
|
|
Self::PluginEnabled => "plugin_enabled",
|
|
Self::PluginDisabled => "plugin_disabled",
|
|
|
|
// Configuration actions
|
|
Self::ConfigChanged => "config_changed",
|
|
Self::RootDirectoryAdded => "root_directory_added",
|
|
Self::RootDirectoryRemoved => "root_directory_removed",
|
|
|
|
// Social/Sharing actions
|
|
Self::ShareLinkCreated => "share_link_created",
|
|
Self::ShareLinkAccessed => "share_link_accessed",
|
|
|
|
// System actions
|
|
Self::DatabaseVacuumed => "database_vacuumed",
|
|
Self::DatabaseCleared => "database_cleared",
|
|
Self::ExportCompleted => "export_completed",
|
|
Self::IntegrityCheckCompleted => "integrity_check_completed",
|
|
};
|
|
write!(f, "{s}")
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Pagination {
|
|
pub offset: u64,
|
|
pub limit: u64,
|
|
pub sort: Option<String>,
|
|
}
|
|
|
|
impl Pagination {
|
|
pub fn new(offset: u64, limit: u64, sort: Option<String>) -> Self {
|
|
Self {
|
|
offset,
|
|
limit,
|
|
sort,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Default for Pagination {
|
|
fn default() -> Self {
|
|
Self {
|
|
offset: 0,
|
|
limit: 50,
|
|
sort: None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct SavedSearch {
|
|
pub id: Uuid,
|
|
pub name: String,
|
|
pub query: String,
|
|
pub sort_order: Option<String>,
|
|
pub created_at: DateTime<Utc>,
|
|
}
|
|
|
|
// Book Management Types
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct BookMetadata {
|
|
pub media_id: MediaId,
|
|
pub isbn: Option<String>,
|
|
pub isbn13: Option<String>,
|
|
pub publisher: Option<String>,
|
|
pub language: Option<String>,
|
|
pub page_count: Option<i32>,
|
|
pub publication_date: Option<chrono::NaiveDate>,
|
|
pub series_name: Option<String>,
|
|
pub series_index: Option<f64>,
|
|
pub format: Option<String>,
|
|
pub authors: Vec<AuthorInfo>,
|
|
pub identifiers: HashMap<String, Vec<String>>,
|
|
pub created_at: DateTime<Utc>,
|
|
pub updated_at: DateTime<Utc>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub struct AuthorInfo {
|
|
pub name: String,
|
|
pub role: String,
|
|
pub file_as: Option<String>,
|
|
pub position: i32,
|
|
}
|
|
|
|
impl AuthorInfo {
|
|
pub fn new(name: String) -> Self {
|
|
Self {
|
|
name,
|
|
role: "author".to_string(),
|
|
file_as: None,
|
|
position: 0,
|
|
}
|
|
}
|
|
|
|
pub fn with_role(mut self, role: String) -> Self {
|
|
self.role = role;
|
|
self
|
|
}
|
|
|
|
pub fn with_file_as(mut self, file_as: String) -> Self {
|
|
self.file_as = Some(file_as);
|
|
self
|
|
}
|
|
|
|
pub fn with_position(mut self, position: i32) -> Self {
|
|
self.position = position;
|
|
self
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct ReadingProgress {
|
|
pub media_id: MediaId,
|
|
pub user_id: Uuid,
|
|
pub current_page: i32,
|
|
pub total_pages: Option<i32>,
|
|
pub progress_percent: f64,
|
|
pub last_read_at: DateTime<Utc>,
|
|
}
|
|
|
|
impl ReadingProgress {
|
|
pub fn new(
|
|
media_id: MediaId,
|
|
user_id: Uuid,
|
|
current_page: i32,
|
|
total_pages: Option<i32>,
|
|
) -> Self {
|
|
let progress_percent = if let Some(total) = total_pages {
|
|
if total > 0 {
|
|
(current_page as f64 / total as f64 * 100.0).min(100.0)
|
|
} else {
|
|
0.0
|
|
}
|
|
} else {
|
|
0.0
|
|
};
|
|
|
|
Self {
|
|
media_id,
|
|
user_id,
|
|
current_page,
|
|
total_pages,
|
|
progress_percent,
|
|
last_read_at: Utc::now(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum ReadingStatus {
|
|
ToRead,
|
|
Reading,
|
|
Completed,
|
|
Abandoned,
|
|
}
|
|
|
|
impl fmt::Display for ReadingStatus {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::ToRead => write!(f, "to_read"),
|
|
Self::Reading => write!(f, "reading"),
|
|
Self::Completed => write!(f, "completed"),
|
|
Self::Abandoned => write!(f, "abandoned"),
|
|
}
|
|
}
|
|
}
|