Skip to main content

umbral_admin/
files.rs

1//! File descriptor helpers — MIME / extension → preview kind, plus the
2//! `serde_json::Value` shape the file-preview macros consume.
3//!
4//! ⚠ A first-class `File` / `Image` ORM field type is deferred. For now
5//! you store file paths in `Text` columns and emit a descriptor JSON
6//! string for the admin to render. This module owns the descriptor
7//! shape so other plugins (and user code) can construct one without
8//! re-implementing the MIME table.
9
10/// Resolve the `preview_kind` from a MIME type and file extension.
11///
12/// Returns a `&'static str` matching one of: `image`, `pdf`, `video`,
13/// `audio`, `text`, `code`, `download`. Extension wins over MIME so a
14/// `text/plain; charset=utf-8` `.py` file resolves to `code`, not `text`.
15pub fn resolve_preview_kind(mime: &str, filename: &str) -> &'static str {
16    let ext = filename.rsplit('.').next().unwrap_or("").to_lowercase();
17    // Check extension-based code/text first so that e.g. "text/plain; charset=utf-8"
18    // on a .py file resolves to "code" rather than "text".
19    match ext.as_str() {
20        "rs" | "py" | "js" | "ts" | "jsx" | "tsx" | "json" | "toml" | "yaml" | "yml" | "html"
21        | "css" | "sql" | "sh" | "bash" | "zsh" | "fish" | "md" | "mdx" => return "code",
22        "txt" | "log" => return "text",
23        _ => {}
24    }
25    // Then MIME-based rules.
26    if mime.starts_with("image/") {
27        return "image";
28    }
29    if mime == "application/pdf" {
30        return "pdf";
31    }
32    if mime.starts_with("video/") {
33        return "video";
34    }
35    if mime.starts_with("audio/") {
36        return "audio";
37    }
38    if mime.starts_with("text/plain") {
39        return "text";
40    }
41    "download"
42}
43
44/// Build a file descriptor JSON value.
45///
46/// `url` is the pre-signed / auth-checked URL the admin should embed;
47/// `thumbnail_url` is optional and only set for the `image` kind where
48/// a thumbnail has been generated.
49pub fn file_descriptor(
50    filename: &str,
51    size: u64,
52    mime: &str,
53    url: &str,
54    thumbnail_url: Option<&str>,
55) -> serde_json::Value {
56    let preview_kind = resolve_preview_kind(mime, filename);
57    let language: Option<&str> = if preview_kind == "code" {
58        Some(filename.rsplit('.').next().unwrap_or("text"))
59    } else {
60        None
61    };
62    serde_json::json!({
63        "filename":      filename,
64        "size":          size,
65        "mime":          mime,
66        "preview_kind":  preview_kind,
67        "url":           url,
68        "thumbnail_url": thumbnail_url,
69        "language":      language,
70    })
71}