- Added package.json with dependencies and scripts for development, build, and linting. - Created postcss.config.mjs for Tailwind CSS integration. - Added SVG assets for UI components including file, globe, next, vercel, and window icons. - Configured TypeScript with tsconfig.json for strict type checking and module resolution.
185 lines
5.1 KiB
Rust
185 lines
5.1 KiB
Rust
//! Request and Response DTOs
|
|
//!
|
|
//! Data Transfer Objects for the API.
|
|
//! Uses domain newtypes for validation instead of the validator crate.
|
|
|
|
use chrono::{DateTime, Utc};
|
|
use domain::{Email, Password};
|
|
use serde::{Deserialize, Serialize};
|
|
use uuid::Uuid;
|
|
|
|
/// Login request with validated email and password newtypes
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct LoginRequest {
|
|
/// Email is validated on deserialization
|
|
pub email: Email,
|
|
/// Password is validated on deserialization (min 8 chars)
|
|
pub password: Password,
|
|
}
|
|
|
|
/// Register request with validated email and password newtypes
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct RegisterRequest {
|
|
/// Email is validated on deserialization
|
|
pub email: Email,
|
|
/// Password is validated on deserialization (min 8 chars)
|
|
pub password: Password,
|
|
}
|
|
|
|
/// User response DTO
|
|
#[derive(Debug, Serialize)]
|
|
pub struct UserResponse {
|
|
pub id: Uuid,
|
|
pub email: String,
|
|
pub created_at: DateTime<Utc>,
|
|
}
|
|
|
|
/// JWT token response
|
|
#[derive(Debug, Serialize)]
|
|
pub struct TokenResponse {
|
|
pub access_token: String,
|
|
pub token_type: String,
|
|
pub expires_in: u64,
|
|
}
|
|
|
|
/// System configuration response
|
|
#[derive(Debug, Serialize)]
|
|
pub struct ConfigResponse {
|
|
pub allow_registration: bool,
|
|
}
|
|
|
|
// ============================================================================
|
|
// Channel DTOs
|
|
// ============================================================================
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct CreateChannelRequest {
|
|
pub name: String,
|
|
pub description: Option<String>,
|
|
/// IANA timezone, e.g. "UTC" or "America/New_York"
|
|
pub timezone: String,
|
|
}
|
|
|
|
/// All fields are optional — only provided fields are updated.
|
|
#[derive(Debug, Deserialize)]
|
|
pub struct UpdateChannelRequest {
|
|
pub name: Option<String>,
|
|
pub description: Option<String>,
|
|
pub timezone: Option<String>,
|
|
/// Replace the entire schedule config (template import/edit)
|
|
pub schedule_config: Option<domain::ScheduleConfig>,
|
|
pub recycle_policy: Option<domain::RecyclePolicy>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct ChannelResponse {
|
|
pub id: Uuid,
|
|
pub owner_id: Uuid,
|
|
pub name: String,
|
|
pub description: Option<String>,
|
|
pub timezone: String,
|
|
pub schedule_config: domain::ScheduleConfig,
|
|
pub recycle_policy: domain::RecyclePolicy,
|
|
pub created_at: DateTime<Utc>,
|
|
pub updated_at: DateTime<Utc>,
|
|
}
|
|
|
|
impl From<domain::Channel> for ChannelResponse {
|
|
fn from(c: domain::Channel) -> Self {
|
|
Self {
|
|
id: c.id,
|
|
owner_id: c.owner_id,
|
|
name: c.name,
|
|
description: c.description,
|
|
timezone: c.timezone,
|
|
schedule_config: c.schedule_config,
|
|
recycle_policy: c.recycle_policy,
|
|
created_at: c.created_at,
|
|
updated_at: c.updated_at,
|
|
}
|
|
}
|
|
}
|
|
|
|
// ============================================================================
|
|
// EPG / playback DTOs
|
|
// ============================================================================
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct MediaItemResponse {
|
|
pub id: String,
|
|
pub title: String,
|
|
pub content_type: domain::ContentType,
|
|
pub duration_secs: u32,
|
|
pub genres: Vec<String>,
|
|
pub year: Option<u16>,
|
|
pub tags: Vec<String>,
|
|
}
|
|
|
|
impl From<domain::MediaItem> for MediaItemResponse {
|
|
fn from(i: domain::MediaItem) -> Self {
|
|
Self {
|
|
id: i.id.into_inner(),
|
|
title: i.title,
|
|
content_type: i.content_type,
|
|
duration_secs: i.duration_secs,
|
|
genres: i.genres,
|
|
year: i.year,
|
|
tags: i.tags,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct ScheduledSlotResponse {
|
|
pub id: Uuid,
|
|
pub start_at: DateTime<Utc>,
|
|
pub end_at: DateTime<Utc>,
|
|
pub item: MediaItemResponse,
|
|
pub source_block_id: Uuid,
|
|
}
|
|
|
|
impl From<domain::ScheduledSlot> for ScheduledSlotResponse {
|
|
fn from(s: domain::ScheduledSlot) -> Self {
|
|
Self {
|
|
id: s.id,
|
|
start_at: s.start_at,
|
|
end_at: s.end_at,
|
|
item: s.item.into(),
|
|
source_block_id: s.source_block_id,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// What is currently playing on a channel.
|
|
/// A 204 No Content response is returned instead when there is no active slot (no-signal).
|
|
#[derive(Debug, Serialize)]
|
|
pub struct CurrentBroadcastResponse {
|
|
pub slot: ScheduledSlotResponse,
|
|
/// Seconds elapsed since the start of the current item — use this as the
|
|
/// initial seek position for the player.
|
|
pub offset_secs: u32,
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub struct ScheduleResponse {
|
|
pub id: Uuid,
|
|
pub channel_id: Uuid,
|
|
pub valid_from: DateTime<Utc>,
|
|
pub valid_until: DateTime<Utc>,
|
|
pub generation: u32,
|
|
pub slots: Vec<ScheduledSlotResponse>,
|
|
}
|
|
|
|
impl From<domain::GeneratedSchedule> for ScheduleResponse {
|
|
fn from(s: domain::GeneratedSchedule) -> Self {
|
|
Self {
|
|
id: s.id,
|
|
channel_id: s.channel_id,
|
|
valid_from: s.valid_from,
|
|
valid_until: s.valid_until,
|
|
generation: s.generation,
|
|
slots: s.slots.into_iter().map(Into::into).collect(),
|
|
}
|
|
}
|
|
}
|