Add CSS import/link/list/delete/content API (ticket 010)

Wires up Settings.css_entries behind a small settings API: import raw CSS
content to a file stored alongside settings.json, link external CSS
paths/URLs by reference (never deletable/copied), list all entries, delete
imported entries, and serve back imported content. CSS storage logic lives
on Settings (import_css/link_css/remove_css/css_content) with api.rs
handlers staying thin, matching the existing engine-query pattern.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
This commit is contained in:
Hugh Kaznowski
2026-09-05 12:51:22 +01:00
co-authored by Claude Sonnet 5
parent faca2af328
commit 71aaa4ebb4
4 changed files with 457 additions and 10 deletions
+56
View File
@@ -1,4 +1,5 @@
use crate::data_value::MetricPrimitiveKind;
use crate::settings::CssEntry;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize)]
@@ -45,3 +46,58 @@ pub struct TimelineParams {
pub end_ms: Option<i64>,
pub bucket_ms: Option<i64>,
}
/// Request body for `POST /api/settings/css`: raw CSS content to import and
/// store alongside the settings file.
#[derive(Debug, Deserialize)]
pub struct ImportCssRequest {
pub filename: String,
pub content: String,
}
/// Request body for `POST /api/settings/css/link`: a reference to an
/// external CSS path/URL that ottyl doesn't copy or own.
#[derive(Debug, Deserialize)]
pub struct LinkCssRequest {
pub path_or_url: String,
}
/// Response shape for a single CSS entry, used both by the list endpoint and
/// by mutation endpoints that return the affected entry. `id` is the entry's
/// real id for `Imported` entries, or a stable id derived from the
/// linked path/URL for `Linked` entries (see `settings::css_entry_id`) —
/// this lets the frontend key/reference both kinds of entries uniformly,
/// even though only `Imported` entries are deletable/content-fetchable.
#[derive(Debug, Clone, Serialize)]
pub struct CssEntryDto {
pub id: String,
pub kind: CssEntryKind,
pub filename: Option<String>,
pub path_or_url: Option<String>,
}
#[derive(Debug, Clone, Copy, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum CssEntryKind {
Imported,
Linked,
}
impl From<&CssEntry> for CssEntryDto {
fn from(entry: &CssEntry) -> Self {
match entry {
CssEntry::Imported { id, filename } => CssEntryDto {
id: id.clone(),
kind: CssEntryKind::Imported,
filename: Some(filename.clone()),
path_or_url: None,
},
CssEntry::Linked { path_or_url } => CssEntryDto {
id: crate::settings::css_entry_id(entry),
kind: CssEntryKind::Linked,
filename: None,
path_or_url: Some(path_or_url.clone()),
},
}
}
}