feat: initialize k-tv-frontend with Next.js and Tailwind CSS
- 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.
This commit is contained in:
184
k-tv-backend/api/src/dto.rs
Normal file
184
k-tv-backend/api/src/dto.rs
Normal file
@@ -0,0 +1,184 @@
|
||||
//! 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user