various: simplify code; work on security and performance

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I9a5114addcab5fbff430ab2b919b83466a6a6964
This commit is contained in:
raf 2026-02-02 17:32:11 +03:00
commit c4adc4e3e0
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
75 changed files with 12921 additions and 358 deletions

View file

@ -551,3 +551,431 @@ impl From<pinakes_core::model::AuditEntry> for AuditEntryResponse {
}
}
}
// 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,
}