Skip to main content

headless_lms_models/
file_uploads.rs

1use crate::prelude::*;
2use chrono::Duration;
3
4/// Records a stored object. `size_bytes` is the byte count measured while receiving it; `None`
5/// where the upload path does not count bytes.
6pub async fn insert(
7    conn: &mut PgConnection,
8    name: &str,
9    path: &str,
10    mime: &str,
11    uploader: Option<Uuid>,
12    size_bytes: Option<i64>,
13) -> ModelResult<Uuid> {
14    let res = sqlx::query!(
15        r#"
16INSERT INTO file_uploads(path, name, mime, uploaded_by_user, size_bytes)
17VALUES ($1, $2, $3, $4, $5)
18RETURNING *
19"#,
20        path,
21        name,
22        mime,
23        uploader,
24        size_bytes
25    )
26    .fetch_one(conn)
27    .await?;
28    Ok(res.id)
29}
30
31/// A stored file's name and object-store path.
32#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct FileUploadRef {
34    pub id: Uuid,
35    pub name: String,
36    pub path: String,
37}
38
39/// The named files, in unspecified order and omitting deleted ones. Callers that need a
40/// particular order must impose it themselves.
41pub async fn get_many(conn: &mut PgConnection, ids: &[Uuid]) -> ModelResult<Vec<FileUploadRef>> {
42    let res = sqlx::query_as!(
43        FileUploadRef,
44        "
45SELECT id,
46  name,
47  path
48FROM file_uploads
49WHERE id = ANY($1)
50  AND deleted_at IS NULL
51",
52        ids
53    )
54    .fetch_all(conn)
55    .await?;
56    Ok(res)
57}
58
59pub async fn get_filename(conn: &mut PgConnection, path: &str) -> ModelResult<String> {
60    let res = sqlx::query!(
61        r#"
62SELECT *
63FROM file_uploads
64WHERE path = $1
65"#,
66        path,
67    )
68    .fetch_one(conn)
69    .await?;
70    Ok(res.name)
71}
72
73pub async fn delete_and_fetch_path(conn: &mut PgConnection, id: Uuid) -> ModelResult<String> {
74    let res = sqlx::query!(
75        "
76UPDATE file_uploads
77SET deleted_at = now()
78WHERE id = $1
79AND deleted_at IS NULL
80RETURNING *
81",
82        id
83    )
84    .fetch_one(conn)
85    .await?;
86    Ok(res.path)
87}
88
89/// Moves an upload's creation time `age` into the past, to bring it within a retention window.
90///
91/// Shifts the row rather than the clock because the retention filters compare against Postgres
92/// `now()`, which no Rust-side clock reaches.
93pub async fn backdate(conn: &mut PgConnection, id: Uuid, age: Duration) -> ModelResult<()> {
94    sqlx::query!(
95        "UPDATE file_uploads SET created_at = now() - $2::interval WHERE id = $1",
96        id,
97        age as Duration
98    )
99    .execute(conn)
100    .await?;
101    Ok(())
102}