diff --git a/Cargo.lock b/Cargo.lock index 0feae1d..2065783 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,6 +23,21 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.18" @@ -246,6 +261,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" + [[package]] name = "byteorder" version = "1.5.0" @@ -273,6 +294,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.6", +] + [[package]] name = "clap" version = "4.5.27" @@ -334,6 +369,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.17" @@ -483,6 +524,7 @@ dependencies = [ "anyhow", "askama", "axum", + "chrono", "clap", "serde", "serde_json", @@ -782,6 +824,29 @@ dependencies = [ "tower-service", ] +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "1.5.0" @@ -943,6 +1008,16 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -1986,6 +2061,64 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" +[[package]] +name = "wasm-bindgen" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" +dependencies = [ + "bumpalo", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] + [[package]] name = "whoami" version = "1.5.2" @@ -1996,6 +2129,15 @@ dependencies = [ "wasite", ] +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/Cargo.toml b/Cargo.toml index 15d4275..f8c5a80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" anyhow = "1.0.95" askama = { version = "0.12.1"} axum = "0.8.1" +chrono = "0.4.39" clap = { version = "4.5.23", features = ["derive"] } serde = { version = "1.0.217", features = ["derive"] } serde_json = "1.0.138" diff --git a/schema.sql b/schema.sql index 21f488c..852ce4d 100644 --- a/schema.sql +++ b/schema.sql @@ -1,22 +1,22 @@ CREATE TABLE tracked_flakes ( - uri TEXT PRIMARY KEY NOT NULL + flake_uri TEXT PRIMARY KEY NOT NULL ); -CREATE TABLE flake_revisions ( +CREATE TABLE revisions ( revision_uri TEXT PRIMARY KEY NOT NULL, - uri TEXT, + flake_uri TEXT, nix_store_path TEXT, - revision TEXT, - nar_hash TEXT, - last_modified INT -); - -CREATE TABLE flake_revisions_inputs ( - flake_revision_uri TEXT NOT NULL, - input_name TEXT NOT NULL, - revision_uri TEXT, - uri TEXT, nar_hash TEXT, last_modified INT, - PRIMARY KEY (flake_revision_uri, input_name) + tracker_last_scanned INT +); + +CREATE TABLE inputs ( + revision_uri TEXT NOT NULL, + input_name TEXT NOT NULL, + locked_revision_uri TEXT, + locked_flake_uri TEXT, + locked_nar_hash TEXT, + last_modified INT, + PRIMARY KEY (revision_uri, input_name) ); diff --git a/src/bin/scan-flake.rs b/src/bin/scan-flake.rs index 532f975..5d03653 100644 --- a/src/bin/scan-flake.rs +++ b/src/bin/scan-flake.rs @@ -3,6 +3,9 @@ use anyhow::{ Context, Result, }; +use chrono::{ + Utc, +}; use clap::{ Parser, }; @@ -167,6 +170,8 @@ struct Cli { async fn main() -> Result<()> { let cli = Cli::parse(); + let scan_time = Utc::now().timestamp(); + let db = SqlitePoolOptions::new().connect("sqlite://flake-tracker.db").await?; let flake_metadata_raw = Command::new("nix") @@ -180,21 +185,21 @@ async fn main() -> Result<()> { let flake_metadata: FlakeMetadata = serde_json::from_slice(&flake_metadata_raw.stdout) .context("Failed to parse flake metadata")?; - sqlx::query("INSERT INTO flake_revisions (revision_uri, uri, nix_store_path, revision, nar_hash, last_modified) + sqlx::query("INSERT INTO revisions (revision_uri, flake_uri, nix_store_path, nar_hash, last_modified, tracker_last_scanned) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(revision_uri) DO UPDATE SET - uri=excluded.uri, + flake_uri=excluded.flake_uri, nix_store_path=excluded.nix_store_path, - revision=excluded.revision, nar_hash=excluded.nar_hash, - last_modified=excluded.last_modified + last_modified=excluded.last_modified, + tracker_last_scanned=excluded.tracker_last_scanned ") .bind(&flake_metadata.locked.flake_uri()?) .bind(&flake_metadata.resolved.flake_uri()?) .bind(&flake_metadata.path) - .bind(&flake_metadata.revision) .bind(&flake_metadata.locked.narHash) .bind(&flake_metadata.locked.lastModified) + .bind(&scan_time) .execute(&db).await?; let locks_root_name = &flake_metadata.locks.root; @@ -207,12 +212,12 @@ async fn main() -> Result<()> { let locks_input_node = flake_metadata.locks.nodes.get(&locks_input_name) .context("Failed to find lock of input")?; - sqlx::query("INSERT INTO flake_revisions_inputs (flake_revision_uri, input_name, revision_uri, uri, nar_hash, last_modified) + sqlx::query("INSERT INTO inputs (revision_uri, input_name, locked_revision_uri, locked_flake_uri, locked_nar_hash, last_modified) VALUES (?, ?, ?, ?, ?, ?) - ON CONFLICT(flake_revision_uri, input_name) DO UPDATE SET - revision_uri=excluded.revision_uri, - uri=excluded.uri, - nar_hash=excluded.nar_hash, + ON CONFLICT(revision_uri, input_name) DO UPDATE SET + locked_revision_uri=excluded.locked_revision_uri, + locked_flake_uri=excluded.locked_flake_uri, + locked_nar_hash=excluded.locked_nar_hash, last_modified=excluded.last_modified ") .bind(flake_metadata.locked.flake_uri()?) @@ -222,6 +227,14 @@ async fn main() -> Result<()> { .bind(locks_input_node.locked.clone().context("Unexpected missing lock")?.narHash) .bind(locks_input_node.locked.clone().context("Unexpected missing lock")?.lastModified) .execute(&db).await?; + + sqlx::query("INSERT INTO revisions (revision_uri, flake_uri) + VALUES (?, ?) + ON CONFLICT(revision_uri) DO NOTHING + ") + .bind(locks_input_node.locked.clone().context("Unexpected missing lock")?.flake_uri()?) + .bind(locks_input_node.original.clone().context("Unexpected missing lock")?.flake_uri()?) + .execute(&db).await?; } } diff --git a/src/storage.rs b/src/storage.rs index be3a63d..297b52d 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -31,15 +31,9 @@ impl Storage { pub async fn flakes(&self) -> Result> { sqlx::query_as(" SELECT - uri - FROM flake_revisions - UNION - SELECT - uri - FROM flake_revisions_inputs - WHERE uri IS NOT NULL - GROUP BY uri - ORDER BY uri + flake_uri + FROM revisions + ORDER BY flake_uri ") .fetch_all(&self.db) .await @@ -51,18 +45,11 @@ impl Storage { SELECT revision_uri, last_modified - FROM flake_revisions - WHERE uri = ? - UNION - SELECT - revision_uri, - last_modified - FROM flake_revisions_inputs - WHERE uri = ? + FROM revisions + WHERE flake_uri = ? ORDER BY last_modified DESC ") .bind(&uri) - .bind(&uri) .fetch_all(&self.db) .await .context("Failed to fetch data from database") @@ -71,14 +58,14 @@ impl Storage { pub async fn inputs_for_revision(&self, revision_uri: &str) -> Result> { sqlx::query_as(" SELECT - flake_revision_uri, - input_name, revision_uri, - uri, - nar_hash, + input_name, + locked_revision_uri, + locked_flake_uri, + locked_nar_hash, last_modified - FROM flake_revisions_inputs - WHERE flake_revision_uri = ? + FROM inputs + WHERE revision_uri = ? ORDER BY input_name ") .bind(&revision_uri) @@ -90,14 +77,14 @@ impl Storage { pub async fn input_of_for_revision(&self, revision_uri: &str) -> Result> { sqlx::query_as(" SELECT - flake_revision_uri, - input_name, revision_uri, - uri, - nar_hash, + input_name, + locked_revision_uri, + locked_flake_uri, + locked_nar_hash, last_modified - FROM flake_revisions_inputs - WHERE revision_uri = ? + FROM inputs + WHERE locked_revision_uri = ? ORDER BY input_name ") .bind(&revision_uri) @@ -110,20 +97,20 @@ impl Storage { sqlx::query_as(" SELECT - flake_revisions.revision_uri, - MAX(flake_revisions.last_modified) AS last_modified, - flake_revisions_inputs.input_name, - flake_revisions_inputs.uri + revisions.revision_uri, + MAX(revisions.last_modified) AS last_modified, + inputs.input_name, + inputs.locked_flake_uri FROM - flake_revisions + revisions LEFT JOIN - flake_revisions_inputs + inputs ON - flake_revisions.revision_uri = flake_revisions_inputs.flake_revision_uri + revisions.revision_uri = inputs.revision_uri WHERE - flake_revisions.uri = ? + revisions.flake_uri = ? GROUP BY - flake_revisions_inputs.input_name + inputs.input_name ") .bind(&flake_uri) .fetch_all(&self.db) @@ -135,9 +122,8 @@ impl Storage { #[derive(FromRow)] pub struct FlakeRevisionRow { pub revision_uri: String, - pub uri: Option, + pub flake_uri: Option, pub nix_store_path: Option, - pub revision: Option, pub nar_hash: Option, pub last_modified: Option, } @@ -147,13 +133,13 @@ pub struct InputForFlakeRow { pub revision_uri: String, pub last_modified: Option, pub input_name: String, - pub uri: Option, + pub locked_flake_uri: Option, } impl InputForFlakeRow { - pub fn flake_link(&self ) -> String { - match &self.uri { - Some(uri) => format!("/f/{}", urlencode(&uri)), + pub fn locked_flake_link(&self ) -> String { + match &self.locked_flake_uri { + Some(locked_flake_uri) => format!("/f/{}", urlencode(&locked_flake_uri)), None => String::from("#"), } } @@ -167,47 +153,47 @@ pub struct RevisionListModel { } impl RevisionListModel { - pub fn link(&self ) -> String { + pub fn revision_link(&self ) -> String { format!("/r/{}", urlencode(&self.revision_uri)) } } #[derive(FromRow)] pub struct FlakeUri { - pub uri: String, + pub flake_uri: String, } impl FlakeUri { - pub fn link(&self ) -> String { - format!("/f/{}", urlencode(&self.uri)) + pub fn flake_link(&self ) -> String { + format!("/f/{}", urlencode(&self.flake_uri)) } } #[derive(FromRow)] pub struct InputModel { - pub flake_revision_uri: String, + pub revision_uri: String, pub input_name: String, - pub revision_uri: Option, - pub uri: Option, - pub nar_hash: Option, + pub locked_revision_uri: Option, + pub locked_flake_uri: Option, + pub locked_nar_hash: Option, pub last_modified: Option, } impl InputModel { - pub fn flake_revision_link(&self) -> String { - format!("/r/{}", urlencode(&self.flake_revision_uri)) + pub fn revision_link(&self) -> String { + format!("/r/{}", urlencode(&self.revision_uri)) } - pub fn revision_link(&self) -> String { - match &self.revision_uri { - Some(revision_uri) => format!("/r/{}", urlencode(&revision_uri)), + pub fn locked_revision_link(&self) -> String { + match &self.locked_revision_uri { + Some(locked_revision_uri) => format!("/r/{}", urlencode(&locked_revision_uri)), None => String::from("#"), } } - pub fn flake_link(&self) -> String { - match &self.uri { - Some(uri) => format!("/f/{}", urlencode(&uri)), + pub fn locked_flake_link(&self) -> String { + match &self.locked_flake_uri { + Some(locked_flake_uri) => format!("/f/{}", urlencode(&locked_flake_uri)), None => String::from("#"), } } diff --git a/templates/flake.html b/templates/flake.html index 1b318a9..e97286b 100644 --- a/templates/flake.html +++ b/templates/flake.html @@ -11,7 +11,7 @@ @@ -22,9 +22,9 @@
  • {{ input.input_name }}
      - {% match input.uri %} - {% when Some with (uri) %} -
    • Flake: {{ uri }}
    • + {% match input.locked_flake_uri %} + {% when Some with (locked_flake_uri) %} +
    • Flake: {{ locked_flake_uri }}
    • {% when None %} {% endmatch %}
    diff --git a/templates/flakes.html b/templates/flakes.html index d4ab143..c0f20d4 100644 --- a/templates/flakes.html +++ b/templates/flakes.html @@ -7,7 +7,7 @@ diff --git a/templates/revision.html b/templates/revision.html index 275d585..358a3ea 100644 --- a/templates/revision.html +++ b/templates/revision.html @@ -15,14 +15,14 @@
  • {{ input.input_name }}
      - {% match input.uri %} - {% when Some with (uri) %} -
    • Flake: {{ uri }}
    • + {% match input.locked_flake_uri %} + {% when Some with (locked_flake_uri) %} +
    • Flake: {{ locked_flake_uri }}
    • {% when None %} {% endmatch %} - {% match input.revision_uri %} - {% when Some with (revision_uri) %} -
    • Revision: {{ revision_uri }}
    • + {% match input.locked_revision_uri %} + {% when Some with (locked_revision_uri) %} +
    • Revision: {{ locked_revision_uri }}
    • {% when None %} {% endmatch %}
    @@ -34,7 +34,7 @@ {% endblock %}