From 3eeb585d0bfefba2011a5161162dd51df6503411 Mon Sep 17 00:00:00 2001 From: siddhantCodes Date: Thu, 19 Oct 2023 22:50:22 +0530 Subject: [PATCH 01/44] add nix support --- README.md | 28 ++++++++++++++ flake.lock | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++ flake.nix | 62 ++++++++++++++++++++++++++++++ 3 files changed, 201 insertions(+) create mode 100644 flake.lock create mode 100644 flake.nix diff --git a/README.md b/README.md index 2da68ea2b6..fac5850f5d 100644 --- a/README.md +++ b/README.md @@ -219,6 +219,34 @@ We also offer our own hosting solution for your static and dynamic sites. Using managed hosting solution, that a non programmers can use with ease. +## Usage with Nix + +```sh +nix run github:fastn-stack/fastn +``` + +In a `flake.nix` file: + +```Nix +{ + inputs = { + flake-utils.url = "github:numtide/flake-utils"; + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + fastn.url = "github:fastn-stack/fastn"; + }; + + outputs = { self, flake-utils, nixpkgs, fastn }: + flake-utils.lib.eachDefaultSystem (system: + rec { + # nix develop + devShell = pkgs.mkShell { + nativeBuildInputs = [ fastn ]; + }; + } + ); +} +``` + ## Contributors diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000000..57de9e3c0f --- /dev/null +++ b/flake.lock @@ -0,0 +1,111 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1694529238, + "narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "ff7b65b44d01cf9ba6a71320833626af21126384", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "naersk": { + "inputs": { + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1697664192, + "narHash": "sha256-nRTG3rYEGFV2+putRiC96+kNXDyKaPJgT6K/1FWN7yo=", + "owner": "nix-community", + "repo": "naersk", + "rev": "636a9b5dd7f2ad7d7c3af929ecf95e4d4fab9e97", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "naersk", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1683236849, + "narHash": "sha256-Y7PNBVLOBvZrmrFmHgXUBUA1lM72tl6JGIn1trOeuyE=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "374ffe54403c3c42d97a513ac7a14ce1b5b86e30", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "nixpkgs-mozilla": { + "flake": false, + "locked": { + "lastModified": 1695805681, + "narHash": "sha256-1ElPLD8eFfnuIk0G52HGGpRtQZ4QPCjChRlEOfkZ5ro=", + "owner": "mozilla", + "repo": "nixpkgs-mozilla", + "rev": "6eabade97bc28d707a8b9d82ad13ef143836736e", + "type": "github" + }, + "original": { + "owner": "mozilla", + "repo": "nixpkgs-mozilla", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1697660476, + "narHash": "sha256-w5/4HKG6/TPOFUki4rYUbME8zC6+suT6hSunyFD4BlI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "964a525d67348323be4fa100345d37b361ebd36e", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "naersk": "naersk", + "nixpkgs": "nixpkgs_2", + "nixpkgs-mozilla": "nixpkgs-mozilla" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000000..d6088e99b0 --- /dev/null +++ b/flake.nix @@ -0,0 +1,62 @@ +{ + inputs = { + flake-utils.url = "github:numtide/flake-utils"; + + naersk.url = "github:nix-community/naersk"; + + nixpkgs-mozilla = { + url = "github:mozilla/nixpkgs-mozilla"; + flake = false; + }; + + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + + }; + + outputs = { self, flake-utils, nixpkgs, nixpkgs-mozilla, naersk }: + flake-utils.lib.eachDefaultSystem (system: + let + pkgs = (import nixpkgs) { + inherit system; + + overlays = [ + (import nixpkgs-mozilla) + ]; + }; + + toolchain = (pkgs.rustChannelOf { + rustToolchain = ./rust-toolchain; + sha256 = "sha256-Q9UgzzvxLi4x9aWUJTn+/5EXekC98ODRU1TwhUs9RnY="; + }).rust; + + naersk' = pkgs.callPackage naersk { + cargo = toolchain; + rustc = toolchain; + }; + + fastnp = naersk'.buildPackage { + name = "fastn"; + version = "0.3.0"; + src = ./.; + + nativeBuildInputs = with pkgs; [ pkg-config openssl.dev ]; + }; + in + rec { + # For `nix build` & `nix run`: + defaultPackage = fastnp; + + packages = { + fastn = fastnp; + }; + + # nix develop + devShell = pkgs.mkShell { + nativeBuildInputs = [ toolchain pkgs.pkg-config pkgs.openssl.dev ]; + }; + + formatter = pkgs.nixpkgs-fmt; + } + ); +} + From 11af173f8de3dcce1dad591912742393fa1f5865 Mon Sep 17 00:00:00 2001 From: siddhantCodes Date: Thu, 19 Oct 2023 23:04:58 +0530 Subject: [PATCH 02/44] bump rust-toolchain in flake.nix to 1.73 --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index d6088e99b0..be326f2781 100644 --- a/flake.nix +++ b/flake.nix @@ -26,7 +26,7 @@ toolchain = (pkgs.rustChannelOf { rustToolchain = ./rust-toolchain; - sha256 = "sha256-Q9UgzzvxLi4x9aWUJTn+/5EXekC98ODRU1TwhUs9RnY="; + sha256 = "sha256-rLP8+fTxnPHoR96ZJiCa/5Ans1OojI7MLsmSqR2ip8o="; }).rust; naersk' = pkgs.callPackage naersk { From 90c8b6c9f45b35e670166d8955c68e49e34b07e6 Mon Sep 17 00:00:00 2001 From: Amit Upadhyay Date: Sun, 22 Oct 2023 15:26:54 +0530 Subject: [PATCH 03/44] package-proxy integration --- fastn-core/src/http.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/fastn-core/src/http.rs b/fastn-core/src/http.rs index 145d66bb34..6b81901445 100644 --- a/fastn-core/src/http.rs +++ b/fastn-core/src/http.rs @@ -329,11 +329,17 @@ where F: FnOnce(String) -> T + Copy, T: futures::Future> + Send + 'static, { - if url[1..].contains("://") || url.starts_with("//") { - f(url).await + let mut url = if url[1..].contains("://") || url.starts_with("//") { + url } else { - f(format!("https://{}", url)).await + format!("https://{}", url) + }; + + if let Ok(package_proxy) = std::env::var("FASTN_PACKAGE_PROXY") { + url = format!("{}/proxy/?url={}", package_proxy, url); } + + f(url).await } #[tracing::instrument] From 3c39f118daecb9ade79790217b4a6bd5460db0f2 Mon Sep 17 00:00:00 2001 From: Amit Upadhyay Date: Sun, 22 Oct 2023 15:41:24 +0530 Subject: [PATCH 04/44] using observer from external repo --- Cargo.lock | 1 + Cargo.toml | 6 +- fastn-observer/Cargo.toml | 11 -- fastn-observer/src/duration_display.rs | 21 --- fastn-observer/src/field.rs | 26 ---- fastn-observer/src/formatter.rs | 207 ------------------------- fastn-observer/src/layer.rs | 130 ---------------- fastn-observer/src/lib.rs | 33 ---- fastn-observer/src/main.rs | 31 ---- fastn-observer/src/opened_span.rs | 47 ------ fastn-observer/src/tree.rs | 40 ----- 11 files changed, 5 insertions(+), 548 deletions(-) delete mode 100644 fastn-observer/Cargo.toml delete mode 100644 fastn-observer/src/duration_display.rs delete mode 100644 fastn-observer/src/field.rs delete mode 100644 fastn-observer/src/formatter.rs delete mode 100644 fastn-observer/src/layer.rs delete mode 100644 fastn-observer/src/lib.rs delete mode 100644 fastn-observer/src/main.rs delete mode 100644 fastn-observer/src/opened_span.rs delete mode 100644 fastn-observer/src/tree.rs diff --git a/Cargo.lock b/Cargo.lock index 3551093146..ffed99485b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1713,6 +1713,7 @@ dependencies = [ [[package]] name = "fastn-observer" version = "0.1.0" +source = "git+https://github.com/fastn-stack/fastn-observer?rev=47b29ea#47b29eacf78f2ec96a4932631282db9f262ce5a4" dependencies = [ "ansi_term", "smallvec", diff --git a/Cargo.toml b/Cargo.toml index 9cb0ec96fd..6a0494a7c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,6 @@ members = [ "fastn-issues", "fastn-js", "fastn-grammar", - "fastn-observer", # "fastn-wasm", # "fastn-runtime", ] @@ -84,7 +83,6 @@ fastn-core = { path = "fastn-core" } fastn-issues = { path = "fastn-issues" } fastn-package = { path = "fastn-package" } fastn-runtime = { path = "fastn-runtime" } -fastn-observer = { path = "fastn-observer" } fastn-wasm = { path = "fastn-wasm" } fastn-grammar = { path = "fastn-grammar" } fluent = "0.16" @@ -139,6 +137,10 @@ zip = "0.6" prettify-js = "0.1.0" indexmap = { version = "2", features = ["serde"] } +[workspace.dependencies.fastn-observer] +git = "https://github.com/fastn-stack/fastn-observer" +rev = "47b29ea" + [workspace.dependencies.rusqlite] version = "0.29.0" features = [ diff --git a/fastn-observer/Cargo.toml b/fastn-observer/Cargo.toml deleted file mode 100644 index 392c9fa3c9..0000000000 --- a/fastn-observer/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "fastn-observer" -version = "0.1.0" -edition = "2021" - -[dependencies] -tracing.workspace = true -tracing-subscriber.workspace = true -tokio.workspace = true -smallvec.workspace = true -ansi_term.workspace = true \ No newline at end of file diff --git a/fastn-observer/src/duration_display.rs b/fastn-observer/src/duration_display.rs deleted file mode 100644 index b5335507bd..0000000000 --- a/fastn-observer/src/duration_display.rs +++ /dev/null @@ -1,21 +0,0 @@ -// borrowed from https://github.com/QnnOkabayashi/tracing-forest/ (license: MIT) - -pub struct DurationDisplay(pub(crate) f64); - -// Taken from chrono -impl std::fmt::Display for DurationDisplay { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - let mut t = self.0; - for unit in ["ns", "µs", "ms", "s"] { - if t < 10.0 { - return write!(f, "{:.2}{}", t, unit); - } else if t < 100.0 { - return write!(f, "{:.1}{}", t, unit); - } else if t < 1000.0 { - return write!(f, "{:.0}{}", t, unit); - } - t /= 1000.0; - } - write!(f, "{:.0}s", t * 1000.0) - } -} diff --git a/fastn-observer/src/field.rs b/fastn-observer/src/field.rs deleted file mode 100644 index d6764aa937..0000000000 --- a/fastn-observer/src/field.rs +++ /dev/null @@ -1,26 +0,0 @@ -// borrowed from https://github.com/QnnOkabayashi/tracing-forest/ (license: MIT) - -pub type FieldSet = smallvec::SmallVec<[Field; 3]>; - -/// A key-value pair recorded from trace data. -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub struct Field { - key: &'static str, - value: String, -} - -impl Field { - pub(crate) fn new(key: &'static str, value: String) -> Self { - Field { key, value } - } - - /// Returns the field's key. - pub fn key(&self) -> &'static str { - self.key - } - - /// Returns the field's value. - pub fn value(&self) -> &str { - &self.value - } -} diff --git a/fastn-observer/src/formatter.rs b/fastn-observer/src/formatter.rs deleted file mode 100644 index d0b5ed9052..0000000000 --- a/fastn-observer/src/formatter.rs +++ /dev/null @@ -1,207 +0,0 @@ -// borrowed from https://github.com/QnnOkabayashi/tracing-forest/ (license: MIT) - -pub fn write_immediate( - _event: &fastn_observer::Event, - _current: Option<&tracing_subscriber::registry::SpanRef>, -) -> std::io::Result<()> -where - S: for<'a> tracing_subscriber::registry::LookupSpan<'a>, -{ - // dbg!(event); - Ok(()) -} - -type IndentVec = smallvec::SmallVec<[Indent; 32]>; - -use ansi_term::Color; - -/// Format logs for pretty printing. -/// -/// # Examples -/// -/// An arbitrarily complex example: -/// ```log -/// INFO try_from_entry_ro [ 324µs | 8.47% / 100.00% ] -/// INFO ┝━ server::internal_search [ 296µs | 19.02% / 91.53% ] -/// INFO │ ┝━ i [filter.info]: Some filter info... -/// INFO │ ┝━ server::search [ 226µs | 10.11% / 70.01% ] -/// INFO │ │ ┝━ be::search [ 181µs | 6.94% / 55.85% ] -/// INFO │ │ │ ┕━ be::search -> filter2idl [ 158µs | 19.65% / 48.91% ] -/// INFO │ │ │ ┝━ be::idl_arc_sqlite::get_idl [ 20.4µs | 6.30% ] -/// INFO │ │ │ │ ┕━ i [filter.info]: Some filter info... -/// INFO │ │ │ ┕━ be::idl_arc_sqlite::get_idl [ 74.3µs | 22.96% ] -/// ERROR │ │ │ ┝━ 🚨 [admin.error]: On no, an admin error occurred :( -/// DEBUG │ │ │ ┝━ 🐛 [debug]: An untagged debug log -/// INFO │ │ │ ┕━ i [admin.info]: there's been a big mistake | alive: false | status: "very sad" -/// INFO │ │ ┕━ be::idl_arc_sqlite::get_identry [ 13.1µs | 4.04% ] -/// ERROR │ │ ┝━ 🔐 [security.critical]: A security critical log -/// INFO │ │ ┕━ 🔓 [security.access]: A security access log -/// INFO │ ┕━ server::search [ 8.08µs | 2.50% ] -/// WARN │ ┕━ 🚧 [filter.warn]: Some filter warning -/// TRACE ┕━ 📍 [trace]: Finished! -/// ``` -#[derive(Debug)] -pub struct Pretty; - -impl Pretty { - pub fn fmt(&self, tree: &fastn_observer::Tree) -> Result { - let mut writer = String::with_capacity(256); - - Pretty::format_tree(tree, None, &mut IndentVec::new(), &mut writer)?; - - Ok(writer) - } -} - -impl Pretty { - fn format_tree( - tree: &fastn_observer::Tree, - duration_root: Option, - indent: &mut IndentVec, - writer: &mut String, - ) -> std::fmt::Result { - match tree { - fastn_observer::Tree::Event(event) => { - Pretty::format_shared(&event.shared, writer)?; - Pretty::format_indent(indent, writer)?; - Pretty::format_event(event, writer) - } - fastn_observer::Tree::Span(span) => { - Pretty::format_shared(&span.shared, writer)?; - Pretty::format_indent(indent, writer)?; - Pretty::format_span(span, duration_root, indent, writer) - } - } - } - - fn format_shared(shared: &fastn_observer::Shared, writer: &mut String) -> std::fmt::Result { - use std::fmt::Write; - - write!(writer, "{:<8} ", ColorLevel(shared.level)) - } - - fn format_indent(indent: &[Indent], writer: &mut String) -> std::fmt::Result { - use std::fmt::Write; - - for indent in indent { - writer.write_str(indent.repr())?; - } - Ok(()) - } - - fn format_event(event: &fastn_observer::Event, writer: &mut String) -> std::fmt::Result { - use std::fmt::Write; - - // write!(writer, "{} [{}]: ", tag.icon(), tag)?; - - if let Some(ref message) = event.message { - writer.write_str(message)?; - } - - for field in event.shared.fields.iter() { - write!( - writer, - " | {} {}: {}", - fastn_observer::DurationDisplay(event.shared.on.as_nanos() as f64), - field.key(), - field.value() - )?; - } - - writeln!(writer) - } - - fn format_span( - span: &fastn_observer::Span, - duration_root: Option, - indent: &mut IndentVec, - writer: &mut String, - ) -> std::fmt::Result { - use std::fmt::Write; - - let total_duration = span.duration.as_nanos() as f64; - let root_duration = duration_root.unwrap_or(total_duration); - - write!( - writer, - "{} {} [ {} ] ", - fastn_observer::DurationDisplay(span.shared.on.as_nanos() as f64), - span.name, - fastn_observer::DurationDisplay(total_duration) - )?; - - for (n, field) in span.shared.fields.iter().enumerate() { - write!( - writer, - "{} {}: {}", - if n == 0 { "" } else { " |" }, - field.key(), - field.value() - )?; - } - writeln!(writer)?; - - if let Some((last, remaining)) = span.nodes.split_last() { - match indent.last_mut() { - Some(edge @ Indent::Turn) => *edge = Indent::Null, - Some(edge @ Indent::Fork) => *edge = Indent::Line, - _ => {} - } - - indent.push(Indent::Fork); - - for tree in remaining { - if let Some(edge) = indent.last_mut() { - *edge = Indent::Fork; - } - Pretty::format_tree(tree, Some(root_duration), indent, writer)?; - } - - if let Some(edge) = indent.last_mut() { - *edge = Indent::Turn; - } - Pretty::format_tree(last, Some(root_duration), indent, writer)?; - - indent.pop(); - } - - Ok(()) - } -} - -enum Indent { - Null, - Line, - Fork, - Turn, -} - -impl Indent { - fn repr(&self) -> &'static str { - match self { - Self::Null => " ", - Self::Line => "│ ", - Self::Fork => "┝━ ", - Self::Turn => "┕━ ", - } - } -} - -// From tracing-tree -struct ColorLevel(tracing::Level); - -impl std::fmt::Display for ColorLevel { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let color = match self.0 { - tracing::Level::TRACE => Color::Purple, - tracing::Level::DEBUG => Color::Blue, - tracing::Level::INFO => Color::Green, - tracing::Level::WARN => Color::RGB(252, 234, 160), // orange - tracing::Level::ERROR => Color::Red, - }; - let style = color.bold(); - write!(f, "{}", style.prefix())?; - f.pad(self.0.as_str())?; - write!(f, "{}", style.suffix()) - } -} diff --git a/fastn-observer/src/layer.rs b/fastn-observer/src/layer.rs deleted file mode 100644 index a2a80ba001..0000000000 --- a/fastn-observer/src/layer.rs +++ /dev/null @@ -1,130 +0,0 @@ -// borrowed from https://github.com/QnnOkabayashi/tracing-forest/ (license: MIT) - -pub const SPAN_NOT_IN_CONTEXT: &str = "Span not in context, this is a bug"; -pub const OPENED_SPAN_NOT_IN_EXTENSIONS: &str = - "Span extension doesn't contain `OpenedSpan`, this is a bug"; -pub const WRITING_URGENT_ERROR: &str = "writing_urgent failed, this is a bug"; - -#[derive(Default)] -pub struct Layer {} - -impl tracing_subscriber::Layer for Layer -where - S: tracing::Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a> + std::fmt::Debug, -{ - fn on_new_span( - &self, - attrs: &tracing::span::Attributes, - id: &tracing::Id, - ctx: tracing_subscriber::layer::Context, - ) { - let span = ctx.span(id).expect(SPAN_NOT_IN_CONTEXT); - let opened = fastn_observer::OpenedSpan::new( - attrs, - span.parent().and_then(|v| { - v.extensions() - .get::() - .map(|v| v.start) - }), - ); - - let mut extensions = span.extensions_mut(); - extensions.insert(opened); - } - - fn on_event(&self, event: &tracing::Event, ctx: tracing_subscriber::layer::Context) { - struct Visitor { - message: Option, - fields: fastn_observer::FieldSet, - immediate: bool, - } - - impl tracing::field::Visit for Visitor { - fn record_bool(&mut self, field: &tracing::field::Field, value: bool) { - match field.name() { - "immediate" => self.immediate |= value, - _ => self.record_debug(field, &value), - } - } - - fn record_debug(&mut self, field: &tracing::field::Field, value: &dyn std::fmt::Debug) { - let value = format!("{:?}", value); - match field.name() { - "message" if self.message.is_none() => self.message = Some(value), - key => self.fields.push(fastn_observer::Field::new(key, value)), - } - } - } - - let mut visitor = Visitor { - message: None, - fields: fastn_observer::FieldSet::default(), - immediate: false, - }; - - event.record(&mut visitor); - let current_span = ctx.event_span(event); - - let shared = fastn_observer::Shared { - level: *event.metadata().level(), - fields: visitor.fields, - on: current_span - .as_ref() - .and_then(|v| { - v.extensions() - .get::() - .map(|v| v.start) - }) - .unwrap_or_else(std::time::Instant::now) - .elapsed(), - }; - - let tree_event = fastn_observer::Event { - shared, - message: visitor.message, - }; - - if visitor.immediate { - fastn_observer::write_immediate(&tree_event, current_span.as_ref()) - .expect(WRITING_URGENT_ERROR); - } - - match current_span.as_ref() { - Some(parent) => parent - .extensions_mut() - .get_mut::() - .expect(OPENED_SPAN_NOT_IN_EXTENSIONS) - .record_event(tree_event), - None => fastn_observer::write_immediate(&tree_event, current_span.as_ref()) - .expect(WRITING_URGENT_ERROR), - } - } - - fn on_close(&self, id: tracing::Id, ctx: tracing_subscriber::layer::Context) { - let span_ref = ctx.span(&id).expect(SPAN_NOT_IN_CONTEXT); - - let span = span_ref - .extensions_mut() - .remove::() - .expect(OPENED_SPAN_NOT_IN_EXTENSIONS) - .close(); - - match span_ref.parent() { - Some(parent) => parent - .extensions_mut() - .get_mut::() - .expect(OPENED_SPAN_NOT_IN_EXTENSIONS) - .record_span(span), - None => { - if fastn_observer::is_traced() { - println!( - "{}", - fastn_observer::formatter::Pretty {} - .fmt(&fastn_observer::Tree::Span(span)) - .unwrap() - ); - } - } - } - } -} diff --git a/fastn-observer/src/lib.rs b/fastn-observer/src/lib.rs deleted file mode 100644 index 310f7009e4..0000000000 --- a/fastn-observer/src/lib.rs +++ /dev/null @@ -1,33 +0,0 @@ -extern crate self as fastn_observer; - -mod duration_display; -mod field; -mod formatter; -mod layer; -mod opened_span; -mod tree; - -pub(crate) use duration_display::DurationDisplay; -pub use field::{Field, FieldSet}; -pub use formatter::write_immediate; -pub use layer::Layer; -pub use opened_span::OpenedSpan; -pub use tree::{Event, Shared, Span, Tree}; - -pub fn observe() { - use tracing_subscriber::layer::SubscriberExt; - - // let level = std::env::var("TRACING") - // .unwrap_or_else(|_| "info".to_string()) - // .parse::() - // .unwrap_or(tracing_forest::util::LevelFilter::INFO); - - let s = tracing_subscriber::registry() - //.with(level) - .with(Layer::default()); - tracing::subscriber::set_global_default(s).unwrap(); -} - -pub fn is_traced() -> bool { - std::env::var("TRACING").is_ok() || std::env::args().any(|e| e == "--trace") -} diff --git a/fastn-observer/src/main.rs b/fastn-observer/src/main.rs deleted file mode 100644 index 17d2769c9d..0000000000 --- a/fastn-observer/src/main.rs +++ /dev/null @@ -1,31 +0,0 @@ -fn main() { - fastn_observer::observe(); - - tokio::runtime::Builder::new_multi_thread() - .enable_all() - .build() - .unwrap() - .block_on(outer_main(2, 3)) -} - -#[tracing::instrument] -async fn outer_main(time1: u64, time2: u64) { - tracing::info!(time1, time2); - test(time1, time2).await; - tracing::info!("we are done"); -} - -#[tracing::instrument] -async fn foo(time: u64) { - tracing::info!(time); - tokio::time::sleep(std::time::Duration::from_secs(time)).await; - tracing::info!(tag = "we are done"); -} - -#[tracing::instrument] -async fn test(time1: u64, time2: u64) { - tracing::info!(hello = 10); - tokio::time::sleep(std::time::Duration::from_secs(time1)).await; - foo(time2).await; - tracing::info!(awesome = "we are done"); -} diff --git a/fastn-observer/src/opened_span.rs b/fastn-observer/src/opened_span.rs deleted file mode 100644 index 9f091882d0..0000000000 --- a/fastn-observer/src/opened_span.rs +++ /dev/null @@ -1,47 +0,0 @@ -// borrowed from https://github.com/QnnOkabayashi/tracing-forest/ (license: MIT) - -pub struct OpenedSpan { - span: fastn_observer::Span, - pub start: std::time::Instant, -} - -impl OpenedSpan { - pub fn new( - attrs: &tracing::span::Attributes, - parent_start: Option, - ) -> Self { - let start = std::time::Instant::now(); - let mut fields = fastn_observer::FieldSet::default(); - - attrs.record( - &mut |field: &tracing::field::Field, value: &dyn std::fmt::Debug| { - let value = format!("{:?}", value); - fields.push(fastn_observer::Field::new(field.name(), value)); - }, - ); - - let shared = fastn_observer::Shared { - level: *attrs.metadata().level(), - fields, - on: parent_start.unwrap_or(start).elapsed(), - }; - - OpenedSpan { - span: fastn_observer::Span::new(shared, attrs.metadata().name()), - start, - } - } - - pub fn close(mut self) -> fastn_observer::Span { - self.span.duration = self.start.elapsed(); - self.span - } - - pub fn record_event(&mut self, event: fastn_observer::Event) { - self.span.nodes.push(fastn_observer::Tree::Event(event)); - } - - pub fn record_span(&mut self, span: fastn_observer::Span) { - self.span.nodes.push(fastn_observer::Tree::Span(span)); - } -} diff --git a/fastn-observer/src/tree.rs b/fastn-observer/src/tree.rs deleted file mode 100644 index 19b2bec5eb..0000000000 --- a/fastn-observer/src/tree.rs +++ /dev/null @@ -1,40 +0,0 @@ -// borrowed from https://github.com/QnnOkabayashi/tracing-forest/ (license: MIT) - -#[derive(Debug)] -pub enum Tree { - Event(Event), - Span(Span), -} - -#[derive(Debug)] -pub struct Event { - pub(crate) shared: Shared, - pub(crate) message: Option, -} - -#[derive(Debug)] -pub struct Span { - pub(crate) shared: Shared, - pub(crate) name: &'static str, - pub(crate) duration: std::time::Duration, - pub(crate) nodes: Vec, -} - -#[derive(Debug)] -pub struct Shared { - pub(crate) level: tracing::Level, - pub(crate) fields: fastn_observer::FieldSet, - /// when did this event occur, with respect to immediate parent start - pub(crate) on: std::time::Duration, -} - -impl Span { - pub(crate) fn new(shared: Shared, name: &'static str) -> Self { - Span { - shared, - name, - duration: std::time::Duration::ZERO, - nodes: Vec::new(), - } - } -} From 3f236e965103296f9da391e6bdb32cfb738b7876 Mon Sep 17 00:00:00 2001 From: Amit Upadhyay Date: Sun, 22 Oct 2023 15:54:55 +0530 Subject: [PATCH 05/44] cargo update --- Cargo.lock | 90 ++++++++++++++++++++++++++++++++++++------------------ Cargo.toml | 12 ++++---- 2 files changed, 67 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ffed99485b..e4a38f6627 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -109,7 +109,7 @@ dependencies = [ "futures-core", "futures-util", "mio 0.8.8", - "socket2 0.5.4", + "socket2 0.5.5", "tokio", "tracing", ] @@ -170,7 +170,7 @@ dependencies = [ "serde_json", "serde_urlencoded", "smallvec", - "socket2 0.5.4", + "socket2 0.5.5", "time", "url", ] @@ -844,10 +844,11 @@ dependencies = [ [[package]] name = "comrak" -version = "0.18.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "482aa5695bca086022be453c700a40c02893f1ba7098a2c88351de55341ae894" +checksum = "82c995deda3bfdebd07d0e2af79e9da13e4b1be652b21a746f3f5b24bf0a49ef" dependencies = [ + "derive_builder", "entities", "memchr", "once_cell", @@ -927,9 +928,9 @@ checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "cpufeatures" -version = "0.2.9" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a17b76ff3a4162b0b27f354a0c87015ddad39d35f9c0c36607a3bdd175dde1f1" +checksum = "3fbc60abd742b35f2492f808e1abbb83d45f72db402e14c55057edc9c7b1e9e4" dependencies = [ "libc", ] @@ -1019,11 +1020,11 @@ dependencies = [ [[package]] name = "crossterm" -version = "0.26.1" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.1", "crossterm_winapi", "libc", "mio 0.8.8", @@ -1257,6 +1258,37 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_builder" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d67778784b508018359cbc8696edb3db78160bab2c2a28ba7f56ef6932997f8" +dependencies = [ + "derive_builder_macro", +] + +[[package]] +name = "derive_builder_core" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" +dependencies = [ + "darling 0.14.4", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_macro" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e" +dependencies = [ + "derive_builder_core", + "syn 1.0.109", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -1650,7 +1682,7 @@ dependencies = [ "ignore", "indoc 2.0.4", "intl-memoizer", - "itertools 0.10.5", + "itertools 0.11.0", "magic-crypt", "mime_guess", "native-tls", @@ -1702,7 +1734,7 @@ version = "0.1.0" dependencies = [ "fastn-grammar", "indoc 2.0.4", - "itertools 0.10.5", + "itertools 0.11.0", "prettify-js", "pretty", "pretty_assertions", @@ -1897,8 +1929,8 @@ name = "ftd" version = "0.3.0" dependencies = [ "colored", - "comrak 0.18.0", - "crossterm 0.26.1", + "comrak 0.19.0", + "crossterm 0.27.0", "css-color-parser", "diffy", "dioxus-html", @@ -1911,7 +1943,7 @@ dependencies = [ "include_dir", "indexmap 2.0.2", "indoc 2.0.4", - "itertools 0.10.5", + "itertools 0.11.0", "once_cell", "pretty_assertions", "rand", @@ -2235,7 +2267,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.9", + "socket2 0.4.10", "tokio", "tower-service", "tracing", @@ -3740,9 +3772,9 @@ dependencies = [ [[package]] name = "rquickjs" -version = "0.1.7" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc022cc82b5de6f38b2f4ddb8ed9c49cdbd7ce112e650b181598e102157257de" +checksum = "6db7788c2818f4546daabe9ae2d1ee2f4db61ab1998d4b483494c4193cc38dab" dependencies = [ "rquickjs-core", "rquickjs-macro", @@ -3750,9 +3782,9 @@ dependencies = [ [[package]] name = "rquickjs-core" -version = "0.1.7" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74fa1ecc1c84b31da87e5b26ce2b5218d36ffeb5c322141c78b79fa86a6ee3b9" +checksum = "b12cf8646fe0af5bcff2822ccd162990f0679a1f9287c7257f4f4193a9d31ea9" dependencies = [ "relative-path", "rquickjs-sys", @@ -3760,9 +3792,9 @@ dependencies = [ [[package]] name = "rquickjs-macro" -version = "0.1.7" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a59ea6b93ccb811b02fefef0eec225d7fed0366b929ebf849afb22013c2953a0" +checksum = "80564583a91b0ae6b2d6b9b3d0f8ffd69a4b17202cc63a12df78dfa8983885fc" dependencies = [ "darling 0.14.4", "fnv", @@ -3778,9 +3810,9 @@ dependencies = [ [[package]] name = "rquickjs-sys" -version = "0.1.7" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24311952af42d8252e399cf48e7d470cb413b1a11a1a5b7fab648cd2edec76c5" +checksum = "b747058afd4d988d056e4972ec8516a5a86fdfc103c1c1485bfee8966a0743ae" dependencies = [ "cc", ] @@ -4215,9 +4247,9 @@ checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "socket2" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" dependencies = [ "libc", "winapi", @@ -4225,9 +4257,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.4" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e" +checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" dependencies = [ "libc", "windows-sys", @@ -4501,7 +4533,7 @@ dependencies = [ "parking_lot 0.12.1", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.4", + "socket2 0.5.5", "tokio-macros", "windows-sys", ] @@ -4547,7 +4579,7 @@ dependencies = [ "postgres-protocol", "postgres-types", "rand", - "socket2 0.5.4", + "socket2 0.5.5", "tokio", "tokio-util", "whoami", diff --git a/Cargo.toml b/Cargo.toml index 6a0494a7c6..47b87cb96b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -70,7 +70,7 @@ bytemuck = { version = "1", features = [ "derive" ] } camino = "1" clap = "4" colored = "2.0.4" -crossterm = "0.26" +crossterm = "0.27" css-color-parser = "0.1" diffy = "0.3" dioxus-html = { git = "https://github.com/DioxusLabs/dioxus", rev = "fb52673433cc57a70c86185ffa7da5fa3a2394da" } @@ -95,7 +95,7 @@ ignore = "0.4" include_dir = "0.7" indoc = "2" intl-memoizer = "0.5" -itertools = "0.10" +itertools = "0.11" log = "0.4" magic-crypt = { version = "3", default-features = false } mime_guess = "2" @@ -110,7 +110,7 @@ regex = "1" reqwest = { version = "0.11", features = ["json"] } rink = { git = "https://github.com/DioxusLabs/dioxus", rev = "fb52673433cc57a70c86185ffa7da5fa3a2394da" } ron = "0.8" -rquickjs = { version = "0.1", features = ["macro"] } +rquickjs = { version = "0.3", features = ["macro"] } quick-js = "0.4.1" rustc-hash = "1" rusty-hook = "^0.11.2" @@ -130,8 +130,8 @@ ansi_term = "0.12" walkdir = "2" smallvec = "1" wasm-bindgen = "0.2" -wasmtime = "10" -wgpu = "0.16" +wasmtime = "13" +wgpu = "0.17" winit = "0.28" zip = "0.6" prettify-js = "0.1.0" @@ -202,7 +202,7 @@ features = [ [workspace.dependencies.comrak] # We use comrak for markup processing. -version = "0.18" +version = "0.19" # By default comrak ships with support for syntax highlighting using syntext for "fenced # code blocks". We have disabled that by not using default features. We did that because # we already have a way to show code in ftd, ftd.code. Further, comark requires syntect 4.6 From 839b222e560f88d5b1f81d6d95bcca3252680524 Mon Sep 17 00:00:00 2001 From: Amit Upadhyay Date: Mon, 23 Oct 2023 13:36:23 +0530 Subject: [PATCH 06/44] updated help message --- fastn/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastn/src/main.rs b/fastn/src/main.rs index 46a1525186..f1f4c33952 100644 --- a/fastn/src/main.rs +++ b/fastn/src/main.rs @@ -245,7 +245,7 @@ async fn fastn_core_commands(matches: &clap::ArgMatches) -> fastn_core::Result<( } fn app(version: &'static str) -> clap::Command { - clap::Command::new("fastn: FTD Package Manager") + clap::Command::new("fastn: Full-stack Web Development Made Easy") .version(version) .arg_required_else_help(true) .arg(clap::arg!(verbose: -v "Sets the level of verbosity")) From 99c5cc401734cb71079c575a68fbe50eb2a9619e Mon Sep 17 00:00:00 2001 From: siddhantCodes Date: Mon, 23 Oct 2023 16:21:01 +0530 Subject: [PATCH 07/44] check for latest vestion https://github.com/orgs/fastn-stack/discussions/1410 --- Cargo.lock | 3 ++ fastn-core/tests/01-help/cmd.p1 | 7 +++-- fastn/Cargo.toml | 3 ++ fastn/src/main.rs | 55 +++++++++++++++++++++++++++++++-- 4 files changed, 63 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e4a38f6627..c92c347459 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1630,6 +1630,9 @@ dependencies = [ "fastn-cloud", "fastn-core", "fastn-observer", + "futures", + "reqwest", + "serde", "thiserror", "tokio", "tracing", diff --git a/fastn-core/tests/01-help/cmd.p1 b/fastn-core/tests/01-help/cmd.p1 index a1885e638b..48dd61f27a 100644 --- a/fastn-core/tests/01-help/cmd.p1 +++ b/fastn-core/tests/01-help/cmd.p1 @@ -17,6 +17,7 @@ Commands: help Print this message or the help of the given subcommand(s) Options: - -v Sets the level of verbosity - -h, --help Print help - -V, --version Print version + -c, --check-for-updates Check for updates + -v Sets the level of verbosity + -h, --help Print help + -V, --version Print version diff --git a/fastn/Cargo.toml b/fastn/Cargo.toml index 2b535608ab..e7dd1d417f 100644 --- a/fastn/Cargo.toml +++ b/fastn/Cargo.toml @@ -13,6 +13,9 @@ colored.workspace = true fastn-cloud.workspace = true fastn-observer.workspace = true fastn-core.workspace = true +futures.workspace = true +reqwest.workspace = true +serde.workspace = true thiserror.workspace = true tokio.workspace = true tracing.workspace = true diff --git a/fastn/src/main.rs b/fastn/src/main.rs index 46a1525186..c2b9af1a03 100644 --- a/fastn/src/main.rs +++ b/fastn/src/main.rs @@ -26,11 +26,25 @@ pub enum Error { async fn async_main() -> Result<(), Error> { let matches = app(version()).get_matches(); + if cloud_commands(&matches).await? { return Ok(()); } - fastn_core_commands(&matches).await?; - Ok(()) + + futures::try_join!( + fastn_core_commands(&matches), + check_for_update_cmd(&matches) + )?; + + match std::env::var("FASTN_CHECK_FOR_UPDATES") { + Ok(val) => { + if val == "1" && !matches.get_flag("check-for-updates") { + check_for_update().await?; + } + return Ok(()); + } + Err(_) => Ok(()), + } } async fn cloud_commands(matches: &clap::ArgMatches) -> Result { @@ -244,9 +258,46 @@ async fn fastn_core_commands(matches: &clap::ArgMatches) -> fastn_core::Result<( unreachable!("No subcommand matched"); } +async fn check_for_update_cmd(matches: &clap::ArgMatches) -> fastn_core::Result<()> { + if matches.get_flag("check-for-updates") { + check_for_update().await?; + } + + Ok(()) +} + +async fn check_for_update() -> fastn_core::Result<()> { + #[derive(serde::Deserialize, Debug)] + struct GithubRelease { + tag_name: String, + } + + let url = "https://api.github.com/repos/fastn-stack/fastn/releases/latest"; + let release: GithubRelease = reqwest::Client::new() + .get(url) + .header(reqwest::header::ACCEPT, "application/vnd.github+json") + .header(reqwest::header::USER_AGENT, "fastn") + .send() + .await? + .json() + .await?; + + let current_version = version(); + + if release.tag_name != current_version { + println!( + "You are using fastn {}, and latest release is {}, visit https://fastn.com/install to learn how to upgrade.", + current_version, release.tag_name + ); + } + + Ok(()) +} + fn app(version: &'static str) -> clap::Command { clap::Command::new("fastn: FTD Package Manager") .version(version) + .arg(clap::arg!(-c --"check-for-updates" "Check for updates")) .arg_required_else_help(true) .arg(clap::arg!(verbose: -v "Sets the level of verbosity")) .arg(clap::arg!(--test "Runs the command in test mode").hide(true)) From 94712317758495182e48ea389470c372696eda3d Mon Sep 17 00:00:00 2001 From: siddhantCodes Date: Mon, 23 Oct 2023 16:27:04 +0530 Subject: [PATCH 08/44] fix: clippy warnings --- fastn/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastn/src/main.rs b/fastn/src/main.rs index c2b9af1a03..ec2981c7ab 100644 --- a/fastn/src/main.rs +++ b/fastn/src/main.rs @@ -41,7 +41,7 @@ async fn async_main() -> Result<(), Error> { if val == "1" && !matches.get_flag("check-for-updates") { check_for_update().await?; } - return Ok(()); + Ok(()) } Err(_) => Ok(()), } From 6037d963c847212705752a6146e0138d80a80b16 Mon Sep 17 00:00:00 2001 From: siddhantCodes Date: Mon, 23 Oct 2023 17:03:34 +0530 Subject: [PATCH 09/44] don't check-for-updates only if FASTN_CHECK_FOR_UPDATES=false fix: url --- fastn/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fastn/src/main.rs b/fastn/src/main.rs index ec2981c7ab..570faf7c07 100644 --- a/fastn/src/main.rs +++ b/fastn/src/main.rs @@ -38,7 +38,7 @@ async fn async_main() -> Result<(), Error> { match std::env::var("FASTN_CHECK_FOR_UPDATES") { Ok(val) => { - if val == "1" && !matches.get_flag("check-for-updates") { + if val != "false" && !matches.get_flag("check-for-updates") { check_for_update().await?; } Ok(()) @@ -286,7 +286,7 @@ async fn check_for_update() -> fastn_core::Result<()> { if release.tag_name != current_version { println!( - "You are using fastn {}, and latest release is {}, visit https://fastn.com/install to learn how to upgrade.", + "You are using fastn {}, and latest release is {}, visit https://fastn.com/install/ to learn how to upgrade.", current_version, release.tag_name ); } From 6489032b7f27945149fd97144ab85590b35ee105 Mon Sep 17 00:00:00 2001 From: siddhantCodes Date: Mon, 23 Oct 2023 19:03:45 +0530 Subject: [PATCH 10/44] support `-c` flag with no subcommand https://github.com/fastn-stack/fastn/pull/1413 --- fastn/src/main.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/fastn/src/main.rs b/fastn/src/main.rs index 570faf7c07..18b9f1100d 100644 --- a/fastn/src/main.rs +++ b/fastn/src/main.rs @@ -39,7 +39,7 @@ async fn async_main() -> Result<(), Error> { match std::env::var("FASTN_CHECK_FOR_UPDATES") { Ok(val) => { if val != "false" && !matches.get_flag("check-for-updates") { - check_for_update().await?; + check_for_update(false).await?; } Ok(()) } @@ -255,18 +255,22 @@ async fn fastn_core_commands(matches: &clap::ArgMatches) -> fastn_core::Result<( return fastn_core::post_build_check(&config).await; } + if matches.get_flag("check-for-updates") { + return check_for_update(true).await; + } + unreachable!("No subcommand matched"); } async fn check_for_update_cmd(matches: &clap::ArgMatches) -> fastn_core::Result<()> { if matches.get_flag("check-for-updates") { - check_for_update().await?; + check_for_update(false).await?; } Ok(()) } -async fn check_for_update() -> fastn_core::Result<()> { +async fn check_for_update(report: bool) -> fastn_core::Result<()> { #[derive(serde::Deserialize, Debug)] struct GithubRelease { tag_name: String, @@ -289,6 +293,8 @@ async fn check_for_update() -> fastn_core::Result<()> { "You are using fastn {}, and latest release is {}, visit https://fastn.com/install/ to learn how to upgrade.", current_version, release.tag_name ); + } else if report { + println!("You are using the latest release of fastn."); } Ok(()) From 4ffda6aa93300bffd65575a38ef5776127a809a5 Mon Sep 17 00:00:00 2001 From: siddhantCodes Date: Sun, 22 Oct 2023 18:54:58 +0530 Subject: [PATCH 11/44] remove nixpkgs-mozilla in favour of rust-overlay and mac support - mac support requires testing! - use lib.cleanSource to stop unnecessary nix builds - rename `fastnp` to fastn --- .gitignore | 3 ++ flake.lock | 80 ++++++++++++++++++++++++++++++++++++++++++++---------- flake.nix | 34 +++++++++++------------ 3 files changed, 86 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 215776a56c..a2e8d54929 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,9 @@ ftd/t/js/**.manual.html ftd/t/js/**.script.html +# nix symlink to the build output +result + # Rust stuff target **/*.rs.bk diff --git a/flake.lock b/flake.lock index 57de9e3c0f..49f5c88d53 100644 --- a/flake.lock +++ b/flake.lock @@ -18,6 +18,24 @@ "type": "github" } }, + "flake-utils_2": { + "inputs": { + "systems": "systems_2" + }, + "locked": { + "lastModified": 1681202837, + "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "cfacdce06f30d2b68473a46042957675eebb3401", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, "naersk": { "inputs": { "nixpkgs": "nixpkgs" @@ -50,29 +68,29 @@ "type": "indirect" } }, - "nixpkgs-mozilla": { - "flake": false, + "nixpkgs_2": { "locked": { - "lastModified": 1695805681, - "narHash": "sha256-1ElPLD8eFfnuIk0G52HGGpRtQZ4QPCjChRlEOfkZ5ro=", - "owner": "mozilla", - "repo": "nixpkgs-mozilla", - "rev": "6eabade97bc28d707a8b9d82ad13ef143836736e", + "lastModified": 1697660476, + "narHash": "sha256-w5/4HKG6/TPOFUki4rYUbME8zC6+suT6hSunyFD4BlI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "964a525d67348323be4fa100345d37b361ebd36e", "type": "github" }, "original": { - "owner": "mozilla", - "repo": "nixpkgs-mozilla", + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", "type": "github" } }, - "nixpkgs_2": { + "nixpkgs_3": { "locked": { - "lastModified": 1697660476, - "narHash": "sha256-w5/4HKG6/TPOFUki4rYUbME8zC6+suT6hSunyFD4BlI=", + "lastModified": 1681358109, + "narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "964a525d67348323be4fa100345d37b361ebd36e", + "rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9", "type": "github" }, "original": { @@ -87,7 +105,26 @@ "flake-utils": "flake-utils", "naersk": "naersk", "nixpkgs": "nixpkgs_2", - "nixpkgs-mozilla": "nixpkgs-mozilla" + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "flake-utils": "flake-utils_2", + "nixpkgs": "nixpkgs_3" + }, + "locked": { + "lastModified": 1697854201, + "narHash": "sha256-H+0Wb20PQx/8N7X/OfwwAVPeN9TbfjcyG0sXbdgsh50=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "6e8e3332433847cd56186b1f6fc8c47603cf5b46", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" } }, "systems": { @@ -104,6 +141,21 @@ "repo": "default", "type": "github" } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index be326f2781..0d28434bf1 100644 --- a/flake.nix +++ b/flake.nix @@ -4,50 +4,50 @@ naersk.url = "github:nix-community/naersk"; - nixpkgs-mozilla = { - url = "github:mozilla/nixpkgs-mozilla"; - flake = false; - }; + rust-overlay.url = "github:oxalica/rust-overlay"; nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; - }; - outputs = { self, flake-utils, nixpkgs, nixpkgs-mozilla, naersk }: + outputs = { self, flake-utils, nixpkgs, rust-overlay, naersk }: flake-utils.lib.eachDefaultSystem (system: let pkgs = (import nixpkgs) { inherit system; overlays = [ - (import nixpkgs-mozilla) + (import rust-overlay) ]; }; - toolchain = (pkgs.rustChannelOf { - rustToolchain = ./rust-toolchain; - sha256 = "sha256-rLP8+fTxnPHoR96ZJiCa/5Ans1OojI7MLsmSqR2ip8o="; - }).rust; + toolchain = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain; naersk' = pkgs.callPackage naersk { cargo = toolchain; rustc = toolchain; }; - fastnp = naersk'.buildPackage { + cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml); + + fastn = naersk'.buildPackage { name = "fastn"; - version = "0.3.0"; - src = ./.; + version = cargoToml.workspace.package.version; + src = pkgs.lib.cleanSource ./.; + + nativeBuildInputs = with pkgs; [ + pkg-config + openssl.dev + ] ++ lib.optionals stdenv.isDarwin [ xcbuild ]; - nativeBuildInputs = with pkgs; [ pkg-config openssl.dev ]; + buildInput = with pkgs; lib.optionals stdenv.isDarwin [ darwin.Security ]; }; in rec { # For `nix build` & `nix run`: - defaultPackage = fastnp; + defaultPackage = fastn; packages = { - fastn = fastnp; + inherit fastn; }; # nix develop From 4d9e353e65b4d94a8dbf81ee237403b31d69bdab Mon Sep 17 00:00:00 2001 From: siddhantCodes Date: Mon, 23 Oct 2023 16:58:33 +0530 Subject: [PATCH 12/44] fix for mac --- flake.nix | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 0d28434bf1..2889d81bf9 100644 --- a/flake.nix +++ b/flake.nix @@ -39,7 +39,9 @@ openssl.dev ] ++ lib.optionals stdenv.isDarwin [ xcbuild ]; - buildInput = with pkgs; lib.optionals stdenv.isDarwin [ darwin.Security ]; + buildInputs = with pkgs; lib.optionals stdenv.isDarwin [ + darwin.apple_sdk.frameworks.SystemConfiguration + ]; }; in rec { @@ -52,7 +54,12 @@ # nix develop devShell = pkgs.mkShell { + name = "fastn-shell"; nativeBuildInputs = [ toolchain pkgs.pkg-config pkgs.openssl.dev ]; + + shellHook = '' + export PATH="$PATH:$HOME/.cargo/bin" + ''; }; formatter = pkgs.nixpkgs-fmt; From e118e8907bd75a5a5d4ef0b276ea96509a486db2 Mon Sep 17 00:00:00 2001 From: Amit Upadhyay Date: Mon, 23 Oct 2023 16:30:59 +0530 Subject: [PATCH 13/44] some auth refactor --- fastn-core/src/auth/github.rs | 24 ++++--- fastn-core/src/auth/mod.rs | 2 +- fastn-core/src/auth/routes.rs | 72 ++++--------------- fastn-core/src/auth/utils.rs | 2 +- fastn-core/src/commands/serve.rs | 12 +--- fastn-core/src/http.rs | 2 + .../src/library2022/processor/user_details.rs | 1 + 7 files changed, 34 insertions(+), 81 deletions(-) diff --git a/fastn-core/src/auth/github.rs b/fastn-core/src/auth/github.rs index 1d7646323b..fab82799c8 100644 --- a/fastn-core/src/auth/github.rs +++ b/fastn-core/src/auth/github.rs @@ -1,12 +1,14 @@ // TODO: This has be set while creating the GitHub OAuth Application -pub const CALLBACK_URL: &str = "/auth/github/callback/"; +pub const CALLBACK_URL: &str = "/-/auth/github/callback/"; #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct UserDetail { pub token: String, pub user_name: String, } -// route: /auth/login/ -pub async fn login(req: actix_web::HttpRequest) -> fastn_core::Result { + +pub async fn login( + req: &fastn_core::http::Request, +) -> fastn_core::Result { // GitHub will be redirect to this url after login process completed let mut next_url = "/".to_string(); @@ -22,8 +24,8 @@ pub async fn login(req: actix_web::HttpRequest) -> fastn_core::Result fastn_core::Result fastn_core::Result { +pub async fn callback( + req: &fastn_core::http::Request, +) -> fastn_core::Result { #[derive(serde::Deserialize)] pub struct QueryParams { pub code: String, @@ -66,8 +70,8 @@ pub async fn callback(req: actix_web::HttpRequest) -> fastn_core::Result::from_query(req.query_string())?.0; let auth_url = format!( "{}://{}{}", - req.connection_info().scheme(), - req.connection_info().host(), + req.connection_info.scheme(), + req.connection_info.host(), CALLBACK_URL ); let client = utils::github_client().set_redirect_uri(oauth2::RedirectUrl::new(auth_url)?); @@ -90,9 +94,7 @@ pub async fn callback(req: actix_web::HttpRequest) -> fastn_core::Result String { - match std::env::var("SECRET_KEY") { + match std::env::var("FASTN_SECRET_KEY") { Ok(secret) => secret, Err(_e) => { println!("WARN: SECRET_KEY not set"); diff --git a/fastn-core/src/auth/routes.rs b/fastn-core/src/auth/routes.rs index a42aeead38..7b688ae156 100644 --- a/fastn-core/src/auth/routes.rs +++ b/fastn-core/src/auth/routes.rs @@ -1,22 +1,14 @@ -// route: /auth/login/ -pub async fn login( - req: actix_web::HttpRequest, - edition: Option, - external_js: Vec, - inline_js: Vec, - external_css: Vec, - inline_css: Vec, -) -> fastn_core::Result { - if fastn_core::auth::utils::is_login(&req) { - return Ok(actix_web::HttpResponse::Found() - .append_header((actix_web::http::header::LOCATION, "/".to_string())) - .finish()); +// route: /-/auth/login/ +pub async fn login(req: &fastn_core::http::Request) -> fastn_core::Result { + if fastn_core::auth::utils::is_authenticated(req) { + return Ok(fastn_core::http::redirect("/".to_string())); } #[derive(serde::Deserialize)] pub struct QueryParams { pub platform: String, } + let query = match actix_web::web::Query::::from_query(req.query_string()) { Ok(q) => q, Err(err) => { @@ -28,35 +20,18 @@ pub async fn login( match query.platform.as_str() { "github" => fastn_core::auth::github::login(req).await, _ => { - let mut req = fastn_core::http::Request::from_actix(req, actix_web::web::Bytes::new()); - req.path = "/sorry/".to_string(); - fastn_core::commands::serve::serve( - req, - edition, - external_js, - inline_js, - external_css, - inline_css, - ) - .await + return Ok(actix_web::HttpResponse::BadRequest() + .body("Please select the platform, by which you want to login")); } // _ => unreachable!(), } } -// route: /auth/logout/ -pub fn logout(req: actix_web::HttpRequest) -> fastn_core::Result { +// route: /-/auth/logout/ +pub fn logout() -> fastn_core::Result { // TODO: Refactor, Not happy with this code, too much of repetition of similar code - // It is logging out from all the platforms - - // Ideally it should capture the platform in the request and then logged out - // only from that platform Ok(actix_web::HttpResponse::Found() .cookie( actix_web::cookie::Cookie::build(fastn_core::auth::AuthProviders::GitHub.as_str(), "") - .domain(fastn_core::auth::utils::domain( - req.connection_info().host(), - )) - .path("/") .expires(actix_web::cookie::time::OffsetDateTime::now_utc()) .finish(), ) @@ -64,32 +39,15 @@ pub fn logout(req: actix_web::HttpRequest) -> fastn_core::Result, - external_js: Vec, - inline_js: Vec, - external_css: Vec, - inline_css: Vec, + req: fastn_core::http::Request, ) -> fastn_core::Result { match req.path() { - "/auth/login/" => { - login( - req, - edition, - external_js, - inline_js, - external_css, - inline_css, - ) - .await - } - fastn_core::auth::github::CALLBACK_URL => fastn_core::auth::github::callback(req).await, - "/auth/logout/" => logout(req), - _ => Ok(actix_web::HttpResponse::new( - actix_web::http::StatusCode::NOT_FOUND, - )), + "/-/auth/login/" => login(&req).await, + "/-/auth/github/" => fastn_core::auth::github::callback(&req).await, + "/-/auth/logout/" => logout(), + _ => Ok(fastn_core::not_found!("route not found: {}", req.path())), } } diff --git a/fastn-core/src/auth/utils.rs b/fastn-core/src/auth/utils.rs index 92018fd99f..db7a299663 100644 --- a/fastn-core/src/auth/utils.rs +++ b/fastn-core/src/auth/utils.rs @@ -51,7 +51,7 @@ pub async fn decrypt_str(encrypted_str: &String) -> Result bool { +pub fn is_authenticated(req: &fastn_core::http::Request) -> bool { let mut found_cookie = false; for auth_provider in fastn_core::auth::AuthProviders::AUTH_ITER.iter() { dbg!(&auth_provider); diff --git a/fastn-core/src/commands/serve.rs b/fastn-core/src/commands/serve.rs index 2ec1ea98dc..763988bd69 100644 --- a/fastn-core/src/commands/serve.rs +++ b/fastn-core/src/commands/serve.rs @@ -638,17 +638,6 @@ async fn route( return Ok(default_response); } - if req.path().starts_with("/auth/") { - return fastn_core::auth::routes::handle_auth( - req, - app_data.edition.clone(), - app_data.external_js.clone(), - app_data.inline_js.clone(), - app_data.external_css.clone(), - app_data.inline_css.clone(), - ) - .await; - } let req = fastn_core::http::Request::from_actix(req, body); match (req.method().to_lowercase().as_str(), req.path()) { ("post", "/-/sync/") if cfg!(feature = "remote") => sync(req).await, @@ -656,6 +645,7 @@ async fn route( ("get", "/-/clone/") if cfg!(feature = "remote") => clone(req).await, ("get", t) if t.starts_with("/-/view-src/") => view_source(req).await, ("get", t) if t.starts_with("/-/edit-src/") => edit_source(req).await, + ("get", t) if t.starts_with("/-/auth/") => fastn_core::auth::routes::handle_auth(req).await, ("post", "/-/edit/") => edit(req).await, ("post", "/-/revert/") => revert(req).await, ("get", "/-/editor-sync/") => editor_sync(req).await, diff --git a/fastn-core/src/http.rs b/fastn-core/src/http.rs index 6b81901445..67dc462100 100644 --- a/fastn-core/src/http.rs +++ b/fastn-core/src/http.rs @@ -86,6 +86,7 @@ pub struct Request { ip: Option, scheme: String, host: String, + pub connection_info: actix_web::dev::ConnectionInfo, // path_params: Vec<(String, )> } @@ -107,6 +108,7 @@ impl Request { uri: req.uri().to_string(), path: req.path().to_string(), query_string: req.query_string().to_string(), + connection_info: req.connection_info().clone(), headers, query: { actix_web::web::Query::>::from_query( diff --git a/fastn-core/src/library2022/processor/user_details.rs b/fastn-core/src/library2022/processor/user_details.rs index 3a97330076..4860cb91d5 100644 --- a/fastn-core/src/library2022/processor/user_details.rs +++ b/fastn-core/src/library2022/processor/user_details.rs @@ -10,6 +10,7 @@ pub fn process( for auth_provider in fastn_core::auth::AuthProviders::AUTH_ITER.iter() { if req.cookie(auth_provider.as_str()).is_some() { found_cookie = true; + break; } } found_cookie From 3dc5ed60b9ad1f81e81059888bb8df1be03e6e10 Mon Sep 17 00:00:00 2001 From: Amit Upadhyay Date: Mon, 23 Oct 2023 16:47:39 +0530 Subject: [PATCH 14/44] some more cleanups --- fastn-core/src/auth/github.rs | 27 ++++----------------------- fastn-core/src/auth/routes.rs | 16 +++------------- fastn-core/src/http.rs | 12 ++++++++++++ 3 files changed, 19 insertions(+), 36 deletions(-) diff --git a/fastn-core/src/auth/github.rs b/fastn-core/src/auth/github.rs index fab82799c8..d082fe3b3e 100644 --- a/fastn-core/src/auth/github.rs +++ b/fastn-core/src/auth/github.rs @@ -1,5 +1,3 @@ -// TODO: This has be set while creating the GitHub OAuth Application -pub const CALLBACK_URL: &str = "/-/auth/github/callback/"; #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct UserDetail { pub token: String, @@ -9,25 +7,11 @@ pub struct UserDetail { pub async fn login( req: &fastn_core::http::Request, ) -> fastn_core::Result { - // GitHub will be redirect to this url after login process completed - - let mut next_url = "/".to_string(); - if let Ok(queries) = - actix_web::web::Query::>::from_query( - req.query_string(), - ) - { - if queries.get("next").is_some() { - next_url = queries.get("next").unwrap().to_string(); - } - } - let redirect_url: String = format!( - "{}://{}{}?next={}", + "{}://{}/-/auth/github/?next={}", req.connection_info.scheme(), req.connection_info.host(), - CALLBACK_URL, - next_url, + req.q("next", "/".to_string())?, ); // Set up the config for the Github OAuth2 process. @@ -50,9 +34,7 @@ pub async fn login( // let mut pairs: Vec<(&str, &str)> = vec![("response_type", self.response_type.as_ref())]; // send redirect to /auth/github/callback/ - Ok(actix_web::HttpResponse::Found() - .append_header((actix_web::http::header::LOCATION, authorize_url.to_string())) - .finish()) + Ok(fastn_core::http::redirect(authorize_url.to_string())) } // route: /auth/github/callback/ @@ -69,10 +51,9 @@ pub async fn callback( } let query = actix_web::web::Query::::from_query(req.query_string())?.0; let auth_url = format!( - "{}://{}{}", + "{}://{}/-/auth/github/", req.connection_info.scheme(), req.connection_info.host(), - CALLBACK_URL ); let client = utils::github_client().set_redirect_uri(oauth2::RedirectUrl::new(auth_url)?); match client diff --git a/fastn-core/src/auth/routes.rs b/fastn-core/src/auth/routes.rs index 7b688ae156..84b6807d47 100644 --- a/fastn-core/src/auth/routes.rs +++ b/fastn-core/src/auth/routes.rs @@ -4,20 +4,9 @@ pub async fn login(req: &fastn_core::http::Request) -> fastn_core::Result::from_query(req.query_string()) { - Ok(q) => q, - Err(err) => { - dbg!(err); - return Ok(actix_web::HttpResponse::BadRequest() - .body("Please select the platform, by which you want to login")); - } - }; - match query.platform.as_str() { + match platform.as_str() { "github" => fastn_core::auth::github::login(req).await, _ => { return Ok(actix_web::HttpResponse::BadRequest() @@ -46,6 +35,7 @@ pub async fn handle_auth( ) -> fastn_core::Result { match req.path() { "/-/auth/login/" => login(&req).await, + // TODO: This has be set while creating the GitHub OAuth Application "/-/auth/github/" => fastn_core::auth::github::callback(&req).await, "/-/auth/logout/" => logout(), _ => Ok(fastn_core::not_found!("route not found: {}", req.path())), diff --git a/fastn-core/src/http.rs b/fastn-core/src/http.rs index 67dc462100..55e6893715 100644 --- a/fastn-core/src/http.rs +++ b/fastn-core/src/http.rs @@ -231,6 +231,18 @@ impl Request { &self.query } + pub fn q(&self, key: &str, default: T) -> fastn_core::Result + where + T: serde::de::DeserializeOwned, + { + let value = match self.query.get(key) { + Some(v) => v, + None => return Ok(default), + }; + + Ok(serde_json::from_value(value.clone())?) + } + pub fn get_ip(&self) -> Option { self.ip.clone() } From ccb2033a94a0aeaea0b39aa51ac39d7b7a5796e5 Mon Sep 17 00:00:00 2001 From: Amit Upadhyay Date: Mon, 23 Oct 2023 21:19:06 +0530 Subject: [PATCH 15/44] reviewing code --- design/github.md | 26 ++++++ fastn-core/src/auth/github.rs | 163 ++++++++++++++++------------------ fastn-core/src/auth/mod.rs | 4 +- fastn-core/src/auth/routes.rs | 30 ++++--- 4 files changed, 120 insertions(+), 103 deletions(-) create mode 100644 design/github.md diff --git a/design/github.md b/design/github.md new file mode 100644 index 0000000000..ce8e553ae3 --- /dev/null +++ b/design/github.md @@ -0,0 +1,26 @@ +# How Does Github Login Work? + +The auth related stuff is in `fastn_core::auth` module. + +## Login + +To login we send user to `/-/auth/login?provider=github&next=`. + +The `next` can be used to send the user to arbitrary URL after successful signing. + +We use `oauth2` crate for authentication with github. + +## Callback URL + +The callback URL is + +## CSRF Token + +Are we CSRF safe? We are generating a CSRF token using `oauth2::CsrfToken::new_random` in +`fastn_core::auth::github::login()`, but we are not checking it in `fastn_core::auth::github::callback()`. I think +we aught to, else we may be susceptible to CSRF. Not sure how someone can use CSRF in this context, but given +the library supports should too. + +How would we verify? Easiest thing would be to store it in a cookie. This is what Django does, stores CSRF token in +cookie, and verifies that tokens match on POST request etc. + diff --git a/fastn-core/src/auth/github.rs b/fastn-core/src/auth/github.rs index d082fe3b3e..f2cd32f7ea 100644 --- a/fastn-core/src/auth/github.rs +++ b/fastn-core/src/auth/github.rs @@ -1,25 +1,24 @@ #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct UserDetail { - pub token: String, - pub user_name: String, + pub access_token: String, + pub username: String, } pub async fn login( req: &fastn_core::http::Request, + next: String, ) -> fastn_core::Result { let redirect_url: String = format!( "{}://{}/-/auth/github/?next={}", req.connection_info.scheme(), req.connection_info.host(), - req.q("next", "/".to_string())?, + next, // TODO: we should url escape this ); - // Set up the config for the Github OAuth2 process. - // https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest - let client = utils::github_client().set_redirect_uri(oauth2::RedirectUrl::new(redirect_url)?); // Note: public_repos user:email all these things are github resources - // So we have to tell client who is getting logged in what are we going to access - let (mut authorize_url, _token) = client + // So we have to tell oauth_client who is getting logged in what are we going to access + let (mut authorize_url, _token) = utils::github_client() + .set_redirect_uri(oauth2::RedirectUrl::new(redirect_url)?) .authorize_url(oauth2::CsrfToken::new_random) .add_scope(oauth2::Scope::new("public_repo".to_string())) .add_scope(oauth2::Scope::new("user:email".to_string())) @@ -31,63 +30,52 @@ pub async fn login( .query_pairs_mut() .append_pair("prompt", "consent"); - // let mut pairs: Vec<(&str, &str)> = vec![("response_type", self.response_type.as_ref())]; - - // send redirect to /auth/github/callback/ Ok(fastn_core::http::redirect(authorize_url.to_string())) } -// route: /auth/github/callback/ +// route: /-/auth/github/done/ // In this API we are accessing // the token and setting it to cookies pub async fn callback( req: &fastn_core::http::Request, -) -> fastn_core::Result { - #[derive(serde::Deserialize)] - pub struct QueryParams { - pub code: String, - pub state: String, - pub next: String, - } - let query = actix_web::web::Query::::from_query(req.query_string())?.0; - let auth_url = format!( - "{}://{}/-/auth/github/", - req.connection_info.scheme(), - req.connection_info.host(), - ); - let client = utils::github_client().set_redirect_uri(oauth2::RedirectUrl::new(auth_url)?); - match client - .exchange_code(oauth2::AuthorizationCode::new(query.code)) + next: String, +) -> fastn_core::Result { + let code = req.q("code", "".to_string())?; + // TODO: CSRF check + + let access_token = match utils::github_client() + .exchange_code(oauth2::AuthorizationCode::new(code)) .request_async(oauth2::reqwest::async_http_client) .await { - Ok(access_token) => { - let token = oauth2::TokenResponse::access_token(&access_token).secret(); - let user_name = apis::user_details(token).await?; - let user_detail_obj: UserDetail = UserDetail { - token: token.to_owned(), - user_name, - }; - let user_detail_str = serde_json::to_string(&user_detail_obj)?; - return Ok(actix_web::HttpResponse::Found() - .cookie( - actix_web::cookie::Cookie::build( - fastn_core::auth::AuthProviders::GitHub.as_str(), - fastn_core::auth::utils::encrypt_str(&user_detail_str).await, - ) - .domain(fastn_core::auth::utils::domain(req.connection_info.host())) - .path("/") - .permanent() - // TODO: AbrarK is running on http, - // will remove it later - // .secure(true) - .finish(), - ) - .append_header((actix_web::http::header::LOCATION, query.next)) - .finish()); - } - Err(err) => Ok(actix_web::HttpResponse::InternalServerError().body(err.to_string())), - } + Ok(access_token) => oauth2::TokenResponse::access_token(&access_token) + .secret() + .to_string(), + Err(e) => return Ok(fastn_core::server_error!("{}", e.to_string())), + }; + + let ud = UserDetail { + username: apis::username(access_token.as_str()).await?, + access_token, + }; + + let user_detail_str = serde_json::to_string(&ud)?; + return Ok(actix_web::HttpResponse::Found() + .cookie( + actix_web::cookie::Cookie::build( + fastn_core::auth::AuthProviders::GitHub.as_str(), + fastn_core::auth::utils::encrypt_str(&user_detail_str).await, + ) + .domain(fastn_core::auth::utils::domain(req.connection_info.host())) + .path("/") + .permanent() + // TODO: AbrarK is running on http, + // will remove it later + // .secure(true) + .finish(), + ) + .append_header((actix_web::http::header::LOCATION, next)) + .finish()); } // it returns identities which matches to given input @@ -143,7 +131,7 @@ pub async fn matched_starred_repos( if starred_repos.is_empty() { return Ok(vec![]); } - let user_starred_repos = apis::starred_repo(ud.token.as_str()).await?; + let user_starred_repos = apis::starred_repo(ud.access_token.as_str()).await?; // filter the user starred repos with input Ok(user_starred_repos .into_iter() @@ -173,7 +161,7 @@ pub async fn matched_watched_repos( if watched_repos.is_empty() { return Ok(vec![]); } - let user_watched_repos = apis::watched_repo(ud.token.as_str()).await?; + let user_watched_repos = apis::watched_repo(ud.access_token.as_str()).await?; // filter the user watched repos with input Ok(user_watched_repos .into_iter() @@ -203,7 +191,7 @@ pub async fn matched_followed_org( if followed_orgs.is_empty() { return Ok(vec![]); } - let user_followed_orgs = apis::followed_org(ud.token.as_str()).await?; + let user_followed_orgs = apis::followed_org(ud.access_token.as_str()).await?; // filter the user followed orgs with input Ok(user_followed_orgs .into_iter() @@ -236,9 +224,9 @@ pub async fn matched_contributed_repos( return Ok(vec![]); } for repo in &contributed_repos { - let repo_contributors = apis::repo_contributors(ud.token.as_str(), repo).await?; + let repo_contributors = apis::repo_contributors(ud.access_token.as_str(), repo).await?; - if repo_contributors.contains(&ud.user_name) { + if repo_contributors.contains(&ud.username) { matched_repo_contributors_list.push(String::from(repo.to_owned())); } } @@ -274,9 +262,9 @@ pub async fn matched_collaborated_repos( return Ok(vec![]); } for repo in &collaborated_repos { - let repo_collaborator = apis::repo_collaborators(ud.token.as_str(), repo).await?; + let repo_collaborator = apis::repo_collaborators(ud.access_token.as_str(), repo).await?; - if repo_collaborator.contains(&ud.user_name) { + if repo_collaborator.contains(&ud.username) { matched_repo_collaborator_list.push(String::from(repo.to_owned())); } } @@ -315,8 +303,8 @@ pub async fn matched_org_teams( for org_team in org_teams.iter() { if let Some((org_name, team_name)) = org_team.split_once('/') { let team_members: Vec = - apis::team_members(ud.token.as_str(), org_name, team_name).await?; - if team_members.contains(&ud.user_name) { + apis::team_members(ud.access_token.as_str(), org_name, team_name).await?; + if team_members.contains(&ud.username) { matched_org_teams.push(org_team.to_string()); } } @@ -332,6 +320,7 @@ pub async fn matched_org_teams( }) .collect()) } + pub async fn matched_sponsored_org( ud: &UserDetail, identities: &[&fastn_core::user_group::UserIdentity], @@ -349,12 +338,18 @@ pub async fn matched_sponsored_org( } }) .collect_vec(); + if sponsors_list.is_empty() { return Ok(vec![]); } + for sponsor in sponsors_list.iter() { - if apis::is_user_sponsored(ud.token.as_str(), ud.user_name.as_str(), sponsor.to_owned()) - .await? + if apis::is_user_sponsored( + ud.access_token.as_str(), + ud.username.as_str(), + sponsor.to_owned(), + ) + .await? { sponsored_users_list.push(sponsor.to_string()); } @@ -373,10 +368,12 @@ pub mod apis { pub struct GraphQLResp { pub data: Data, } + #[derive(Debug, serde::Deserialize)] pub struct Data { pub user: User, } + #[derive(Debug, serde::Deserialize)] pub struct User { #[serde(rename = "isSponsoredBy")] @@ -458,6 +455,7 @@ pub mod apis { .await?; Ok(watched_repo.into_iter().map(|x| x.full_name).collect()) } + pub async fn repo_contributors( token: &str, repo_name: &str, @@ -479,6 +477,7 @@ pub mod apis { .await?; Ok(repo_contributor.into_iter().map(|x| x.login).collect()) } + pub async fn repo_collaborators( token: &str, repo_name: &str, @@ -503,25 +502,14 @@ pub mod apis { .map(|x| x.login) .collect()) } + pub async fn is_user_sponsored( token: &str, - user_name: &str, + username: &str, sponsored_by: &str, ) -> fastn_core::Result { - // API Docs: https://docs.github.com/en/graphql/reference/queries#user - // TODO: Handle paginated response - let query = format!( - "{}{}{}{}{}{}{}{}{}", - "query { user(login:", - r#"""#, - user_name, - r#"""#, - "){isSponsoredBy(accountLogin:", - r#"""#, - sponsored_by, - r#"""#, - ")}}" + "query {{ user(login: \"{username}\") {{ isSponsoredBy(accountLogin: \"{sponsored_by}\" )}} }}", ); let sponsor_obj = graphql_sponsor_api("https://api.github.com/graphql", query.as_str(), token).await?; @@ -531,17 +519,19 @@ pub mod apis { Ok(false) } } + // TODO: It can be stored in the request cookies - pub async fn user_details(token: &str) -> fastn_core::Result { + pub async fn username(access_token: &str) -> fastn_core::Result { // API Docs: https://docs.github.com/en/rest/users/users#get-the-authenticated-user // TODO: Handle paginated response #[derive(Debug, serde::Deserialize)] struct UserDetails { login: String, } + let user_obj: UserDetails = fastn_core::auth::utils::get_api( "https://api.github.com/user", - format!("{} {}", "Bearer", token).as_str(), + format!("{} {}", "Bearer", access_token).as_str(), ) .await?; @@ -583,7 +573,6 @@ pub mod apis { } pub mod utils { - // Lazy means a value which initialize at the first time access // we have to access it before using it and make sure to use it while starting a server // TODO: they should be configured with auth feature flag @@ -591,18 +580,18 @@ pub mod utils { // are set static GITHUB_CLIENT_ID: once_cell::sync::Lazy = { once_cell::sync::Lazy::new(|| { - oauth2::ClientId::new(match std::env::var("GITHUB_CLIENT_ID") { + oauth2::ClientId::new(match std::env::var("FASTN_GITHUB_CLIENT_ID") { Ok(val) => val, - Err(e) => format!("{}{}", "GITHUB_CLIENT_ID not set in env ", e), + Err(e) => format!("{}{}", "FASTN_GITHUB_CLIENT_ID not set in env ", e), }) }) }; static GITHUB_CLIENT_SECRET: once_cell::sync::Lazy = { once_cell::sync::Lazy::new(|| { - oauth2::ClientSecret::new(match std::env::var("GITHUB_CLIENT_SECRET") { + oauth2::ClientSecret::new(match std::env::var("FASTN_GITHUB_CLIENT_SECRET") { Ok(val) => val, - Err(e) => format!("{}{}", "GITHUB_CLIENT_SECRET not set in env ", e), + Err(e) => format!("{}{}", "FASTN_GITHUB_CLIENT_SECRET not set in env ", e), }) }) }; diff --git a/fastn-core/src/auth/mod.rs b/fastn-core/src/auth/mod.rs index 7c4fac4290..cb6c0cbd95 100644 --- a/fastn-core/src/auth/mod.rs +++ b/fastn-core/src/auth/mod.rs @@ -58,8 +58,8 @@ pub async fn get_user_data_from_cookies( let github_ud: github::UserDetail = serde_json::from_str(ud_decrypted.as_str())?; match requested_field { - "username" | "user_name" | "user-name" => Ok(Some(github_ud.user_name)), - "token" => Ok(Some(github_ud.token)), + "username" | "user_name" | "user-name" => Ok(Some(github_ud.username)), + "token" => Ok(Some(github_ud.access_token)), _ => Err(fastn_core::Error::GenericError(format!( "invalid field {} requested for platform {}", requested_field, platform diff --git a/fastn-core/src/auth/routes.rs b/fastn-core/src/auth/routes.rs index 84b6807d47..b0f550f4c2 100644 --- a/fastn-core/src/auth/routes.rs +++ b/fastn-core/src/auth/routes.rs @@ -1,22 +1,22 @@ // route: /-/auth/login/ -pub async fn login(req: &fastn_core::http::Request) -> fastn_core::Result { +pub async fn login( + req: &fastn_core::http::Request, + next: String, +) -> fastn_core::Result { if fastn_core::auth::utils::is_authenticated(req) { - return Ok(fastn_core::http::redirect("/".to_string())); + return Ok(fastn_core::http::redirect(next)); } - let platform = req.q("platform", "github".to_string())?; + let provider = req.q("provider", "github".to_string())?; - match platform.as_str() { - "github" => fastn_core::auth::github::login(req).await, - _ => { - return Ok(actix_web::HttpResponse::BadRequest() - .body("Please select the platform, by which you want to login")); - } // _ => unreachable!(), + match provider.as_str() { + "github" => fastn_core::auth::github::login(req, next).await, + _ => Ok(fastn_core::not_found!("unknown provider: {}", provider)), } } // route: /-/auth/logout/ -pub fn logout() -> fastn_core::Result { +pub fn logout(next: String) -> fastn_core::Result { // TODO: Refactor, Not happy with this code, too much of repetition of similar code Ok(actix_web::HttpResponse::Found() .cookie( @@ -24,7 +24,7 @@ pub fn logout() -> fastn_core::Result { .expires(actix_web::cookie::time::OffsetDateTime::now_utc()) .finish(), ) - .append_header((actix_web::http::header::LOCATION, "/".to_string())) + .append_header((actix_web::http::header::LOCATION, next)) .finish()) } @@ -33,11 +33,13 @@ pub fn logout() -> fastn_core::Result { pub async fn handle_auth( req: fastn_core::http::Request, ) -> fastn_core::Result { + let next = req.q("next", "/".to_string())?; + match req.path() { - "/-/auth/login/" => login(&req).await, + "/-/auth/login/" => login(&req, next).await, // TODO: This has be set while creating the GitHub OAuth Application - "/-/auth/github/" => fastn_core::auth::github::callback(&req).await, - "/-/auth/logout/" => logout(), + "/-/auth/github/" => fastn_core::auth::github::callback(&req, next).await, + "/-/auth/logout/" => logout(next), _ => Ok(fastn_core::not_found!("route not found: {}", req.path())), } } From 0f2013fea68fb142b5c2e510f4407d154b7f51e0 Mon Sep 17 00:00:00 2001 From: Amit Upadhyay Date: Wed, 25 Oct 2023 10:50:18 +0530 Subject: [PATCH 16/44] refactor bearer token formatting --- fastn-core/src/auth/discord.rs | 9 ++---- fastn-core/src/auth/github.rs | 50 ++++++++++++---------------------- fastn-core/src/auth/twitter.rs | 19 ++++++------- fastn-core/src/auth/utils.rs | 13 +++++---- 4 files changed, 36 insertions(+), 55 deletions(-) diff --git a/fastn-core/src/auth/discord.rs b/fastn-core/src/auth/discord.rs index dc5f10653c..a49c4c1ebf 100644 --- a/fastn-core/src/auth/discord.rs +++ b/fastn-core/src/auth/discord.rs @@ -293,11 +293,8 @@ pub mod apis { username: String, id: String, } - let user_obj: UserDetails = fastn_core::auth::utils::get_api( - "https://discord.com/api/users/@me", - format!("{} {}", "Bearer", token).as_str(), - ) - .await?; + let user_obj: UserDetails = + fastn_core::auth::utils::get_api("https://discord.com/api/users/@me", token).await?; Ok((user_obj.username, user_obj.id)) } @@ -353,7 +350,7 @@ pub mod apis { } let user_server_list: Vec = fastn_core::auth::utils::get_api( format!("{}?limit=100", "https://discord.com/api/users/@me/guilds").as_str(), - format!("{} {}", "Bearer", token).as_str(), + token, ) .await?; Ok(user_server_list.into_iter().map(|x| x.name).collect()) diff --git a/fastn-core/src/auth/github.rs b/fastn-core/src/auth/github.rs index f2cd32f7ea..ab5968cb3e 100644 --- a/fastn-core/src/auth/github.rs +++ b/fastn-core/src/auth/github.rs @@ -363,6 +363,7 @@ pub async fn matched_sponsored_org( }) .collect()) } + pub mod apis { #[derive(Debug, serde::Deserialize)] pub struct GraphQLResp { @@ -379,9 +380,9 @@ pub mod apis { #[serde(rename = "isSponsoredBy")] pub is_sponsored_by: bool, } + // TODO: API to starred a repo on behalf of the user // API Docs: https://docs.github.com/en/rest/activity/starring#list-repositories-starred-by-the-authenticated-user - pub async fn starred_repo(token: &str) -> fastn_core::Result> { // API Docs: https://docs.github.com/en/rest/activity/starring#list-repositories-starred-by-the-authenticated-user // TODO: Handle paginated response @@ -391,8 +392,8 @@ pub mod apis { full_name: String, } let starred_repo: Vec = fastn_core::auth::utils::get_api( - format!("{}?per_page=100", "https://api.github.com/user/starred").as_str(), - format!("{} {}", "Bearer", token).as_str(), + "https://api.github.com/user/starred?per_page=100", + token, ) .await?; Ok(starred_repo.into_iter().map(|x| x.full_name).collect()) @@ -406,8 +407,8 @@ pub mod apis { login: String, } let watched_repo: Vec = fastn_core::auth::utils::get_api( - format!("{}?per_page=100", "https://api.github.com/user/following").as_str(), - format!("{} {}", "Bearer", token).as_str(), + "https://api.github.com/user/following?per_page=100", + token, ) .await?; Ok(watched_repo.into_iter().map(|x| x.login).collect()) @@ -427,11 +428,9 @@ pub mod apis { let user_orgs: Vec = fastn_core::auth::utils::get_api( format!( - "{}{}{}{}/members?per_page=100", - "https://api.github.com/orgs/", org_title, "/teams/", team_slug - ) - .as_str(), - format!("{} {}", "Bearer", token).as_str(), + "https://api.github.com/orgs/{org_title}/teams/{team_slug}/members?per_page=100", + ), + token, ) .await?; Ok(user_orgs.into_iter().map(|x| x.login).collect()) @@ -445,12 +444,8 @@ pub mod apis { full_name: String, } let watched_repo: Vec = fastn_core::auth::utils::get_api( - format!( - "{}?per_page=100", - "https://api.github.com/user/subscriptions" - ) - .as_str(), - format!("{} {}", "Bearer", token).as_str(), + "https://api.github.com/user/subscriptions?per_page=100", + token, ) .await?; Ok(watched_repo.into_iter().map(|x| x.full_name).collect()) @@ -467,12 +462,8 @@ pub mod apis { login: String, } let repo_contributor: Vec = fastn_core::auth::utils::get_api( - format!( - "{}{}/contributors?per_page=100", - "https://api.github.com/repos/", repo_name - ) - .as_str(), - format!("{} {}", "Bearer", token).as_str(), + format!("https://api.github.com/repos/{repo_name}/contributors?per_page=100",), + token, ) .await?; Ok(repo_contributor.into_iter().map(|x| x.login).collect()) @@ -489,12 +480,8 @@ pub mod apis { login: String, } let repo_collaborators_list: Vec = fastn_core::auth::utils::get_api( - format!( - "{}{}/collaborators?per_page=100", - "https://api.github.com/repos/", repo_name - ) - .as_str(), - format!("{} {}", "Bearer", token).as_str(), + format!("https://api.github.com/repos/{repo_name}/collaborators?per_page=100"), + token, ) .await?; Ok(repo_collaborators_list @@ -529,11 +516,8 @@ pub mod apis { login: String, } - let user_obj: UserDetails = fastn_core::auth::utils::get_api( - "https://api.github.com/user", - format!("{} {}", "Bearer", access_token).as_str(), - ) - .await?; + let user_obj: UserDetails = + fastn_core::auth::utils::get_api("https://api.github.com/user", access_token).await?; Ok(String::from(&user_obj.login)) } diff --git a/fastn-core/src/auth/twitter.rs b/fastn-core/src/auth/twitter.rs index a74ff0087f..9552a9b12d 100644 --- a/fastn-core/src/auth/twitter.rs +++ b/fastn-core/src/auth/twitter.rs @@ -437,11 +437,8 @@ pub mod apis { username: String, id: String, } - let user_obj: DataObj = fastn_core::auth::utils::get_api( - "https://api.twitter.com/2/users/me", - format!("{} {}", "Bearer", token).as_str(), - ) - .await?; + let user_obj: DataObj = + fastn_core::auth::utils::get_api("https://api.twitter.com/2/users/me", token).await?; Ok((user_obj.data.username, user_obj.data.id)) } @@ -463,7 +460,7 @@ pub mod apis { "https://api.twitter.com/2/users/by/username/", username ) .as_str(), - format!("{} {}", "Bearer", token).as_str(), + token, ) .await?; Ok(user_obj.data.id) @@ -478,7 +475,7 @@ pub mod apis { "https://api.twitter.com/2/tweets/", tweet_id, "/liking_users" ) .as_str(), - format!("{} {}", "Bearer", token).as_str(), + token, ) .await?; Ok(liking_member_list.data.into_iter().map(|x| x.id).collect()) @@ -496,7 +493,7 @@ pub mod apis { "https://api.twitter.com/2/tweets/", tweet_id, "/retweeted_by" ) .as_str(), - format!("{} {}", "Bearer", token).as_str(), + token, ) .await?; Ok(retweeted_member_list @@ -515,7 +512,7 @@ pub mod apis { "https://api.twitter.com/2/users/", member_id, "/followers" ) .as_str(), - format!("{} {}", "Bearer", token).as_str(), + token, ) .await?; Ok(member_follower_list @@ -537,7 +534,7 @@ pub mod apis { "https://api.twitter.com/2/users/", member_id, "/following" ) .as_str(), - format!("{} {}", "Bearer", token).as_str(), + token, ) .await?; Ok(member_following_list @@ -559,7 +556,7 @@ pub mod apis { "https://api.twitter.com/2/spaces/", space_id, "/buyers" ) .as_str(), - format!("{} {}", "Bearer", token).as_str(), + token, ) .await?; Ok(space_ticket_buyers_list diff --git a/fastn-core/src/auth/utils.rs b/fastn-core/src/auth/utils.rs index db7a299663..f89a127743 100644 --- a/fastn-core/src/auth/utils.rs +++ b/fastn-core/src/auth/utils.rs @@ -9,12 +9,15 @@ pub fn domain(host: &str) -> String { } pub async fn get_api( - url: &str, - token: &str, + url: impl AsRef, + bearer_token: &str, ) -> fastn_core::Result { let response = reqwest::Client::new() - .get(url) - .header(reqwest::header::AUTHORIZATION, token) + .get(url.as_ref()) + .header( + reqwest::header::AUTHORIZATION, + format!("{} {}", "Bearer", bearer_token), + ) .header(reqwest::header::ACCEPT, "application/json") .header( reqwest::header::USER_AGENT, @@ -26,7 +29,7 @@ pub async fn get_api( if !response.status().eq(&reqwest::StatusCode::OK) { return Err(fastn_core::Error::APIResponseError(format!( "fastn-API-ERROR: {}, Error: {}", - url, + url.as_ref(), response.text().await? ))); } From 4f5c611e00760336951e16ff7e0440551aa6dc36 Mon Sep 17 00:00:00 2001 From: Amit Upadhyay Date: Wed, 25 Oct 2023 11:46:54 +0530 Subject: [PATCH 17/44] some more refactor --- fastn-core/src/auth/github.rs | 26 +++++++++++--------------- fastn-core/src/auth/utils.rs | 29 ----------------------------- fastn-core/src/http.rs | 30 ++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 44 deletions(-) diff --git a/fastn-core/src/auth/github.rs b/fastn-core/src/auth/github.rs index ab5968cb3e..10435602a5 100644 --- a/fastn-core/src/auth/github.rs +++ b/fastn-core/src/auth/github.rs @@ -391,11 +391,9 @@ pub mod apis { struct UserRepos { full_name: String, } - let starred_repo: Vec = fastn_core::auth::utils::get_api( - "https://api.github.com/user/starred?per_page=100", - token, - ) - .await?; + let starred_repo: Vec = + fastn_core::http::get_api("https://api.github.com/user/starred?per_page=100", token) + .await?; Ok(starred_repo.into_iter().map(|x| x.full_name).collect()) } @@ -406,11 +404,9 @@ pub mod apis { struct FollowedOrg { login: String, } - let watched_repo: Vec = fastn_core::auth::utils::get_api( - "https://api.github.com/user/following?per_page=100", - token, - ) - .await?; + let watched_repo: Vec = + fastn_core::http::get_api("https://api.github.com/user/following?per_page=100", token) + .await?; Ok(watched_repo.into_iter().map(|x| x.login).collect()) } @@ -426,7 +422,7 @@ pub mod apis { login: String, } - let user_orgs: Vec = fastn_core::auth::utils::get_api( + let user_orgs: Vec = fastn_core::http::get_api( format!( "https://api.github.com/orgs/{org_title}/teams/{team_slug}/members?per_page=100", ), @@ -443,7 +439,7 @@ pub mod apis { struct UserRepos { full_name: String, } - let watched_repo: Vec = fastn_core::auth::utils::get_api( + let watched_repo: Vec = fastn_core::http::get_api( "https://api.github.com/user/subscriptions?per_page=100", token, ) @@ -461,7 +457,7 @@ pub mod apis { struct RepoContributor { login: String, } - let repo_contributor: Vec = fastn_core::auth::utils::get_api( + let repo_contributor: Vec = fastn_core::http::get_api( format!("https://api.github.com/repos/{repo_name}/contributors?per_page=100",), token, ) @@ -479,7 +475,7 @@ pub mod apis { struct RepoCollaborator { login: String, } - let repo_collaborators_list: Vec = fastn_core::auth::utils::get_api( + let repo_collaborators_list: Vec = fastn_core::http::get_api( format!("https://api.github.com/repos/{repo_name}/collaborators?per_page=100"), token, ) @@ -517,7 +513,7 @@ pub mod apis { } let user_obj: UserDetails = - fastn_core::auth::utils::get_api("https://api.github.com/user", access_token).await?; + fastn_core::http::get_api("https://api.github.com/user", access_token).await?; Ok(String::from(&user_obj.login)) } diff --git a/fastn-core/src/auth/utils.rs b/fastn-core/src/auth/utils.rs index f89a127743..da456fa179 100644 --- a/fastn-core/src/auth/utils.rs +++ b/fastn-core/src/auth/utils.rs @@ -8,35 +8,6 @@ pub fn domain(host: &str) -> String { } } -pub async fn get_api( - url: impl AsRef, - bearer_token: &str, -) -> fastn_core::Result { - let response = reqwest::Client::new() - .get(url.as_ref()) - .header( - reqwest::header::AUTHORIZATION, - format!("{} {}", "Bearer", bearer_token), - ) - .header(reqwest::header::ACCEPT, "application/json") - .header( - reqwest::header::USER_AGENT, - reqwest::header::HeaderValue::from_static("fastn"), - ) - .send() - .await?; - - if !response.status().eq(&reqwest::StatusCode::OK) { - return Err(fastn_core::Error::APIResponseError(format!( - "fastn-API-ERROR: {}, Error: {}", - url.as_ref(), - response.text().await? - ))); - } - - Ok(response.json().await?) -} - pub async fn encrypt_str(user_detail_str: &String) -> String { use magic_crypt::MagicCryptTrait; let secret_key = fastn_core::auth::secret_key(); diff --git a/fastn-core/src/http.rs b/fastn-core/src/http.rs index 55e6893715..cb0e6be134 100644 --- a/fastn-core/src/http.rs +++ b/fastn-core/src/http.rs @@ -587,3 +587,33 @@ pub(crate) fn get_available_port( } None } + +// TODO: move to fastn_core::http +pub async fn get_api( + url: impl AsRef, + bearer_token: &str, +) -> fastn_core::Result { + let response = reqwest::Client::new() + .get(url.as_ref()) + .header( + reqwest::header::AUTHORIZATION, + format!("{} {}", "Bearer", bearer_token), + ) + .header(reqwest::header::ACCEPT, "application/json") + .header( + reqwest::header::USER_AGENT, + reqwest::header::HeaderValue::from_static("fastn"), + ) + .send() + .await?; + + if !response.status().eq(&reqwest::StatusCode::OK) { + return Err(fastn_core::Error::APIResponseError(format!( + "fastn-API-ERROR: {}, Error: {}", + url.as_ref(), + response.text().await? + ))); + } + + Ok(response.json().await?) +} From 172776bf9930153495a9620e9ccd9a670c1a27c2 Mon Sep 17 00:00:00 2001 From: Amit Upadhyay Date: Wed, 25 Oct 2023 13:52:04 +0530 Subject: [PATCH 18/44] some more refactor --- fastn-core/src/auth/github/apis.rs | 146 ++++++++++ .../src/auth/{github.rs => github/mod.rs} | 261 ++---------------- fastn-core/src/auth/github/utils.rs | 34 +++ fastn-core/src/http.rs | 32 +++ 4 files changed, 237 insertions(+), 236 deletions(-) create mode 100644 fastn-core/src/auth/github/apis.rs rename fastn-core/src/auth/{github.rs => github/mod.rs} (56%) create mode 100644 fastn-core/src/auth/github/utils.rs diff --git a/fastn-core/src/auth/github/apis.rs b/fastn-core/src/auth/github/apis.rs new file mode 100644 index 0000000000..cbd3da6ef7 --- /dev/null +++ b/fastn-core/src/auth/github/apis.rs @@ -0,0 +1,146 @@ +#[derive(Debug, serde::Deserialize)] +pub struct GraphQLResp { + pub data: Data, +} + +#[derive(Debug, serde::Deserialize)] +pub struct Data { + pub user: User, +} + +#[derive(Debug, serde::Deserialize)] +pub struct User { + #[serde(rename = "isSponsoredBy")] + pub is_sponsored_by: bool, +} + +// TODO: API to starred a repo on behalf of the user +// API Docs: https://docs.github.com/en/rest/activity/starring#list-repositories-starred-by-the-authenticated-user +pub async fn starred_repo(token: &str) -> fastn_core::Result> { + // API Docs: https://docs.github.com/en/rest/activity/starring#list-repositories-starred-by-the-authenticated-user + // TODO: Handle paginated response + + #[derive(Debug, serde::Deserialize)] + struct UserRepos { + full_name: String, + } + let starred_repo: Vec = + fastn_core::http::get_api("https://api.github.com/user/starred?per_page=100", token) + .await?; + Ok(starred_repo.into_iter().map(|x| x.full_name).collect()) +} + +pub async fn followed_org(token: &str) -> fastn_core::Result> { + // API Docs: https://docs.github.com/en/rest/users/followers#list-followers-of-the-authenticated-user + // TODO: Handle paginated response + #[derive(Debug, serde::Deserialize)] + struct FollowedOrg { + login: String, + } + let watched_repo: Vec = + fastn_core::http::get_api("https://api.github.com/user/following?per_page=100", token) + .await?; + Ok(watched_repo.into_iter().map(|x| x.login).collect()) +} + +pub async fn team_members( + token: &str, + org_title: &str, + team_slug: &str, +) -> fastn_core::Result> { + // API Docs: https://docs.github.com/en/rest/teams/members#list-team-members + // TODO: Handle paginated response + #[derive(Debug, serde::Deserialize)] + struct TeamMembers { + login: String, + } + + let user_orgs: Vec = fastn_core::http::get_api( + format!("https://api.github.com/orgs/{org_title}/teams/{team_slug}/members?per_page=100",), + token, + ) + .await?; + Ok(user_orgs.into_iter().map(|x| x.login).collect()) +} + +pub async fn watched_repo(token: &str) -> fastn_core::Result> { + // API Docs: https://docs.github.com/en/rest/activity/watching#list-repositories-watched-by-the-authenticated-user + // TODO: Handle paginated response + #[derive(Debug, serde::Deserialize)] + struct UserRepos { + full_name: String, + } + let watched_repo: Vec = fastn_core::http::get_api( + "https://api.github.com/user/subscriptions?per_page=100", + token, + ) + .await?; + Ok(watched_repo.into_iter().map(|x| x.full_name).collect()) +} + +pub async fn repo_contributors(token: &str, repo_name: &str) -> fastn_core::Result> { + // API Docs: https://docs.github.com/en/rest/activity/starring#list-repositories-starred-by-the-authenticated-user + // TODO: Handle paginated response + #[derive(Debug, serde::Deserialize)] + struct RepoContributor { + login: String, + } + let repo_contributor: Vec = fastn_core::http::get_api( + format!("https://api.github.com/repos/{repo_name}/contributors?per_page=100",), + token, + ) + .await?; + Ok(repo_contributor.into_iter().map(|x| x.login).collect()) +} + +pub async fn repo_collaborators(token: &str, repo_name: &str) -> fastn_core::Result> { + // API Docs: https://docs.github.com/en/rest/collaborators/collaborators#list-repository-collaborators + // TODO: Handle paginated response + #[derive(Debug, serde::Deserialize)] + struct RepoCollaborator { + login: String, + } + let repo_collaborators_list: Vec = fastn_core::http::get_api( + format!("https://api.github.com/repos/{repo_name}/collaborators?per_page=100"), + token, + ) + .await?; + Ok(repo_collaborators_list + .into_iter() + .map(|x| x.login) + .collect()) +} + +pub async fn is_user_sponsored( + token: &str, + username: &str, + sponsored_by: &str, +) -> fastn_core::Result { + let query = format!( + r#"query {{ + user(login: "{username}") + {{ isSponsoredBy(accountLogin: "{sponsored_by}" )}} + }}"# + ); + let sponsor_obj: GraphQLResp = fastn_core::http::github_graphql(query.as_str(), token).await?; + if sponsor_obj.data.user.is_sponsored_by { + Ok(true) + } else { + Ok(false) + } +} + +// TODO: It can be stored in the request cookies +pub async fn username(access_token: &str) -> fastn_core::Result { + // API Docs: https://docs.github.com/en/rest/users/users#get-the-authenticated-user + // TODO: Handle paginated response + #[derive(Debug, serde::Deserialize)] + struct UserDetails { + login: String, + } + + let user_obj: UserDetails = + fastn_core::http::get_api("https://api.github.com/user", access_token).await?; + + Ok(String::from(&user_obj.login)) +} diff --git a/fastn-core/src/auth/github.rs b/fastn-core/src/auth/github/mod.rs similarity index 56% rename from fastn-core/src/auth/github.rs rename to fastn-core/src/auth/github/mod.rs index 10435602a5..98eb61cf36 100644 --- a/fastn-core/src/auth/github.rs +++ b/fastn-core/src/auth/github/mod.rs @@ -1,3 +1,6 @@ +mod apis; +mod utils; + #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct UserDetail { pub access_token: String, @@ -17,7 +20,7 @@ pub async fn login( // Note: public_repos user:email all these things are github resources // So we have to tell oauth_client who is getting logged in what are we going to access - let (mut authorize_url, _token) = utils::github_client() + let (mut authorize_url, _token) = fastn_core::auth::github::utils::github_client() .set_redirect_uri(oauth2::RedirectUrl::new(redirect_url)?) .authorize_url(oauth2::CsrfToken::new_random) .add_scope(oauth2::Scope::new("public_repo".to_string())) @@ -43,7 +46,7 @@ pub async fn callback( let code = req.q("code", "".to_string())?; // TODO: CSRF check - let access_token = match utils::github_client() + let access_token = match fastn_core::auth::github::utils::github_client() .exchange_code(oauth2::AuthorizationCode::new(code)) .request_async(oauth2::reqwest::async_http_client) .await @@ -55,7 +58,7 @@ pub async fn callback( }; let ud = UserDetail { - username: apis::username(access_token.as_str()).await?, + username: fastn_core::auth::github::apis::username(access_token.as_str()).await?, access_token, }; @@ -131,7 +134,8 @@ pub async fn matched_starred_repos( if starred_repos.is_empty() { return Ok(vec![]); } - let user_starred_repos = apis::starred_repo(ud.access_token.as_str()).await?; + let user_starred_repos = + fastn_core::auth::github::apis::starred_repo(ud.access_token.as_str()).await?; // filter the user starred repos with input Ok(user_starred_repos .into_iter() @@ -161,7 +165,8 @@ pub async fn matched_watched_repos( if watched_repos.is_empty() { return Ok(vec![]); } - let user_watched_repos = apis::watched_repo(ud.access_token.as_str()).await?; + let user_watched_repos = + fastn_core::auth::github::apis::watched_repo(ud.access_token.as_str()).await?; // filter the user watched repos with input Ok(user_watched_repos .into_iter() @@ -191,7 +196,8 @@ pub async fn matched_followed_org( if followed_orgs.is_empty() { return Ok(vec![]); } - let user_followed_orgs = apis::followed_org(ud.access_token.as_str()).await?; + let user_followed_orgs = + fastn_core::auth::github::apis::followed_org(ud.access_token.as_str()).await?; // filter the user followed orgs with input Ok(user_followed_orgs .into_iter() @@ -224,7 +230,9 @@ pub async fn matched_contributed_repos( return Ok(vec![]); } for repo in &contributed_repos { - let repo_contributors = apis::repo_contributors(ud.access_token.as_str(), repo).await?; + let repo_contributors = + fastn_core::auth::github::apis::repo_contributors(ud.access_token.as_str(), repo) + .await?; if repo_contributors.contains(&ud.username) { matched_repo_contributors_list.push(String::from(repo.to_owned())); @@ -262,7 +270,9 @@ pub async fn matched_collaborated_repos( return Ok(vec![]); } for repo in &collaborated_repos { - let repo_collaborator = apis::repo_collaborators(ud.access_token.as_str(), repo).await?; + let repo_collaborator = + fastn_core::auth::github::apis::repo_collaborators(ud.access_token.as_str(), repo) + .await?; if repo_collaborator.contains(&ud.username) { matched_repo_collaborator_list.push(String::from(repo.to_owned())); @@ -302,8 +312,12 @@ pub async fn matched_org_teams( for org_team in org_teams.iter() { if let Some((org_name, team_name)) = org_team.split_once('/') { - let team_members: Vec = - apis::team_members(ud.access_token.as_str(), org_name, team_name).await?; + let team_members: Vec = fastn_core::auth::github::apis::team_members( + ud.access_token.as_str(), + org_name, + team_name, + ) + .await?; if team_members.contains(&ud.username) { matched_org_teams.push(org_team.to_string()); } @@ -344,7 +358,7 @@ pub async fn matched_sponsored_org( } for sponsor in sponsors_list.iter() { - if apis::is_user_sponsored( + if fastn_core::auth::github::apis::is_user_sponsored( ud.access_token.as_str(), ud.username.as_str(), sponsor.to_owned(), @@ -363,228 +377,3 @@ pub async fn matched_sponsored_org( }) .collect()) } - -pub mod apis { - #[derive(Debug, serde::Deserialize)] - pub struct GraphQLResp { - pub data: Data, - } - - #[derive(Debug, serde::Deserialize)] - pub struct Data { - pub user: User, - } - - #[derive(Debug, serde::Deserialize)] - pub struct User { - #[serde(rename = "isSponsoredBy")] - pub is_sponsored_by: bool, - } - - // TODO: API to starred a repo on behalf of the user - // API Docs: https://docs.github.com/en/rest/activity/starring#list-repositories-starred-by-the-authenticated-user - pub async fn starred_repo(token: &str) -> fastn_core::Result> { - // API Docs: https://docs.github.com/en/rest/activity/starring#list-repositories-starred-by-the-authenticated-user - // TODO: Handle paginated response - - #[derive(Debug, serde::Deserialize)] - struct UserRepos { - full_name: String, - } - let starred_repo: Vec = - fastn_core::http::get_api("https://api.github.com/user/starred?per_page=100", token) - .await?; - Ok(starred_repo.into_iter().map(|x| x.full_name).collect()) - } - - pub async fn followed_org(token: &str) -> fastn_core::Result> { - // API Docs: https://docs.github.com/en/rest/users/followers#list-followers-of-the-authenticated-user - // TODO: Handle paginated response - #[derive(Debug, serde::Deserialize)] - struct FollowedOrg { - login: String, - } - let watched_repo: Vec = - fastn_core::http::get_api("https://api.github.com/user/following?per_page=100", token) - .await?; - Ok(watched_repo.into_iter().map(|x| x.login).collect()) - } - - pub async fn team_members( - token: &str, - org_title: &str, - team_slug: &str, - ) -> fastn_core::Result> { - // API Docs: https://docs.github.com/en/rest/teams/members#list-team-members - // TODO: Handle paginated response - #[derive(Debug, serde::Deserialize)] - struct TeamMembers { - login: String, - } - - let user_orgs: Vec = fastn_core::http::get_api( - format!( - "https://api.github.com/orgs/{org_title}/teams/{team_slug}/members?per_page=100", - ), - token, - ) - .await?; - Ok(user_orgs.into_iter().map(|x| x.login).collect()) - } - - pub async fn watched_repo(token: &str) -> fastn_core::Result> { - // API Docs: https://docs.github.com/en/rest/activity/watching#list-repositories-watched-by-the-authenticated-user - // TODO: Handle paginated response - #[derive(Debug, serde::Deserialize)] - struct UserRepos { - full_name: String, - } - let watched_repo: Vec = fastn_core::http::get_api( - "https://api.github.com/user/subscriptions?per_page=100", - token, - ) - .await?; - Ok(watched_repo.into_iter().map(|x| x.full_name).collect()) - } - - pub async fn repo_contributors( - token: &str, - repo_name: &str, - ) -> fastn_core::Result> { - // API Docs: https://docs.github.com/en/rest/activity/starring#list-repositories-starred-by-the-authenticated-user - // TODO: Handle paginated response - #[derive(Debug, serde::Deserialize)] - struct RepoContributor { - login: String, - } - let repo_contributor: Vec = fastn_core::http::get_api( - format!("https://api.github.com/repos/{repo_name}/contributors?per_page=100",), - token, - ) - .await?; - Ok(repo_contributor.into_iter().map(|x| x.login).collect()) - } - - pub async fn repo_collaborators( - token: &str, - repo_name: &str, - ) -> fastn_core::Result> { - // API Docs: https://docs.github.com/en/rest/collaborators/collaborators#list-repository-collaborators - // TODO: Handle paginated response - #[derive(Debug, serde::Deserialize)] - struct RepoCollaborator { - login: String, - } - let repo_collaborators_list: Vec = fastn_core::http::get_api( - format!("https://api.github.com/repos/{repo_name}/collaborators?per_page=100"), - token, - ) - .await?; - Ok(repo_collaborators_list - .into_iter() - .map(|x| x.login) - .collect()) - } - - pub async fn is_user_sponsored( - token: &str, - username: &str, - sponsored_by: &str, - ) -> fastn_core::Result { - let query = format!( - "query {{ user(login: \"{username}\") {{ isSponsoredBy(accountLogin: \"{sponsored_by}\" )}} }}", - ); - let sponsor_obj = - graphql_sponsor_api("https://api.github.com/graphql", query.as_str(), token).await?; - if sponsor_obj.data.user.is_sponsored_by { - Ok(true) - } else { - Ok(false) - } - } - - // TODO: It can be stored in the request cookies - pub async fn username(access_token: &str) -> fastn_core::Result { - // API Docs: https://docs.github.com/en/rest/users/users#get-the-authenticated-user - // TODO: Handle paginated response - #[derive(Debug, serde::Deserialize)] - struct UserDetails { - login: String, - } - - let user_obj: UserDetails = - fastn_core::http::get_api("https://api.github.com/user", access_token).await?; - - Ok(String::from(&user_obj.login)) - } - - pub async fn graphql_sponsor_api( - url: &str, - query_str: &str, - token: &str, - ) -> fastn_core::Result { - let mut map: std::collections::HashMap<&str, &str> = std::collections::HashMap::new(); - map.insert("query", query_str); - - let response = reqwest::Client::new() - .post(url) - .json(&map) - .header( - reqwest::header::AUTHORIZATION, - format!("{} {}", "Bearer", token), - ) - .header(reqwest::header::ACCEPT, "application/json") - .header( - reqwest::header::USER_AGENT, - reqwest::header::HeaderValue::from_static("fastn"), - ) - .send() - .await?; - if !response.status().eq(&reqwest::StatusCode::OK) { - return Err(fastn_core::Error::APIResponseError(format!( - "GitHub API ERROR: {}", - url - ))); - } - let return_obj = response.json::().await?; - - Ok(return_obj) - } -} - -pub mod utils { - // Lazy means a value which initialize at the first time access - // we have to access it before using it and make sure to use it while starting a server - // TODO: they should be configured with auth feature flag - // if feature flag auth is enabled Make sure that before accessing in the API these variable - // are set - static GITHUB_CLIENT_ID: once_cell::sync::Lazy = { - once_cell::sync::Lazy::new(|| { - oauth2::ClientId::new(match std::env::var("FASTN_GITHUB_CLIENT_ID") { - Ok(val) => val, - Err(e) => format!("{}{}", "FASTN_GITHUB_CLIENT_ID not set in env ", e), - }) - }) - }; - - static GITHUB_CLIENT_SECRET: once_cell::sync::Lazy = { - once_cell::sync::Lazy::new(|| { - oauth2::ClientSecret::new(match std::env::var("FASTN_GITHUB_CLIENT_SECRET") { - Ok(val) => val, - Err(e) => format!("{}{}", "FASTN_GITHUB_CLIENT_SECRET not set in env ", e), - }) - }) - }; - - pub fn github_client() -> oauth2::basic::BasicClient { - oauth2::basic::BasicClient::new( - GITHUB_CLIENT_ID.to_owned(), - Some(GITHUB_CLIENT_SECRET.to_owned()), - oauth2::AuthUrl::new("https://github.com/login/oauth/authorize".to_string()).unwrap(), - Some( - oauth2::TokenUrl::new("https://github.com/login/oauth/access_token".to_string()) - .expect("Invalid token endpoint URL"), - ), - ) - } -} diff --git a/fastn-core/src/auth/github/utils.rs b/fastn-core/src/auth/github/utils.rs new file mode 100644 index 0000000000..7cbc2b3bc0 --- /dev/null +++ b/fastn-core/src/auth/github/utils.rs @@ -0,0 +1,34 @@ +// Lazy means a value which initialize at the first time access +// we have to access it before using it and make sure to use it while starting a server +// TODO: they should be configured with auth feature flag +// if feature flag auth is enabled Make sure that before accessing in the API these variable +// are set +static GITHUB_CLIENT_ID: once_cell::sync::Lazy = { + once_cell::sync::Lazy::new(|| { + oauth2::ClientId::new(match std::env::var("FASTN_GITHUB_CLIENT_ID") { + Ok(val) => val, + Err(e) => format!("{}{}", "FASTN_GITHUB_CLIENT_ID not set in env ", e), + }) + }) +}; + +static GITHUB_CLIENT_SECRET: once_cell::sync::Lazy = { + once_cell::sync::Lazy::new(|| { + oauth2::ClientSecret::new(match std::env::var("FASTN_GITHUB_CLIENT_SECRET") { + Ok(val) => val, + Err(e) => format!("{}{}", "FASTN_GITHUB_CLIENT_SECRET not set in env ", e), + }) + }) +}; + +pub fn github_client() -> oauth2::basic::BasicClient { + oauth2::basic::BasicClient::new( + GITHUB_CLIENT_ID.to_owned(), + Some(GITHUB_CLIENT_SECRET.to_owned()), + oauth2::AuthUrl::new("https://github.com/login/oauth/authorize".to_string()).unwrap(), + Some( + oauth2::TokenUrl::new("https://github.com/login/oauth/access_token".to_string()) + .expect("Invalid token endpoint URL"), + ), + ) +} diff --git a/fastn-core/src/http.rs b/fastn-core/src/http.rs index cb0e6be134..f204df7466 100644 --- a/fastn-core/src/http.rs +++ b/fastn-core/src/http.rs @@ -617,3 +617,35 @@ pub async fn get_api( Ok(response.json().await?) } + +pub async fn github_graphql( + query: &str, + token: &str, +) -> fastn_core::Result { + let mut map: std::collections::HashMap<&str, &str> = std::collections::HashMap::new(); + map.insert("query", query); + + let response = reqwest::Client::new() + .post("https://api.github.com/graphql") + .json(&map) + .header( + reqwest::header::AUTHORIZATION, + format!("{} {}", "Bearer", token), + ) + .header(reqwest::header::ACCEPT, "application/json") + .header( + reqwest::header::USER_AGENT, + reqwest::header::HeaderValue::from_static("fastn"), + ) + .send() + .await?; + if !response.status().eq(&reqwest::StatusCode::OK) { + return Err(fastn_core::Error::APIResponseError(format!( + "GitHub API ERROR: {}", + response.status() + ))); + } + let return_obj = response.json::().await?; + + Ok(return_obj) +} From f1ba5529b011f5c7ef3361e75c67764dc45c1445 Mon Sep 17 00:00:00 2001 From: Arpita-Jaiswal Date: Wed, 25 Oct 2023 17:00:51 +0530 Subject: [PATCH 19/44] Mobile first --- fastn-js/js/dom.js | 1 - fastn-js/js/postInit.js | 59 ++++++++++++++------------- fastn-js/js/virtual.js | 18 ++++++++ ftd/src/interpreter/things/default.rs | 6 +-- 4 files changed, 51 insertions(+), 33 deletions(-) diff --git a/fastn-js/js/dom.js b/fastn-js/js/dom.js index e237cc64e3..1dec843463 100644 --- a/fastn-js/js/dom.js +++ b/fastn-js/js/dom.js @@ -370,7 +370,6 @@ fastn_dom.Spacing = { Fixed: (value) => { return [4, value]; } } - fastn_dom.BorderStyle = { Solid: "solid", Dashed: "dashed", diff --git a/fastn-js/js/postInit.js b/fastn-js/js/postInit.js index f09e309c28..928188d0c8 100644 --- a/fastn-js/js/postInit.js +++ b/fastn-js/js/postInit.js @@ -2,6 +2,34 @@ ftd.clickOutsideEvents = []; ftd.globalKeyEvents = []; ftd.globalKeySeqEvents = []; + +ftd.get_device = function () { + const MOBILE_CLASS = "mobile"; + // not at all sure about this function logic. + let width = window.innerWidth; + // In the future, we may want to have more than one break points, and + // then we may also want the theme builders to decide where the + // breakpoints should go. we should be able to fetch fpm variables + // here, or maybe simply pass the width, user agent etc. to fpm and + // let people put the checks on width user agent etc., but it would + // be good if we can standardize few breakpoints. or maybe we should + // do both, some standard breakpoints and pass the raw data. + // we would then rename this function to detect_device() which will + // return one of "desktop", "mobile". and also maybe have another + // function detect_orientation(), "landscape" and "portrait" etc., + // and instead of setting `ftd#mobile: boolean` we set `ftd#device` + // and `ftd#view-port-orientation` etc. + let mobile_breakpoint = fastn_utils.getStaticValue(ftd.breakpoint_width.get("mobile")); + if (width <= mobile_breakpoint) { + document.body.classList.add(MOBILE_CLASS); + return fastn_dom.DeviceData.Mobile; + } + if (document.body.classList.contains(MOBILE_CLASS)) { + document.body.classList.remove(MOBILE_CLASS); + } + return fastn_dom.DeviceData.Desktop; +} + ftd.post_init = function () { const DARK_MODE_COOKIE = "fastn-dark-mode"; const COOKIE_SYSTEM_LIGHT = "system-light"; @@ -9,7 +37,6 @@ ftd.post_init = function () { const COOKIE_DARK_MODE = "dark"; const COOKIE_LIGHT_MODE = "light"; const DARK_MODE_CLASS = "dark"; - const MOBILE_CLASS = "mobile"; let last_device = "desktop"; window.onresize = function () { @@ -71,7 +98,7 @@ ftd.post_init = function () { }) } function initialise_device() { - let current = get_device(); + let current = ftd.get_device(); if (current === last_device) { return; } @@ -80,32 +107,6 @@ ftd.post_init = function () { last_device = current; } - function get_device() { - // not at all sure about this function logic. - let width = window.innerWidth; - // In the future, we may want to have more than one break points, and - // then we may also want the theme builders to decide where the - // breakpoints should go. we should be able to fetch fpm variables - // here, or maybe simply pass the width, user agent etc. to fpm and - // let people put the checks on width user agent etc., but it would - // be good if we can standardize few breakpoints. or maybe we should - // do both, some standard breakpoints and pass the raw data. - // we would then rename this function to detect_device() which will - // return one of "desktop", "mobile". and also maybe have another - // function detect_orientation(), "landscape" and "portrait" etc., - // and instead of setting `ftd#mobile: boolean` we set `ftd#device` - // and `ftd#view-port-orientation` etc. - let mobile_breakpoint = fastn_utils.getStaticValue(ftd.breakpoint_width.get("mobile")); - if (width <= mobile_breakpoint) { - document.body.classList.add(MOBILE_CLASS); - return fastn_dom.DeviceData.Mobile; - } - if (document.body.classList.contains(MOBILE_CLASS)) { - document.body.classList.remove(MOBILE_CLASS); - } - return fastn_dom.DeviceData.Desktop; - } - /* ftd.dark-mode behaviour: @@ -216,8 +217,8 @@ ftd.post_init = function () { function start_watching_dark_mode_system_preference() { window.matchMedia('(prefers-color-scheme: dark)').addEventListener("change", update_dark_mode); } - initialise_dark_mode(); initialise_device(); + initialise_dark_mode(); initialise_click_outside_events(); initialise_global_key_events(); fastn_utils.resetFullHeight(); diff --git a/fastn-js/js/virtual.js b/fastn-js/js/virtual.js index a033fe87ad..bbab3af807 100644 --- a/fastn-js/js/virtual.js +++ b/fastn-js/js/virtual.js @@ -149,6 +149,24 @@ fastn_virtual.document = new Document2(); fastn_virtual.hydrate = function(main) { + let current_device = ftd.get_device(); + let found_device = ftd.device.get(); + if (current_device !== found_device) { + ftd.device = fastn.mutable(current_device); + // let styles = document.getElementById("styles"); + // styles.innerText = ""; + var children = document.body.children; + // Loop through the direct children and remove those with tagName 'div' + for (var i = children.length - 1; i >= 0; i--) { + var child = children[i]; + if (child.tagName === 'DIV') { + document.body.removeChild(child); + } + } + + main(document.body); + return; + } hydrating = true; let body = fastn_virtual.document.createElement("body"); main(body); diff --git a/ftd/src/interpreter/things/default.rs b/ftd/src/interpreter/things/default.rs index df11ec6065..4430afcb77 100644 --- a/ftd/src/interpreter/things/default.rs +++ b/ftd/src/interpreter/things/default.rs @@ -9693,9 +9693,9 @@ pub fn default_bag() -> indexmap::IndexMap { value: ftd::interpreter::PropertyValue::Value { value: ftd::interpreter::Value::OrType { name: ftd::interpreter::FTD_DEVICE_DATA.to_string(), - variant: ftd::interpreter::FTD_DEVICE_DATA_DESKTOP.to_string(), - full_variant: ftd::interpreter::FTD_DEVICE_DATA_DESKTOP.to_string(), - value: Box::new(ftd::interpreter::Value::new_string("desktop") + variant: ftd::interpreter::FTD_DEVICE_DATA_MOBILE.to_string(), + full_variant: ftd::interpreter::FTD_DEVICE_DATA_MOBILE.to_string(), + value: Box::new(ftd::interpreter::Value::new_string("mobile") .into_property_value(false, 0)) }, is_mutable: true, From e6ff850c1b28ddfad4ebd423b7dbe41f760a3759 Mon Sep 17 00:00:00 2001 From: Arpita-Jaiswal Date: Wed, 25 Oct 2023 17:35:42 +0530 Subject: [PATCH 20/44] Fixes: Mobile first --- fastn-js/js/postInit.js | 2 +- fastn-js/js/virtual.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fastn-js/js/postInit.js b/fastn-js/js/postInit.js index 928188d0c8..06d0b88933 100644 --- a/fastn-js/js/postInit.js +++ b/fastn-js/js/postInit.js @@ -37,7 +37,7 @@ ftd.post_init = function () { const COOKIE_DARK_MODE = "dark"; const COOKIE_LIGHT_MODE = "light"; const DARK_MODE_CLASS = "dark"; - let last_device = "desktop"; + let last_device = ftd.device.get(); window.onresize = function () { initialise_device() diff --git a/fastn-js/js/virtual.js b/fastn-js/js/virtual.js index bbab3af807..8ee1b9d65e 100644 --- a/fastn-js/js/virtual.js +++ b/fastn-js/js/virtual.js @@ -153,8 +153,8 @@ fastn_virtual.hydrate = function(main) { let found_device = ftd.device.get(); if (current_device !== found_device) { ftd.device = fastn.mutable(current_device); - // let styles = document.getElementById("styles"); - // styles.innerText = ""; + let styles = document.getElementById("styles"); + styles.innerText = ""; var children = document.body.children; // Loop through the direct children and remove those with tagName 'div' for (var i = children.length - 1; i >= 0; i--) { From dc4cad858a929039a9120abead3f6d84c83b7c9d Mon Sep 17 00:00:00 2001 From: Arpita-Jaiswal Date: Wed, 25 Oct 2023 18:48:44 +0530 Subject: [PATCH 21/44] Style fixes --- fastn-js/js/dom.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/fastn-js/js/dom.js b/fastn-js/js/dom.js index 1dec843463..90856f2c01 100644 --- a/fastn-js/js/dom.js +++ b/fastn-js/js/dom.js @@ -938,7 +938,13 @@ class Node2 { if (!fastn_dom.classes[cssClass]) { fastn_dom.classes[cssClass] = fastn_dom.classes[cssClass] || obj; let styles = document.getElementById('styles'); - styles.innerHTML = `${styles.innerHTML}${getClassAsString(cssClass, obj)}\n`; + let newClasses = getClassAsString(cssClass, obj); + let textNode = document.createTextNode(newClasses); + if (styles.styleSheet) { + styles.styleSheet.cssText = newClasses; + } else { + styles.appendChild(textNode); + } } return cls; } @@ -953,7 +959,13 @@ class Node2 { if (!fastn_dom.classes[cssClass]) { fastn_dom.classes[cssClass] = fastn_dom.classes[cssClass] || obj; let styles = document.getElementById('styles'); - styles.innerHTML = `${styles.innerHTML}${getClassAsString(cssClass, obj)}\n`; + let newClasses = getClassAsString(cssClass, obj); + let textNode = document.createTextNode(newClasses); + if (styles.styleSheet) { + styles.styleSheet.cssText = newClasses; + } else { + styles.appendChild(textNode); + } } this.#node.style.removeProperty(property); this.#node.classList.add(cls); From c4dcc737693a0bbefa7e9941366d8050ed6bd1e2 Mon Sep 17 00:00:00 2001 From: heulitig Date: Wed, 25 Oct 2023 19:39:41 +0530 Subject: [PATCH 22/44] fbt + html + js tests fixed --- fastn-core/tests/02-hello/output/index.html | 2 +- .../tests/03-nested-document/output/index.html | 2 +- .../output/nested/document/index.html | 2 +- .../03-nested-document/output/nested/index.html | 2 +- .../tests/04-import-code-block/output/index.html | 2 +- .../04-import-code-block/output/lib/index.html | 2 +- fastn-core/tests/05-hello-font/output/index.html | 2 +- .../tests/08-static-assets/output/index.html | 2 +- .../tests/09-markdown-pages/output/index.html | 2 +- .../tests/11-readme-with-index/output/index.html | 2 +- .../15-fpm-dependency-alias/output/index.html | 2 +- .../tests/16-include-processor/output/index.html | 2 +- ftd/t/html/1-component.html | 2 +- ftd/t/html/10-conditional-properties.html | 2 +- ftd/t/html/100-linear-gradient.html | 2 +- ftd/t/html/100-re-export.html | 2 +- ftd/t/html/101-re-re-export.html | 2 +- ftd/t/html/102-access-modifiers.html | 2 +- ftd/t/html/103-block-header-record.html | 2 +- ftd/t/html/104-block-header-record-extended.html | 2 +- ftd/t/html/105-document-breakpoint.html | 2 +- ftd/t/html/106-comments.html | 2 +- ftd/t/html/107-old-fastn-code-syntax.html | 2 +- ftd/t/html/108-linear-gradient-conditional.html | 2 +- ftd/t/html/109-image-fit.html | 2 +- ftd/t/html/11-external-children.html | 2 +- ftd/t/html/110-fallback-fonts.html | 2 +- ftd/t/html/12-conditional-component.html | 2 +- ftd/t/html/13-image.html | 2 +- ftd/t/html/14-processor.html | 2 +- ftd/t/html/15-foreign-variable.html | 2 +- ftd/t/html/16-or-type.html | 2 +- ftd/t/html/17-record.html | 2 +- ftd/t/html/18-styles.html | 2 +- ftd/t/html/19-complex-styles.html | 2 +- ftd/t/html/2-component.html | 2 +- ftd/t/html/20-link.html | 2 +- ftd/t/html/21-color.html | 2 +- ftd/t/html/22-test.html | 2 +- ftd/t/html/23-alignment.html | 2 +- ftd/t/html/23-doc-site.html | 2 +- ftd/t/html/24-margin.html | 2 +- ftd/t/html/25-expander.html | 2 +- ftd/t/html/25-overflow.html | 2 +- ftd/t/html/26-border.html | 2 +- ftd/t/html/27-optional.html | 2 +- ftd/t/html/28-complex.html | 2 +- ftd/t/html/29-slides.html | 2 +- ftd/t/html/3-component.html | 2 +- ftd/t/html/30-slides.html | 2 +- ftd/t/html/31-message.html | 2 +- ftd/t/html/32-test.html | 2 +- ftd/t/html/33-component-using-css.html | 2 +- ftd/t/html/33-using-css.html | 2 +- ftd/t/html/34-device.html | 4 ++-- ftd/t/html/35-condition-on-color.html | 2 +- ftd/t/html/36-test.html | 4 ++-- ftd/t/html/37-cursor.html | 2 +- ftd/t/html/38-role.html | 2 +- ftd/t/html/39-events.html | 2 +- ftd/t/html/4-component.html | 2 +- ftd/t/html/40-anchor.html | 2 +- ftd/t/html/41-responsive-type.html | 2 +- ftd/t/html/42-default-function.html | 2 +- ftd/t/html/43-default-colors-types.html | 2 +- ftd/t/html/44-region.html | 2 +- ftd/t/html/45-using-hyphen.html | 2 +- ftd/t/html/46-record-in-pscript.html | 2 +- ftd/t/html/47-white-space.html | 2 +- ftd/t/html/48-basic-functions.html | 2 +- ftd/t/html/49-import.html | 2 +- ftd/t/html/5-component-recursion.html | 2 +- ftd/t/html/50-using-import.html | 2 +- ftd/t/html/51-text-transform.html | 2 +- ftd/t/html/52-code-and-iframe.html | 2 +- ftd/t/html/53-decimal.html | 2 +- ftd/t/html/53-font-family.html | 2 +- ftd/t/html/54-input.html | 2 +- ftd/t/html/55-inherited.html | 2 +- ftd/t/html/55-line-clamp.html | 2 +- ftd/t/html/56-passing-events.html | 2 +- ftd/t/html/57-border-style.html | 2 +- ftd/t/html/58-id.html | 2 +- ftd/t/html/59-sticky.html | 2 +- ftd/t/html/6-function.html | 2 +- ftd/t/html/60-region-id-slug.html | 2 +- ftd/t/html/61-loop-variable.html | 2 +- ftd/t/html/62-spacing.html | 2 +- ftd/t/html/63-checkbox.html | 2 +- ftd/t/html/64-muliple-node-dep.html | 2 +- ftd/t/html/64-multiple-node-dep-1.html | 2 +- ftd/t/html/65-mut-loop.html | 2 +- ftd/t/html/66-inheritance.html | 2 +- ftd/t/html/67-enabled.html | 2 +- ftd/t/html/68-anchor-id.html | 2 +- ftd/t/html/69-inside-loop.html | 2 +- ftd/t/html/7-events.html | 2 +- ftd/t/html/70-figma-json-to-ftd.html | 2 +- ftd/t/html/70-length-check.html | 2 +- ftd/t/html/71-web-component.html | 2 +- ftd/t/html/72-external-js.html | 2 +- ftd/t/html/73-complex-ftd-ui.html | 2 +- ftd/t/html/74-import-complex-ftd-ui.html | 2 +- ftd/t/html/75-ui-list-display.html | 2 +- ftd/t/html/76-inter-argument.html | 2 +- ftd/t/html/77-property-source-fix.html | 2 +- ftd/t/html/79-shorthand-lists.html | 2 +- ftd/t/html/8-counter.html | 2 +- ftd/t/html/80-module.html | 2 +- ftd/t/html/81-markdown.html | 2 +- ftd/t/html/82-text-style.html | 2 +- ftd/t/html/83-text-indent.html | 2 +- ftd/t/html/84-ftd-ui-list-issue.html | 2 +- ftd/t/html/85-bg-image.html | 2 +- ftd/t/html/86-ftd-document.html | 2 +- ftd/t/html/86-shadow.html | 2 +- ftd/t/html/87-bg-repeat.html | 2 +- ftd/t/html/87-mutability.html | 2 +- ftd/t/html/88-ftd-length.html | 4 ++-- ftd/t/html/89-display.html | 2 +- ftd/t/html/9-conditional-properties.html | 2 +- ftd/t/html/90-img-alt.html | 2 +- ftd/t/html/91-opacity.html | 2 +- ftd/t/html/92-rive.html | 2 +- ftd/t/html/93-rive-bell.html | 2 +- ftd/t/html/94-rive-toggle.html | 2 +- ftd/t/html/95-rive-bell-animation.html | 2 +- ftd/t/html/96-rive-truck-animation.html | 2 +- ftd/t/html/97-rive-fastn.html | 4 ++-- ftd/t/html/98-device.html | 4 ++-- ftd/t/html/99-unoptimized-device.html | 4 ++-- ftd/t/html/check.html | 2 +- ftd/t/html/function.html | 2 +- ftd/t/html/get.html | 2 +- ftd/t/html/h-100.html | 2 +- ftd/t/html/resume.html | 2 +- ftd/t/html/sd.html | 2 +- ftd/t/js/11-device.html | 3 +-- ftd/t/js/24-device.html | 2 +- ftd/t/js/30-web-component.html | 14 ++++++++------ ftd/t/js/31-advance-list.html | 6 +++--- ftd/t/js/42-links.html | 2 +- ftd/t/js/50-iframe-fullscreen.html | 2 +- 143 files changed, 158 insertions(+), 157 deletions(-) diff --git a/fastn-core/tests/02-hello/output/index.html b/fastn-core/tests/02-hello/output/index.html index bcaeebf4b2..9cd0c98c4c 100644 --- a/fastn-core/tests/02-hello/output/index.html +++ b/fastn-core/tests/02-hello/output/index.html @@ -563,7 +563,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/fastn-core/tests/03-nested-document/output/index.html b/fastn-core/tests/03-nested-document/output/index.html index 2174026e55..b4650db6ba 100644 --- a/fastn-core/tests/03-nested-document/output/index.html +++ b/fastn-core/tests/03-nested-document/output/index.html @@ -562,7 +562,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/fastn-core/tests/03-nested-document/output/nested/document/index.html b/fastn-core/tests/03-nested-document/output/nested/document/index.html index f619c9cc04..2970767a43 100644 --- a/fastn-core/tests/03-nested-document/output/nested/document/index.html +++ b/fastn-core/tests/03-nested-document/output/nested/document/index.html @@ -562,7 +562,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/fastn-core/tests/03-nested-document/output/nested/index.html b/fastn-core/tests/03-nested-document/output/nested/index.html index 356cb73d11..c334813562 100644 --- a/fastn-core/tests/03-nested-document/output/nested/index.html +++ b/fastn-core/tests/03-nested-document/output/nested/index.html @@ -562,7 +562,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/fastn-core/tests/04-import-code-block/output/index.html b/fastn-core/tests/04-import-code-block/output/index.html index 2bd5cadce6..0f1c406eac 100644 --- a/fastn-core/tests/04-import-code-block/output/index.html +++ b/fastn-core/tests/04-import-code-block/output/index.html @@ -563,7 +563,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/fastn-core/tests/04-import-code-block/output/lib/index.html b/fastn-core/tests/04-import-code-block/output/lib/index.html index 54cc11b02d..38185efcca 100644 --- a/fastn-core/tests/04-import-code-block/output/lib/index.html +++ b/fastn-core/tests/04-import-code-block/output/lib/index.html @@ -562,7 +562,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/fastn-core/tests/05-hello-font/output/index.html b/fastn-core/tests/05-hello-font/output/index.html index 713f52899f..a41ce499c1 100644 --- a/fastn-core/tests/05-hello-font/output/index.html +++ b/fastn-core/tests/05-hello-font/output/index.html @@ -562,7 +562,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/fastn-core/tests/08-static-assets/output/index.html b/fastn-core/tests/08-static-assets/output/index.html index 2174026e55..b4650db6ba 100644 --- a/fastn-core/tests/08-static-assets/output/index.html +++ b/fastn-core/tests/08-static-assets/output/index.html @@ -562,7 +562,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/fastn-core/tests/09-markdown-pages/output/index.html b/fastn-core/tests/09-markdown-pages/output/index.html index 2174026e55..b4650db6ba 100644 --- a/fastn-core/tests/09-markdown-pages/output/index.html +++ b/fastn-core/tests/09-markdown-pages/output/index.html @@ -562,7 +562,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/fastn-core/tests/11-readme-with-index/output/index.html b/fastn-core/tests/11-readme-with-index/output/index.html index 30aa65dc4f..12bd740dec 100644 --- a/fastn-core/tests/11-readme-with-index/output/index.html +++ b/fastn-core/tests/11-readme-with-index/output/index.html @@ -562,7 +562,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/fastn-core/tests/15-fpm-dependency-alias/output/index.html b/fastn-core/tests/15-fpm-dependency-alias/output/index.html index 82db1fc7f3..508b42baf7 100644 --- a/fastn-core/tests/15-fpm-dependency-alias/output/index.html +++ b/fastn-core/tests/15-fpm-dependency-alias/output/index.html @@ -562,7 +562,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/fastn-core/tests/16-include-processor/output/index.html b/fastn-core/tests/16-include-processor/output/index.html index e1540936ba..3907fbf693 100644 --- a/fastn-core/tests/16-include-processor/output/index.html +++ b/fastn-core/tests/16-include-processor/output/index.html @@ -563,7 +563,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/1-component.html b/ftd/t/html/1-component.html index aa9039f5e0..7cfe6aa66b 100644 --- a/ftd/t/html/1-component.html +++ b/ftd/t/html/1-component.html @@ -562,7 +562,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/10-conditional-properties.html b/ftd/t/html/10-conditional-properties.html index db871ddc21..045f04eb73 100644 --- a/ftd/t/html/10-conditional-properties.html +++ b/ftd/t/html/10-conditional-properties.html @@ -565,7 +565,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/100-linear-gradient.html b/ftd/t/html/100-linear-gradient.html index a4e613093d..1449a0ee6d 100644 --- a/ftd/t/html/100-linear-gradient.html +++ b/ftd/t/html/100-linear-gradient.html @@ -742,7 +742,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/100-re-export.html b/ftd/t/html/100-re-export.html index 2ad12cfa4f..f723c35bc9 100644 --- a/ftd/t/html/100-re-export.html +++ b/ftd/t/html/100-re-export.html @@ -562,7 +562,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/101-re-re-export.html b/ftd/t/html/101-re-re-export.html index 342b6798f1..6a52adb50e 100644 --- a/ftd/t/html/101-re-re-export.html +++ b/ftd/t/html/101-re-re-export.html @@ -564,7 +564,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/102-access-modifiers.html b/ftd/t/html/102-access-modifiers.html index c715153d2b..f403fc21e6 100644 --- a/ftd/t/html/102-access-modifiers.html +++ b/ftd/t/html/102-access-modifiers.html @@ -564,7 +564,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/103-block-header-record.html b/ftd/t/html/103-block-header-record.html index 8e981052ca..1a013655f9 100644 --- a/ftd/t/html/103-block-header-record.html +++ b/ftd/t/html/103-block-header-record.html @@ -562,7 +562,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/104-block-header-record-extended.html b/ftd/t/html/104-block-header-record-extended.html index af3119f3e6..8b6ea2b578 100644 --- a/ftd/t/html/104-block-header-record-extended.html +++ b/ftd/t/html/104-block-header-record-extended.html @@ -603,7 +603,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/105-document-breakpoint.html b/ftd/t/html/105-document-breakpoint.html index 7b554059b7..e77d000780 100644 --- a/ftd/t/html/105-document-breakpoint.html +++ b/ftd/t/html/105-document-breakpoint.html @@ -562,7 +562,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/106-comments.html b/ftd/t/html/106-comments.html index f09884a4bf..b4287ffe4d 100644 --- a/ftd/t/html/106-comments.html +++ b/ftd/t/html/106-comments.html @@ -562,7 +562,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/107-old-fastn-code-syntax.html b/ftd/t/html/107-old-fastn-code-syntax.html index cd4011deab..ac88e99c5e 100644 --- a/ftd/t/html/107-old-fastn-code-syntax.html +++ b/ftd/t/html/107-old-fastn-code-syntax.html @@ -563,7 +563,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/108-linear-gradient-conditional.html b/ftd/t/html/108-linear-gradient-conditional.html index 641fd60310..d21f2b468c 100644 --- a/ftd/t/html/108-linear-gradient-conditional.html +++ b/ftd/t/html/108-linear-gradient-conditional.html @@ -607,7 +607,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/109-image-fit.html b/ftd/t/html/109-image-fit.html index 53b7e8338e..aa3cf8c4b5 100644 --- a/ftd/t/html/109-image-fit.html +++ b/ftd/t/html/109-image-fit.html @@ -562,7 +562,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/11-external-children.html b/ftd/t/html/11-external-children.html index fc5714cc9f..f290e82f17 100644 --- a/ftd/t/html/11-external-children.html +++ b/ftd/t/html/11-external-children.html @@ -567,7 +567,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/110-fallback-fonts.html b/ftd/t/html/110-fallback-fonts.html index d2732f0a36..dcc62167a1 100644 --- a/ftd/t/html/110-fallback-fonts.html +++ b/ftd/t/html/110-fallback-fonts.html @@ -591,7 +591,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/12-conditional-component.html b/ftd/t/html/12-conditional-component.html index b567f52bcd..80ac8a9b3b 100644 --- a/ftd/t/html/12-conditional-component.html +++ b/ftd/t/html/12-conditional-component.html @@ -564,7 +564,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/13-image.html b/ftd/t/html/13-image.html index 87ea015e73..6a177d7075 100644 --- a/ftd/t/html/13-image.html +++ b/ftd/t/html/13-image.html @@ -572,7 +572,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/14-processor.html b/ftd/t/html/14-processor.html index 9562db7b75..dca3a47640 100644 --- a/ftd/t/html/14-processor.html +++ b/ftd/t/html/14-processor.html @@ -563,7 +563,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/15-foreign-variable.html b/ftd/t/html/15-foreign-variable.html index 1d1a345375..f5c3abca76 100644 --- a/ftd/t/html/15-foreign-variable.html +++ b/ftd/t/html/15-foreign-variable.html @@ -562,7 +562,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/16-or-type.html b/ftd/t/html/16-or-type.html index 3183071481..034592a97e 100644 --- a/ftd/t/html/16-or-type.html +++ b/ftd/t/html/16-or-type.html @@ -565,7 +565,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/17-record.html b/ftd/t/html/17-record.html index 2230d5db33..a6d6fe3e58 100644 --- a/ftd/t/html/17-record.html +++ b/ftd/t/html/17-record.html @@ -569,7 +569,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/18-styles.html b/ftd/t/html/18-styles.html index 716a096792..8ec0fd1986 100644 --- a/ftd/t/html/18-styles.html +++ b/ftd/t/html/18-styles.html @@ -564,7 +564,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/19-complex-styles.html b/ftd/t/html/19-complex-styles.html index bbbefaebe3..6df4070c88 100644 --- a/ftd/t/html/19-complex-styles.html +++ b/ftd/t/html/19-complex-styles.html @@ -566,7 +566,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/2-component.html b/ftd/t/html/2-component.html index 08447ecf62..7fe2732bab 100644 --- a/ftd/t/html/2-component.html +++ b/ftd/t/html/2-component.html @@ -562,7 +562,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/20-link.html b/ftd/t/html/20-link.html index e873dba7d4..f8e3e6d9cd 100644 --- a/ftd/t/html/20-link.html +++ b/ftd/t/html/20-link.html @@ -562,7 +562,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/21-color.html b/ftd/t/html/21-color.html index d2fa1e8044..c512d4f05e 100644 --- a/ftd/t/html/21-color.html +++ b/ftd/t/html/21-color.html @@ -570,7 +570,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/22-test.html b/ftd/t/html/22-test.html index ff040f93dc..9718f871d1 100644 --- a/ftd/t/html/22-test.html +++ b/ftd/t/html/22-test.html @@ -563,7 +563,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/23-alignment.html b/ftd/t/html/23-alignment.html index 07c3c27b34..7986433836 100644 --- a/ftd/t/html/23-alignment.html +++ b/ftd/t/html/23-alignment.html @@ -563,7 +563,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/23-doc-site.html b/ftd/t/html/23-doc-site.html index 0627caeee3..27615a90d3 100644 --- a/ftd/t/html/23-doc-site.html +++ b/ftd/t/html/23-doc-site.html @@ -684,7 +684,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/24-margin.html b/ftd/t/html/24-margin.html index d548f602cf..c6bc998a0c 100644 --- a/ftd/t/html/24-margin.html +++ b/ftd/t/html/24-margin.html @@ -562,7 +562,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/25-expander.html b/ftd/t/html/25-expander.html index 285dcf5eb4..c76244abd3 100644 --- a/ftd/t/html/25-expander.html +++ b/ftd/t/html/25-expander.html @@ -571,7 +571,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/25-overflow.html b/ftd/t/html/25-overflow.html index 33a0859b91..5b66c683c6 100644 --- a/ftd/t/html/25-overflow.html +++ b/ftd/t/html/25-overflow.html @@ -563,7 +563,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/26-border.html b/ftd/t/html/26-border.html index ce1459e3a2..917650cc7c 100644 --- a/ftd/t/html/26-border.html +++ b/ftd/t/html/26-border.html @@ -562,7 +562,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/27-optional.html b/ftd/t/html/27-optional.html index f0478b9a19..03e9995687 100644 --- a/ftd/t/html/27-optional.html +++ b/ftd/t/html/27-optional.html @@ -562,7 +562,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/28-complex.html b/ftd/t/html/28-complex.html index a48996cf4b..d1e24a28b1 100644 --- a/ftd/t/html/28-complex.html +++ b/ftd/t/html/28-complex.html @@ -602,7 +602,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/29-slides.html b/ftd/t/html/29-slides.html index 3522721d7d..eabed25275 100644 --- a/ftd/t/html/29-slides.html +++ b/ftd/t/html/29-slides.html @@ -571,7 +571,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/3-component.html b/ftd/t/html/3-component.html index 725cac2777..afb8ea5b8a 100644 --- a/ftd/t/html/3-component.html +++ b/ftd/t/html/3-component.html @@ -563,7 +563,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/30-slides.html b/ftd/t/html/30-slides.html index 051743fab2..03c05c37e2 100644 --- a/ftd/t/html/30-slides.html +++ b/ftd/t/html/30-slides.html @@ -589,7 +589,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/31-message.html b/ftd/t/html/31-message.html index 8ad05520c1..a76c000a65 100644 --- a/ftd/t/html/31-message.html +++ b/ftd/t/html/31-message.html @@ -592,7 +592,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/32-test.html b/ftd/t/html/32-test.html index 4086e7d9fa..0feef2cf39 100644 --- a/ftd/t/html/32-test.html +++ b/ftd/t/html/32-test.html @@ -563,7 +563,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/33-component-using-css.html b/ftd/t/html/33-component-using-css.html index 3ddc214cd8..4e3e8086b1 100644 --- a/ftd/t/html/33-component-using-css.html +++ b/ftd/t/html/33-component-using-css.html @@ -564,7 +564,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/33-using-css.html b/ftd/t/html/33-using-css.html index 83c5424198..1d097613a8 100644 --- a/ftd/t/html/33-using-css.html +++ b/ftd/t/html/33-using-css.html @@ -563,7 +563,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", diff --git a/ftd/t/html/34-device.html b/ftd/t/html/34-device.html index 255dd4bfdf..a261a8c90f 100644 --- a/ftd/t/html/34-device.html +++ b/ftd/t/html/34-device.html @@ -570,7 +570,7 @@ } } }, -"ftd#device": "desktop", +"ftd#device": "mobile", "ftd#empty": "", "ftd#follow-system-dark-mode": true, "ftd#font-code": "sans-serif", @@ -792,7 +792,7 @@ -
Hello World
+
Hello World
+ + + + + + +
+ + From 63e418de360227a225d54a07b2110ff8cb3dc4e3 Mon Sep 17 00:00:00 2001 From: heulitig Date: Thu, 26 Oct 2023 16:18:38 +0530 Subject: [PATCH 32/44] bump version -> 0.3.77 --- Cargo.lock | 2 +- fastn/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c92c347459..2fe6098475 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1623,7 +1623,7 @@ dependencies = [ [[package]] name = "fastn" -version = "0.3.76" +version = "0.3.77" dependencies = [ "clap", "colored", diff --git a/fastn/Cargo.toml b/fastn/Cargo.toml index e7dd1d417f..dd473fe81b 100644 --- a/fastn/Cargo.toml +++ b/fastn/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fastn" -version = "0.3.76" +version = "0.3.77" authors.workspace = true edition.workspace = true license.workspace = true From 8101d18f3d696fc6623b6ad06ee9d23708cc366e Mon Sep 17 00:00:00 2001 From: heulitig Date: Thu, 26 Oct 2023 17:15:08 +0530 Subject: [PATCH 33/44] bump version -> 0.3.78 --- Cargo.lock | 2 +- fastn/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2fe6098475..eae03eb2a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1623,7 +1623,7 @@ dependencies = [ [[package]] name = "fastn" -version = "0.3.77" +version = "0.3.78" dependencies = [ "clap", "colored", diff --git a/fastn/Cargo.toml b/fastn/Cargo.toml index dd473fe81b..b44d74b54a 100644 --- a/fastn/Cargo.toml +++ b/fastn/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fastn" -version = "0.3.77" +version = "0.3.78" authors.workspace = true edition.workspace = true license.workspace = true From 41fb09ca9d1e40fa0adec9e772fad05cd53f0bb3 Mon Sep 17 00:00:00 2001 From: Amit Upadhyay Date: Thu, 26 Oct 2023 14:26:49 +0530 Subject: [PATCH 34/44] basic test --- fastn-core/src/tutor.rs | 93 ++++++++++++++----- .../one/a-website/01-hello-world/README.md | 3 + .../tutor-tests/one/a-website/README.md | 3 + .../one/b-ui/01-hello-world/README.md | 3 + fastn-core/tutor-tests/one/b-ui/README.md | 3 + 5 files changed, 80 insertions(+), 25 deletions(-) create mode 100644 fastn-core/tutor-tests/one/a-website/01-hello-world/README.md create mode 100644 fastn-core/tutor-tests/one/a-website/README.md create mode 100644 fastn-core/tutor-tests/one/b-ui/01-hello-world/README.md create mode 100644 fastn-core/tutor-tests/one/b-ui/README.md diff --git a/fastn-core/src/tutor.rs b/fastn-core/src/tutor.rs index 9e66779b6a..a328d127c2 100644 --- a/fastn-core/src/tutor.rs +++ b/fastn-core/src/tutor.rs @@ -26,7 +26,7 @@ pub async fn process( )); } - let state: TutorState = + let state = match tokio::fs::read(dirs::home_dir().unwrap().join(".fastn").join("tutor.json")).await { Ok(v) => serde_json::from_slice(&v)?, Err(e) => match e.kind() { @@ -34,7 +34,7 @@ pub async fn process( _ => return Err(e.into()), }, } - .try_into()?; + .to_state(std::env::current_dir()?)?; doc.from_json(&state, &kind, &value) } @@ -45,21 +45,21 @@ struct TutorStateFS { current: String, } -#[derive(Debug, serde::Serialize)] +#[derive(Debug, serde::Serialize, PartialEq)] struct TutorState { workshops: Vec, } -impl TryFrom for TutorState { - type Error = ftd::interpreter::Error; - - fn try_from(state: TutorStateFS) -> Result { - // loop over all folders in current folder +impl TutorStateFS { + fn to_state>( + self: TutorStateFS, + path: T, + ) -> ftd::interpreter::Result { let mut workshops = vec![]; static RE: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| regex::Regex::new(r"^[a-zA-Z]-[a-zA-Z]+.*$").unwrap()); - for entry in std::fs::read_dir(std::env::current_dir()?)? { + for entry in std::fs::read_dir(path)? { let entry = entry?; let path = entry.path(); if !path.is_dir() { @@ -70,17 +70,17 @@ impl TryFrom for TutorState { continue; } - workshops.push(Workshop::load(&path, &state)?); + workshops.push(Workshop::load(&path, &self)?); } Ok(TutorState { workshops }) } } -#[derive(Debug, serde::Serialize)] +#[derive(Debug, serde::Serialize, PartialEq)] struct Workshop { title: String, - about: String, + url: String, done: bool, current: bool, tutorials: Vec, @@ -88,7 +88,6 @@ struct Workshop { impl Workshop { fn load(path: &std::path::Path, state: &TutorStateFS) -> ftd::interpreter::Result { - let (title, about) = title_and_about_from_readme(path)?; let mut tutorials = vec![]; let id = path.file_name().unwrap().to_string_lossy(); @@ -103,8 +102,8 @@ impl Workshop { } Ok(Workshop { - title: title.to_string(), - about: about.to_string(), + title: title_from_readme(path)?, + url: format!("/{id}/"), done: !tutorials.iter().any(|t| !t.done), current: tutorials.iter().any(|t| t.current), tutorials, @@ -112,11 +111,9 @@ impl Workshop { } } -fn title_and_about_from_readme( - folder: &std::path::Path, -) -> ftd::interpreter::Result<(String, String)> { +fn title_from_readme(folder: &std::path::Path) -> ftd::interpreter::Result { let content = std::fs::read_to_string(folder.join("README.md"))?; - let (title, about) = match content.split_once("\n\n") { + let (title, _about) = match content.split_once("\n\n") { Some(v) => v, None => { return Err(ftd::interpreter::Error::OtherError( @@ -124,14 +121,14 @@ fn title_and_about_from_readme( )) } }; - Ok((title.to_string(), about.to_string())) + Ok(title.replacen("# ", "", 1)) } -#[derive(Debug, serde::Serialize)] +#[derive(Debug, serde::Serialize, PartialEq)] struct Tutorial { id: String, + url: String, title: String, - about: String, done: bool, current: bool, } @@ -142,14 +139,13 @@ impl Tutorial { path: &std::path::Path, state: &TutorStateFS, ) -> ftd::interpreter::Result { - let (title, about) = title_and_about_from_readme(path)?; let id = format!("{parent}/{}", path.file_name().unwrap().to_string_lossy()); Ok(Tutorial { - title: title.to_string(), - about: about.to_string(), + title: title_from_readme(path)?, done: state.done.contains(&id), current: state.current == id, + url: format!("/{id}/"), id, }) } @@ -160,3 +156,50 @@ pub fn is_tutor() -> bool { // with either of these are passed we allow APIs like /-/shutdown/, `/-/start/` etc std::env::args().any(|e| e == "tutor" || e == "--tutor") } + +#[cfg(test)] +mod test { + use pretty_assertions::assert_eq; + + #[test] + fn test() { + assert_eq!( + super::TutorStateFS { + done: vec![], + current: "".to_string(), + } + .to_state("tutor-tests/one") + .unwrap(), + super::TutorState { + workshops: vec![ + super::Workshop { + title: "Build Websites Using `fastn`".to_string(), + url: "/a-website/".to_string(), + done: false, + current: false, + tutorials: vec![super::Tutorial { + id: "a-website/01-hello-world".to_string(), + url: "/a-website/01-hello-world/".to_string(), + title: "Install and start using `fastn`".to_string(), + done: false, + current: false, + }], + }, + super::Workshop { + title: "Build User Interfaces Using `fastn`".to_string(), + url: "/b-ui/".to_string(), + done: false, + current: false, + tutorials: vec![super::Tutorial { + id: "b-ui/01-hello-world".to_string(), + url: "/b-ui/01-hello-world/".to_string(), + title: "Install and start using `fastn`".to_string(), + done: false, + current: false, + }], + } + ] + } + ) + } +} diff --git a/fastn-core/tutor-tests/one/a-website/01-hello-world/README.md b/fastn-core/tutor-tests/one/a-website/01-hello-world/README.md new file mode 100644 index 0000000000..11d9765222 --- /dev/null +++ b/fastn-core/tutor-tests/one/a-website/01-hello-world/README.md @@ -0,0 +1,3 @@ +# Install and start using `fastn` + +In this exercise we will install fastn and create a basic hello world program. \ No newline at end of file diff --git a/fastn-core/tutor-tests/one/a-website/README.md b/fastn-core/tutor-tests/one/a-website/README.md new file mode 100644 index 0000000000..cfd5187ce7 --- /dev/null +++ b/fastn-core/tutor-tests/one/a-website/README.md @@ -0,0 +1,3 @@ +# Build Websites Using `fastn` + +This workshop teaches you how to build websites using `fastn`. \ No newline at end of file diff --git a/fastn-core/tutor-tests/one/b-ui/01-hello-world/README.md b/fastn-core/tutor-tests/one/b-ui/01-hello-world/README.md new file mode 100644 index 0000000000..11d9765222 --- /dev/null +++ b/fastn-core/tutor-tests/one/b-ui/01-hello-world/README.md @@ -0,0 +1,3 @@ +# Install and start using `fastn` + +In this exercise we will install fastn and create a basic hello world program. \ No newline at end of file diff --git a/fastn-core/tutor-tests/one/b-ui/README.md b/fastn-core/tutor-tests/one/b-ui/README.md new file mode 100644 index 0000000000..594b45e2af --- /dev/null +++ b/fastn-core/tutor-tests/one/b-ui/README.md @@ -0,0 +1,3 @@ +# Build User Interfaces Using `fastn` + +This workshop teaches you how to build user interfaces using `fastn`. \ No newline at end of file From 40dedb4c7780425dcc3616b26213693e8b266c48 Mon Sep 17 00:00:00 2001 From: Amit Upadhyay Date: Thu, 26 Oct 2023 14:44:09 +0530 Subject: [PATCH 35/44] more tests --- fastn-core/src/tutor.rs | 80 ++++++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 33 deletions(-) diff --git a/fastn-core/src/tutor.rs b/fastn-core/src/tutor.rs index a328d127c2..cf91731abd 100644 --- a/fastn-core/src/tutor.rs +++ b/fastn-core/src/tutor.rs @@ -163,43 +163,57 @@ mod test { #[test] fn test() { - assert_eq!( - super::TutorStateFS { - done: vec![], - current: "".to_string(), - } - .to_state("tutor-tests/one") - .unwrap(), - super::TutorState { - workshops: vec![ - super::Workshop { - title: "Build Websites Using `fastn`".to_string(), - url: "/a-website/".to_string(), + let mut ts = super::TutorState { + workshops: vec![ + super::Workshop { + title: "Build Websites Using `fastn`".to_string(), + url: "/a-website/".to_string(), + done: false, + current: false, + tutorials: vec![super::Tutorial { + id: "a-website/01-hello-world".to_string(), + url: "/a-website/01-hello-world/".to_string(), + title: "Install and start using `fastn`".to_string(), done: false, current: false, - tutorials: vec![super::Tutorial { - id: "a-website/01-hello-world".to_string(), - url: "/a-website/01-hello-world/".to_string(), - title: "Install and start using `fastn`".to_string(), - done: false, - current: false, - }], - }, - super::Workshop { - title: "Build User Interfaces Using `fastn`".to_string(), - url: "/b-ui/".to_string(), + }], + }, + super::Workshop { + title: "Build User Interfaces Using `fastn`".to_string(), + url: "/b-ui/".to_string(), + done: false, + current: false, + tutorials: vec![super::Tutorial { + id: "b-ui/01-hello-world".to_string(), + url: "/b-ui/01-hello-world/".to_string(), + title: "Install and start using `fastn`".to_string(), done: false, current: false, - tutorials: vec![super::Tutorial { - id: "b-ui/01-hello-world".to_string(), - url: "/b-ui/01-hello-world/".to_string(), - title: "Install and start using `fastn`".to_string(), - done: false, - current: false, - }], - } - ] + }], + }, + ], + }; + + assert_eq!( + super::TutorStateFS::default() + .to_state("tutor-tests/one") + .unwrap(), + ts, + ); + + ts.workshops[0].tutorials[0].done = true; + ts.workshops[0].done = true; + ts.workshops[1].current = true; + ts.workshops[1].tutorials[0].current = true; + + assert_eq!( + super::TutorStateFS { + done: vec!["a-website/01-hello-world".to_string()], + current: "b-ui/01-hello-world".to_string(), } - ) + .to_state("tutor-tests/one") + .unwrap(), + ts, + ); } } From 25c0ad7d0531b6520a857e7cd8986aba34de4fc1 Mon Sep 17 00:00:00 2001 From: Amit Upadhyay Date: Thu, 26 Oct 2023 14:50:58 +0530 Subject: [PATCH 36/44] sorted dirs --- fastn-core/src/tutor.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/fastn-core/src/tutor.rs b/fastn-core/src/tutor.rs index cf91731abd..cfbaf324fe 100644 --- a/fastn-core/src/tutor.rs +++ b/fastn-core/src/tutor.rs @@ -1,3 +1,5 @@ +use itertools::Itertools; + pub async fn pwd() -> fastn_core::Result { if !is_tutor() { return Ok(fastn_core::not_found!("this only works in tutor mode")); @@ -59,7 +61,7 @@ impl TutorStateFS { static RE: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| regex::Regex::new(r"^[a-zA-Z]-[a-zA-Z]+.*$").unwrap()); - for entry in std::fs::read_dir(path)? { + for entry in std::fs::read_dir(path)?.sorted_by(sort_path) { let entry = entry?; let path = entry.path(); if !path.is_dir() { @@ -77,6 +79,12 @@ impl TutorStateFS { } } +fn sort_path( + a: &std::result::Result, + b: &std::result::Result, +) -> std::cmp::Ordering { + a.as_ref().unwrap().path().cmp(&b.as_ref().unwrap().path()) +} #[derive(Debug, serde::Serialize, PartialEq)] struct Workshop { title: String, @@ -91,7 +99,7 @@ impl Workshop { let mut tutorials = vec![]; let id = path.file_name().unwrap().to_string_lossy(); - for entry in std::fs::read_dir(path)? { + for entry in std::fs::read_dir(path)?.sorted_by(sort_path) { let entry = entry?; let path = entry.path(); if !path.is_dir() { From 35ed74d092852311dd0eaf5cf815a6f5aaba2902 Mon Sep 17 00:00:00 2001 From: Amit Upadhyay Date: Thu, 26 Oct 2023 14:56:56 +0530 Subject: [PATCH 37/44] more RE --- fastn-core/src/tutor.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/fastn-core/src/tutor.rs b/fastn-core/src/tutor.rs index cfbaf324fe..65c3229a01 100644 --- a/fastn-core/src/tutor.rs +++ b/fastn-core/src/tutor.rs @@ -1,5 +1,3 @@ -use itertools::Itertools; - pub async fn pwd() -> fastn_core::Result { if !is_tutor() { return Ok(fastn_core::not_found!("this only works in tutor mode")); @@ -57,6 +55,8 @@ impl TutorStateFS { self: TutorStateFS, path: T, ) -> ftd::interpreter::Result { + use itertools::Itertools; + let mut workshops = vec![]; static RE: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| regex::Regex::new(r"^[a-zA-Z]-[a-zA-Z]+.*$").unwrap()); @@ -64,10 +64,10 @@ impl TutorStateFS { for entry in std::fs::read_dir(path)?.sorted_by(sort_path) { let entry = entry?; let path = entry.path(); + if !path.is_dir() { continue; } - if !RE.is_match(&path.file_name().unwrap().to_string_lossy()) { continue; } @@ -80,11 +80,12 @@ impl TutorStateFS { } fn sort_path( - a: &std::result::Result, - b: &std::result::Result, + a: &std::io::Result, + b: &std::io::Result, ) -> std::cmp::Ordering { a.as_ref().unwrap().path().cmp(&b.as_ref().unwrap().path()) } + #[derive(Debug, serde::Serialize, PartialEq)] struct Workshop { title: String, @@ -96,15 +97,24 @@ struct Workshop { impl Workshop { fn load(path: &std::path::Path, state: &TutorStateFS) -> ftd::interpreter::Result { + use itertools::Itertools; + let mut tutorials = vec![]; let id = path.file_name().unwrap().to_string_lossy(); + static RE: once_cell::sync::Lazy = + once_cell::sync::Lazy::new(|| regex::Regex::new(r"^[0-9][0-9]-[a-zA-Z]+.*$").unwrap()); + for entry in std::fs::read_dir(path)?.sorted_by(sort_path) { let entry = entry?; let path = entry.path(); + if !path.is_dir() { continue; } + if !RE.is_match(&path.file_name().unwrap().to_string_lossy()) { + continue; + } tutorials.push(Tutorial::load(&id, &path, state)?); } From 2980af80edfcf3ae9a0e2f5ca096ff0d4dee67c2 Mon Sep 17 00:00:00 2001 From: Harsh Singh <64768386+harshdoesdev@users.noreply.github.com> Date: Fri, 27 Oct 2023 13:05:52 +0530 Subject: [PATCH 38/44] new test and minor fixes (#1426) --- fastn-js/js/dom.js | 18 +- ftd/t/js/68-mask.ftd | 53 ++-- ftd/t/js/68-mask.html | 644 ++++++++++++++++++++++++++++++++++++++---- 3 files changed, 623 insertions(+), 92 deletions(-) diff --git a/fastn-js/js/dom.js b/fastn-js/js/dom.js index c2130a83ee..91f6d15c81 100644 --- a/fastn-js/js/dom.js +++ b/fastn-js/js/dom.js @@ -1269,7 +1269,7 @@ class Node2 { const maskDarkImageString = maskDarkImageValues.join(", "); if(maskLightImageString === maskDarkImageString) { - this.attachCss(propertyWithPrefix, maskLightImageString, false); + this.attachCss(propertyWithPrefix, maskLightImageString, true); } else { let lightClass = this.attachCss(propertyWithPrefix, maskLightImageString, true); this.attachCss(propertyWithPrefix, maskDarkImageString, true, `body.dark .${lightClass}`); @@ -1306,19 +1306,19 @@ class Node2 { this.attachMaskSizeCss(value, vendorPrefix); const maskRepeatValue = fastn_utils.getStaticValue(value.get("repeat")); if(fastn_utils.isNull(maskRepeatValue)) { - this.attachCss("mask-repeat", maskRepeatValue); - this.attachCss("-webkit-mask-repeat", maskRepeatValue); + this.attachCss("mask-repeat", maskRepeatValue, true); + this.attachCss("-webkit-mask-repeat", maskRepeatValue, true); } else { - this.attachCss("mask-repeat", maskRepeatValue); - this.attachCss("-webkit-mask-repeat", maskRepeatValue); + this.attachCss("mask-repeat", maskRepeatValue, true); + this.attachCss("-webkit-mask-repeat", maskRepeatValue, true); } const maskPositionValue = fastn_utils.getStaticValue(value.get("position")); if(fastn_utils.isNull(maskPositionValue)) { - this.attachCss("mask-position", maskPositionValue); - this.attachCss("-webkit-mask-position", maskPositionValue); + this.attachCss("mask-position", maskPositionValue, true); + this.attachCss("-webkit-mask-position", maskPositionValue, true); } else { - this.attachCss("mask-position", maskPositionValue); - this.attachCss("-webkit-mask-position", maskPositionValue); + this.attachCss("mask-position", maskPositionValue, true); + this.attachCss("-webkit-mask-position", maskPositionValue, true); } } attachExternalCss(css) { diff --git a/ftd/t/js/68-mask.ftd b/ftd/t/js/68-mask.ftd index 52614fe4dd..d2af189f9a 100644 --- a/ftd/t/js/68-mask.ftd +++ b/ftd/t/js/68-mask.ftd @@ -1,39 +1,28 @@ --- ftd.container: -background.solid: red -mask.image: https://mdn.github.io/css-examples/masking/star.svg -width.fixed.px: 300 -height.fixed.px: 300 - --- ftd.color red-orange: -light: red -dark: orange - --- ftd.color yellow-blue: -light: yellow -dark: blue - --- ftd.linear-gradient lg: -direction: bottom-left -colors: $color-values +-- ftd.mask-multi mi: +image: https://s3-us-west-2.amazonaws.com/s.cdpn.io/18515/heart.svg +repeat: no-repeat +position: center +size: cover --- ftd.linear-gradient-color list color-values: +-- component icon: +caption ftd.color color: --- ftd.linear-gradient-color: $red-orange -stop-position.percent: 40 +-- ftd.container: +background.solid: $icon.color +mask.multi: $mi +width.fixed.px: 48 +height.fixed.px: 48 --- ftd.linear-gradient-color: $yellow-blue +-- end: icon --- end: color-values +-- ftd.color list colors: red, orange, yellow, green, blue, indigo, violet, cyan, magenta, lime, olive, maroon, purple, white, #e5e5e5, #ccc, #b2b2b2, #999, #7f7f7f, #666, #4c4c4c, #333, #191919, black +-- ftd.row: +width: fill-container +align-content: center +wrap: true --- ftd.mask-multi mi: -image: https://mdn.github.io/css-examples/masking/star.svg -size.fixed.rem: 2 -repeat: no-repeat -position: center +-- icon: $color +for: $color in $colors --- ftd.container: -background.solid: red -mask.multi: $mi -width.fixed.px: 300 -height.fixed.px: 300 +-- end: ftd.row diff --git a/ftd/t/js/68-mask.html b/ftd/t/js/68-mask.html index 24b766bd62..32fb620c9c 100644 --- a/ftd/t/js/68-mask.html +++ b/ftd/t/js/68-mask.html @@ -16,33 +16,469 @@ -
+ + + + + + +
This person named Sam Wan has first visited Bangalore on 27th October
Person Sam Wan lives at Sam City in Some House. His contact number is +1234-56789
First Person is Sam Ather lives at Sam Ather City at Some Other House. His contact number is +987-654321
+ + From 919e35990d44c9fc857076d2ead136ec2c7a852a Mon Sep 17 00:00:00 2001 From: Arpita-Jaiswal Date: Fri, 27 Oct 2023 16:30:06 +0530 Subject: [PATCH 40/44] Fix: SEO data added again --- fastn-js/js/dom.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/fastn-js/js/dom.js b/fastn-js/js/dom.js index 91f6d15c81..d462e2365c 100644 --- a/fastn-js/js/dom.js +++ b/fastn-js/js/dom.js @@ -806,7 +806,7 @@ class Node2 { return this.#parent; } removeAllFaviconLinks() { - if (hydrating) { + if (hydrating || rerender) { const links = document.head.querySelectorAll('link[rel="shortcut icon"]'); links.forEach( link => { link.parentNode.removeChild(link); @@ -815,7 +815,7 @@ class Node2 { } setFavicon(url) { - if (hydrating) { + if (hydrating || rerender) { if (url instanceof fastn.recordInstanceClass) url = url.get('src'); while (true) { if (url instanceof fastn.mutableClass) url = url.get(); @@ -902,7 +902,7 @@ class Node2 { } } updateMetaTitle(value) { - if (!ssr && hydrating) { + if (!ssr && (hydrating || rerender)) { if (!fastn_utils.isNull(value)) window.document.title = value; } } @@ -911,7 +911,7 @@ class Node2 { this.removeMetaTagByName(name); return; } - if (!ssr && hydrating) { + if (!ssr && (hydrating || rerender)) { const metaTag = window.document.createElement('meta'); metaTag.setAttribute('name', name); metaTag.setAttribute('content', value); @@ -923,7 +923,7 @@ class Node2 { this.removeMetaTagByProperty(property); return; } - if (!ssr && hydrating) { + if (!ssr && (hydrating || rerender)) { const metaTag = window.document.createElement('meta'); metaTag.setAttribute('property', property); metaTag.setAttribute('content', value); @@ -931,7 +931,7 @@ class Node2 { } } removeMetaTagByName(name) { - if (!ssr && hydrating) { + if (!ssr && (hydrating || rerender)) { const metaTags = document.getElementsByTagName('meta'); for (let i = 0; i < metaTags.length; i++) { const metaTag = metaTags[i]; @@ -943,7 +943,7 @@ class Node2 { } } removeMetaTagByProperty(property) { - if (!ssr && hydrating) { + if (!ssr && (hydrating || rerender)) { const metaTags = document.getElementsByTagName('meta'); for (let i = 0; i < metaTags.length; i++) { const metaTag = metaTags[i]; @@ -1322,7 +1322,7 @@ class Node2 { } } attachExternalCss(css) { - if (hydrating) { + if (hydrating || rerender) { let css_tag = document.createElement('link'); css_tag.rel = 'stylesheet'; css_tag.type = 'text/css'; @@ -1336,7 +1336,7 @@ class Node2 { } } attachExternalJs(js) { - if (hydrating) { + if (hydrating || rerender) { let js_tag = document.createElement('script'); js_tag.src = js; From efa77795bd7578ee6e9cbda7a5bccff1fd0a7c6e Mon Sep 17 00:00:00 2001 From: Arpita-Jaiswal Date: Fri, 27 Oct 2023 16:34:43 +0530 Subject: [PATCH 41/44] Bump fastn version to 0.3.79 --- Cargo.lock | 2 +- fastn-js/js/dom.js | 2 +- fastn/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eae03eb2a8..17bb151280 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1623,7 +1623,7 @@ dependencies = [ [[package]] name = "fastn" -version = "0.3.78" +version = "0.3.79" dependencies = [ "clap", "colored", diff --git a/fastn-js/js/dom.js b/fastn-js/js/dom.js index d462e2365c..d1f78713f9 100644 --- a/fastn-js/js/dom.js +++ b/fastn-js/js/dom.js @@ -958,7 +958,7 @@ class Node2 { attachCss(property, value, createClass, className) { let propertyShort = fastn_dom.propertyMap[property] || property; propertyShort = `__${propertyShort}`; - let cls = `${propertyShort}-${JSON.stringify(fastn_dom.class_count)}`; + let cls = `${propertyShort}-${fastn_dom.class_count}`; if (!!className) { cls = className; } else { diff --git a/fastn/Cargo.toml b/fastn/Cargo.toml index b44d74b54a..6f6564ef73 100644 --- a/fastn/Cargo.toml +++ b/fastn/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "fastn" -version = "0.3.78" +version = "0.3.79" authors.workspace = true edition.workspace = true license.workspace = true From 03fc09f642fa07c8d25ba632ef02d2617f33a492 Mon Sep 17 00:00:00 2001 From: siddhantCodes Date: Fri, 27 Oct 2023 02:19:07 +0530 Subject: [PATCH 42/44] .env support - if there's a .env file in CWD, parse it and set env vars from it - create-package should generate a .gitignore file that contains .env file - check if the .env file is checked in, panic and proceed only when FASTN_DANGER_ACCEPT_CHECKED_IN_ENV is set --- Cargo.lock | 7 ++++ fastn-core/src/commands/create_package.rs | 13 ++++--- fastn/Cargo.toml | 1 + fastn/src/main.rs | 41 +++++++++++++++++++++++ 4 files changed, 57 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17bb151280..47b2eb8566 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1457,6 +1457,12 @@ dependencies = [ "syn 2.0.38", ] +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + [[package]] name = "dtoa" version = "1.0.9" @@ -1627,6 +1633,7 @@ version = "0.3.79" dependencies = [ "clap", "colored", + "dotenvy", "fastn-cloud", "fastn-core", "fastn-observer", diff --git a/fastn-core/src/commands/create_package.rs b/fastn-core/src/commands/create_package.rs index 12339d28e8..cf87c65ce4 100644 --- a/fastn-core/src/commands/create_package.rs +++ b/fastn-core/src/commands/create_package.rs @@ -1,7 +1,7 @@ async fn template_contents( project_name: &str, download_base_url: Option<&str>, -) -> (String, String) { +) -> (String, String, String) { let ftd = format!( r#"-- import: fastn @@ -14,8 +14,12 @@ async fn template_contents( .unwrap_or_default() ); let index = "-- ftd.text: Hello world".to_string(); + let gitignore = r#".build/ +.env + "# + .to_string(); - (ftd, index) + (ftd, index, gitignore) } pub async fn create_package( @@ -53,12 +57,11 @@ pub async fn create_package( // Create all directories if not present tokio::fs::create_dir_all(final_dir.as_str()).await?; - let tmp_contents = template_contents(name, download_base_url).await; - let tmp_fastn = tmp_contents.0; - let tmp_index = tmp_contents.1; + let (tmp_fastn, tmp_index, tmp_gitignore) = template_contents(name, download_base_url).await; fastn_core::utils::update(&final_dir.join("FASTN.ftd"), tmp_fastn.as_bytes()).await?; fastn_core::utils::update(&final_dir.join("index.ftd"), tmp_index.as_bytes()).await?; + fastn_core::utils::update(&final_dir.join(".gitignore"), tmp_gitignore.as_bytes()).await?; // Note: Not required for now // let sync_message = "Initial sync".to_string(); diff --git a/fastn/Cargo.toml b/fastn/Cargo.toml index 6f6564ef73..227487f9f4 100644 --- a/fastn/Cargo.toml +++ b/fastn/Cargo.toml @@ -20,3 +20,4 @@ thiserror.workspace = true tokio.workspace = true tracing.workspace = true tracing-subscriber.workspace = true +dotenvy = "0.15.7" diff --git a/fastn/src/main.rs b/fastn/src/main.rs index db4d787b91..e56697d623 100644 --- a/fastn/src/main.rs +++ b/fastn/src/main.rs @@ -1,4 +1,5 @@ mod commands; + pub fn main() { fastn_observer::observe(); @@ -27,6 +28,8 @@ pub enum Error { async fn async_main() -> Result<(), Error> { let matches = app(version()).get_matches(); + set_env_vars(); + if cloud_commands(&matches).await? { return Ok(()); } @@ -543,3 +546,41 @@ pub fn version() -> &'static str { } } } + +fn set_env_vars() -> () { + let checked_in = { + if let Ok(status) = std::process::Command::new("git") + .arg("ls-files") + .arg("--error-unmatch") + .arg(".env") + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status() + { + status.success() // .env is checked in + } else { + false + } + }; + + let ignore = { + if let Ok(val) = std::env::var("FASTN_DANGER_ACCEPT_CHECKED_IN_ENV") { + val != "false" + } else { + false + } + }; + + if checked_in && !ignore { + eprintln!( + "ERROR: the .env file is checked in to version control! This is a security risk. +Remove it from your version control system or run fastn again with +FASTN_DANGER_ACCEPT_CHECKED_IN_ENV set" + ); + std::process::exit(1); + } + + if let Ok(_) = dotenvy::dotenv() { + println!("INFO: loaded environment variables from .env file."); + } +} From 4e6e9a7f2c8a53359dbaf063bd33b364b116c8f8 Mon Sep 17 00:00:00 2001 From: siddhantCodes Date: Fri, 27 Oct 2023 02:26:56 +0530 Subject: [PATCH 43/44] fix clippy warnings --- fastn/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fastn/src/main.rs b/fastn/src/main.rs index e56697d623..2c45d69367 100644 --- a/fastn/src/main.rs +++ b/fastn/src/main.rs @@ -547,7 +547,7 @@ pub fn version() -> &'static str { } } -fn set_env_vars() -> () { +fn set_env_vars() { let checked_in = { if let Ok(status) = std::process::Command::new("git") .arg("ls-files") @@ -580,7 +580,7 @@ FASTN_DANGER_ACCEPT_CHECKED_IN_ENV set" std::process::exit(1); } - if let Ok(_) = dotenvy::dotenv() { + if dotenvy::dotenv().is_ok() { println!("INFO: loaded environment variables from .env file."); } } From fcf243cd5d1f2155db00d9d09b13a1035cf53032 Mon Sep 17 00:00:00 2001 From: siddhantCodes Date: Fri, 27 Oct 2023 19:08:46 +0530 Subject: [PATCH 44/44] issue a warning when FASTN_DANGER_ACCEPT_CHECKED_IN_ENV is used --- fastn/src/main.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/fastn/src/main.rs b/fastn/src/main.rs index 2c45d69367..ffc73481d8 100644 --- a/fastn/src/main.rs +++ b/fastn/src/main.rs @@ -578,9 +578,16 @@ Remove it from your version control system or run fastn again with FASTN_DANGER_ACCEPT_CHECKED_IN_ENV set" ); std::process::exit(1); - } + } else { + if checked_in && ignore { + println!( + "WARN: your .env file has been detected in the version control system! This poses a +significant security risk in case the source code becomes public." + ); + } - if dotenvy::dotenv().is_ok() { - println!("INFO: loaded environment variables from .env file."); + if dotenvy::dotenv().is_ok() { + println!("INFO: loaded environment variables from .env file."); + } } }