OXYGENHUB
◆ main spectra / crates / spectra-core / src / projects.rs
2.2 KiB · Rust · last changed Spectra stands alone: its own identities, signatures and CBOR; projects of any kind of work (#26, #27) 1 day ago source blame history raw
1
//! Projects (spec §23.1): any kind of work a group of agents shares. A
2
//! project has a name, a description, and optionally a folder and a link;
3
//! Spectra records where the folder is and reads nothing inside it. The
4
//! owner links projects (`project.linked`: one depends on another), which
5
//! is what `to: "downstream"` follows.
6
7
use crate::event::Event;
8
use anyhow::{Context, Result};
9
use serde_json::Value as Json;
10
use sqlx::SqliteConnection;
11
use std::path::Path;
12
13
/// Kinds of event the owner signs about projects.
14
pub const KINDS: &[&str] = &["project.added", "project.linked", "project.unlinked"];
15
16
/// A project's name from a folder: its name, lower-cased, made valid.
17
pub fn default_name(path: &Path) -> Result<String> {
18
    let base = path.canonicalize()?.file_name().and_then(|n| n.to_str()).map(str::to_lowercase).context("the path has no name")?;
19
    Ok(base.chars().map(|c| if c.is_ascii_alphanumeric() || c == '-' { c } else { '-' }).collect())
20
}
21
22
/// Apply a project event to the tables.
23
pub async fn project(conn: &mut SqliteConnection, e: &Event) -> sqlx::Result<()> {
24
    let p = e.json();
25
    let s = |k: &str| p.get(k).and_then(Json::as_str).map(String::from);
26
    match e.kind.as_str() {
27
        "project.added" => {
28
            let Some(name) = s("name") else { return Ok(()) };
29
            sqlx::query("INSERT OR REPLACE INTO projects (name, path, description, url, seq) VALUES (?, ?, ?, ?, ?)")
30
                .bind(&name)
31
                .bind(s("path").filter(|p| !p.is_empty()))
32
                .bind(s("description").unwrap_or_default())
33
                .bind(s("url"))
34
                .bind(e.seq as i64)
35
                .execute(&mut *conn)
36
                .await?;
37
        }
38
        "project.linked" | "project.unlinked" => {
39
            let (Some(project), Some(on)) = (s("project"), s("depends_on")) else { return Ok(()) };
40
            let sql = if e.kind == "project.linked" {
41
                "INSERT OR IGNORE INTO project_relations (project, depends_on) VALUES (?, ?)"
42
            } else {
43
                "DELETE FROM project_relations WHERE project = ? AND depends_on = ?"
44
            };
45
            sqlx::query(sql).bind(project).bind(on).execute(&mut *conn).await?;
46
        }
47
        _ => {}
48
    }
49
    Ok(())
50
}