diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 0e20476..076259a 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -103,6 +103,65 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "askama" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70a6e7ebd44d0047fd48206c83c5cd3214acc7b9d87f001da170145c47ef7d12" +dependencies = [ + "askama_derive", + "askama_escape", + "askama_shared", + "mime 0.3.16", + "mime_guess", +] + +[[package]] +name = "askama_derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d7169690c4f56343dcd821ab834972a22570a2662a19a84fd7775d5e1c3881" +dependencies = [ + "askama_shared", + "proc-macro2 1.0.21", + "quote 1.0.7", + "syn 1.0.40", +] + +[[package]] +name = "askama_escape" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90c108c1a94380c89d2215d0ac54ce09796823cca0fd91b299cfff3b33e346fb" + +[[package]] +name = "askama_rocket" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40428c13bd027adfd481391729b85bf9961b59e5b21bb55e80f603d19d054583" +dependencies = [ + "askama", + "rocket", +] + +[[package]] +name = "askama_shared" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62fc272363345c8cdc030e4c259d9d028237f8b057dc9bb327772a257bde6bb5" +dependencies = [ + "askama_escape", + "humansize", + "nom", + "num-traits", + "percent-encoding 2.1.0", + "proc-macro2 1.0.21", + "quote 1.0.7", + "serde", + "syn 1.0.40", + "toml 0.5.7", +] + [[package]] name = "atty" version = "0.2.14" @@ -742,6 +801,12 @@ version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" +[[package]] +name = "humansize" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e" + [[package]] name = "hyper" version = "0.10.16" @@ -974,6 +1039,8 @@ checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" name = "mi" version = "1.0.0" dependencies = [ + "askama", + "askama_rocket", "chrono", "color-eyre", "diesel", @@ -1016,6 +1083,16 @@ version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" +[[package]] +name = "mime_guess" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" +dependencies = [ + "mime 0.3.16", + "unicase 2.6.0", +] + [[package]] name = "miniz_oxide" version = "0.4.1" @@ -1079,6 +1156,16 @@ dependencies = [ "winapi 0.3.9", ] +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "memchr", + "version_check 0.9.2", +] + [[package]] name = "notify" version = "4.0.15" @@ -1539,7 +1626,7 @@ dependencies = [ "rocket_http", "state", "time 0.1.44", - "toml", + "toml 0.4.10", "version_check 0.9.2", "yansi", ] @@ -2053,6 +2140,15 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645" +dependencies = [ + "serde", +] + [[package]] name = "tracing" version = "0.1.19" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 73df98c..3a5810e 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -7,6 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +askama_rocket = "0.10" chrono = { version = "0.4", features = ["serde"] } color-eyre = "0.5" futures-io = "0.3" @@ -31,6 +32,10 @@ twapi-ureq = "0.1.5" ureq = { version = "1", features = ["json", "charset"] } url = "2" +[dependencies.askama] +version = "0.10" +features = [ "with-rocket" ] + [dependencies.diesel] version = "1" features = ["sqlite", "r2d2", "chrono"] diff --git a/backend/Rocket.example.toml b/backend/Rocket.example.toml index d5f04c9..0769b5c 100644 --- a/backend/Rocket.example.toml +++ b/backend/Rocket.example.toml @@ -3,6 +3,7 @@ address = "0.0.0.0" switchcounter_webhook = "..." discord_webhook = "" +asset_path = "../static" # I don't know how to generate these without using Go, sorry. These are hex-encoded # ED25519 public/private keys. diff --git a/backend/default.nix b/backend/default.nix index 8c47a24..927fba1 100644 --- a/backend/default.nix +++ b/backend/default.nix @@ -6,6 +6,7 @@ let rustc = rust; cargo = rust; }; + gruvbox = pkgs.callPackage sources.gruvbox-css { }; src = builtins.filterSource (path: type: type != "directory" || builtins.baseNameOf path != "target") ./.; @@ -13,6 +14,7 @@ in naersk.buildPackage { name = "mi_backend"; inherit src; buildInputs = with pkgs; [ openssl pkg-config sqlite libsodium ]; + GRUVBOX_CSS = "${gruvbox}/gruvbox.css"; SODIUM_USE_PKG_CONFIG = "1"; SODIUM_SHARED = "1"; } diff --git a/backend/src/frontend.rs b/backend/src/frontend.rs new file mode 100644 index 0000000..6d605e5 --- /dev/null +++ b/backend/src/frontend.rs @@ -0,0 +1,45 @@ +use askama::Template; +use rocket::{fairing::AdHoc, response::content::Css, Request}; + +#[derive(Template)] +#[template(path = "app.html")] +struct App { + title: String, + message: String, +} + +#[get("/")] +fn frontend() -> App { + App { + title: "Mi".to_string(), + message: "Loading...".to_string(), + } +} + +#[derive(Template)] +#[template(path = "notfound.html")] +struct NotFound { + title: String, + message: String, +} + +#[catch(404)] +fn not_found(req: &Request) -> NotFound { + NotFound { + title: "Not found".to_string(), + message: format!("{} not found", req.uri()), + } +} + +#[get("/static/gruvbox.css")] +fn gruvbox() -> Css { + Css(include_str!(env!("GRUVBOX_CSS")).to_string()) +} + +pub fn fairing() -> AdHoc { + AdHoc::on_attach("frontend integration", |rocket| { + Ok(rocket + .register(catchers![not_found]) + .mount("/", routes![frontend, gruvbox])) + }) +} diff --git a/backend/src/lib.rs b/backend/src/lib.rs index e11501b..27d4bd7 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -19,6 +19,7 @@ pub const APPLICATION_NAME: &str = concat!( ); pub mod api; +pub mod frontend; pub mod models; pub mod paseto; pub mod rocket_trace; diff --git a/backend/src/main.rs b/backend/src/main.rs index b094231..29f3082 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -11,7 +11,7 @@ use rocket_contrib::helmet::SpaceHelmet; use rocket_cors::{AllowedHeaders, AllowedOrigins}; use rocket_prometheus::PrometheusMetrics; -use ::mi::{api, paseto, rocket_trace::*, web::*, MainDatabase, APPLICATION_NAME}; +use ::mi::{api, frontend, paseto, rocket_trace::*, web::*, MainDatabase, APPLICATION_NAME}; #[get("/.within/botinfo")] fn botinfo() -> &'static str { @@ -58,8 +58,10 @@ fn main() -> Result<()> { rocket::ignite() .attach(prometheus.clone()) .attach(cors) - .attach(MainDatabase::fairing()) .attach(SpaceHelmet::default()) + .attach(static_files()) + .attach(frontend::fairing()) + .attach(MainDatabase::fairing()) .attach(RequestId {}) .attach(paseto::ed25519_keypair()) .attach(DiscordWebhook::fairing()) diff --git a/backend/src/rocket_trace.rs b/backend/src/rocket_trace.rs index ec5c39b..770036f 100644 --- a/backend/src/rocket_trace.rs +++ b/backend/src/rocket_trace.rs @@ -1,8 +1,10 @@ -use rocket::fairing::{Fairing, Info, Kind}; +use rocket::fairing::{AdHoc, Fairing, Info, Kind}; use rocket::http::Header; use rocket::{Data, Request, Response}; +use rocket_contrib::serve::StaticFiles; use rusty_ulid::generate_ulid_string; +#[derive(Default)] pub struct RequestId; impl Fairing for RequestId { @@ -32,3 +34,11 @@ impl Fairing for RequestId { }; } } + +pub fn static_files() -> AdHoc { + AdHoc::on_attach("Static fileserver", |rocket| { + let asset_path = rocket.config().get_string("asset_path").unwrap(); + + Ok(rocket.mount("/static", StaticFiles::from(asset_path))) + }) +} diff --git a/backend/templates/app.html b/backend/templates/app.html new file mode 100644 index 0000000..0169643 --- /dev/null +++ b/backend/templates/app.html @@ -0,0 +1,21 @@ + + + + {{ title }} + + + + + + +
+
{{ message }}
+ + +
+ + diff --git a/backend/templates/notfound.html b/backend/templates/notfound.html new file mode 100644 index 0000000..ef52661 --- /dev/null +++ b/backend/templates/notfound.html @@ -0,0 +1,20 @@ + + + + {{ title }} + + + + + + +
+ +

{{ title }}

+

{{ message }}

+ Go home +
+ + diff --git a/frontend/.gitignore b/frontend/.gitignore index 8cc1f65..d86ae43 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -1,3 +1,2 @@ -index.html -index.js -elm-stuff \ No newline at end of file +elm-stuff +elm.js \ No newline at end of file diff --git a/frontend/elm-srcs.nix b/frontend/elm-srcs.nix index 12b3dbd..6f79e7c 100644 --- a/frontend/elm-srcs.nix +++ b/frontend/elm-srcs.nix @@ -15,6 +15,11 @@ version = "1.0.2"; }; + "rtfeldman/elm-css" = { + sha256 = "0nxiyxyw3kw55whkpwhrcgc0dr6a8zlm2nqvsaqdw6mzkykg0ba6"; + version = "16.1.0"; + }; + "elm/core" = { sha256 = "19w0iisdd66ywjayyga4kv2p1v9rxzqjaxhckp8ni6n8i0fb2dvf"; version = "1.0.5"; @@ -50,6 +55,11 @@ version = "1.0.5"; }; + "rtfeldman/elm-hex" = { + sha256 = "1y0aa16asvwdqmgbskh5iba6psp43lkcjjw9mgzj3gsrg33lp00d"; + version = "1.0.0"; + }; + "elm/parser" = { sha256 = "0a3cxrvbm7mwg9ykynhp7vjid58zsw03r63qxipxp3z09qks7512"; version = "1.1.0"; diff --git a/frontend/elm.json b/frontend/elm.json index 564d82c..18d3742 100644 --- a/frontend/elm.json +++ b/frontend/elm.json @@ -13,13 +13,15 @@ "elm/json": "1.1.3", "elm/time": "1.0.0", "elm/url": "1.0.0", + "rtfeldman/elm-css": "16.1.0", "rtfeldman/elm-iso8601-date-strings": "1.1.3" }, "indirect": { "elm/bytes": "1.0.8", "elm/file": "1.0.5", "elm/parser": "1.1.0", - "elm/virtual-dom": "1.0.2" + "elm/virtual-dom": "1.0.2", + "rtfeldman/elm-hex": "1.0.0" } }, "test-dependencies": { diff --git a/frontend/scripts/build-dev.sh b/frontend/scripts/build-dev.sh new file mode 100755 index 0000000..03647fc --- /dev/null +++ b/frontend/scripts/build-dev.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env nix-shell +#! nix-shell -i bash -p elmPackages.elm + +elm make ./src/Main.elm --output elm.js diff --git a/frontend/src/Main.elm b/frontend/src/Main.elm index 486a757..c2cc158 100644 --- a/frontend/src/Main.elm +++ b/frontend/src/Main.elm @@ -6,6 +6,7 @@ import Html.Attributes exposing (autofocus, class, value) import Html.Events exposing (onClick, onInput) import Mi import Mi.Switch +import Mi.WebMention -- We added a new AddTodo message type. @@ -48,7 +49,6 @@ update msg model = UpdateText newText -> { model | text = newText } - -- We append the model.text value to the end of our list of todo strings. AddTodo -> { model | text = "", todos = model.todos ++ [ model.text ] } @@ -76,4 +76,4 @@ main = { init = { text = "", todos = [] } , view = view , update = update - } \ No newline at end of file + } diff --git a/frontend/src/Mi/Switch.elm b/frontend/src/Mi/Switch.elm index 59cfd8d..e6390a4 100644 --- a/frontend/src/Mi/Switch.elm +++ b/frontend/src/Mi/Switch.elm @@ -1,12 +1,11 @@ -module Mi.Switch exposing (..) +module Mi.Switch exposing (Switch, decoder, frontURL, idURL, listURL, switchURL) -import Html exposing (..) -import Html.Attributes exposing (..) import Iso8601 import Json.Decode exposing (Decoder, field, int, map5, nullable, string) -import Time exposing (..) +import Time exposing (Posix) import Url.Builder as UB + type alias Switch = { id : String , who : String @@ -36,21 +35,21 @@ switchURL = idURL : String -> String idURL id = UB.absolute - [ "api","switches", "id", id ] + [ "api", "switches", "id", id ] [] frontURL : String frontURL = UB.absolute - ["api", "switches", "current" ] + [ "api", "switches", "current" ] [] listURL : Int -> Int -> String listURL limit page = UB.absolute - ["api", "switches", "" ] + [ "api", "switches", "" ] [ UB.int "limit" limit , UB.int "page" page - ] \ No newline at end of file + ] diff --git a/frontend/src/Mi/WebMention.elm b/frontend/src/Mi/WebMention.elm new file mode 100644 index 0000000..2efed02 --- /dev/null +++ b/frontend/src/Mi/WebMention.elm @@ -0,0 +1,35 @@ +module Mi.WebMention exposing (decoder, idURL, listURL) + +import Json.Decode exposing (Decoder, field, map3, string) +import Url.Builder as UB + + +type alias WebMention = + { id : String + , source_url : String + , target_url : String + } + + +decoder : Decoder WebMention +decoder = + map3 WebMention + (field "id" string) + (field "source_url" string) + (field "target_url" string) + + +idURL : String -> String +idURL id = + UB.absolute + [ "api", "webmention", id ] + [] + + +listURL : Int -> Int -> String +listURL limit page = + UB.absolute + [ "api", "webmention" ] + [ UB.int "limit" limit + , UB.int "page" page + ] diff --git a/frontend/src/Signup.elm b/frontend/src/Signup.elm new file mode 100644 index 0000000..5582443 --- /dev/null +++ b/frontend/src/Signup.elm @@ -0,0 +1,115 @@ +module Signup exposing (main) + +import Browser +import Css exposing (..) +import Html exposing (Attribute, Html, button, div, h1, input, text) +import Html.Attributes exposing (id, style, type_) +import Html.Events exposing (onClick, onInput) + + +type alias User = + { name : String + , email : String + , password : String + , loggedIn : Bool + } + + +initialModel : User +initialModel = + { name = "" + , email = "" + , password = "" + , loggedIn = False + } + + +view : User -> Html Msg +view user = + div [ style "margin" "auto" ] + [ h1 [ style "padding-left" "3cm" ] [ text "Sign up" ] + , Html.form formStyle + [ div [] + [ text "Name" + , input ([ id "name", type_ "text", onInput SaveName ] ++ inputStyle) [] + ] + , div [] + [ text "Email" + , input ([ id "email", type_ "email", onInput SaveEmail ] ++ inputStyle) [] + ] + , div [] + [ text "Password" + , input ([ id "password", type_ "password", onInput SavePassword ] ++ inputStyle) [] + ] + , div [] + [ button + ([ type_ "submit", onClick Signup ] ++ buttonStyle) + [ text "Create my account" ] + ] + ] + ] + + +formStyle : List (Attribute msg) +formStyle = + [ style "border-radius" "5px" + , style "background-color" "#f2f2f2" + , style "padding" "20px" + , style "width" "300px" + ] + + +inputStyle : List (Attribute msg) +inputStyle = + [ style "display" "block" + , style "width" "260px" + , style "padding" "12px 20px" + , style "margin" "8px 0" + , style "border" "none" + , style "border-radius" "4px" + ] + + +buttonStyle : List (Attribute msg) +buttonStyle = + [ style "width" "300px" + , style "background-color" "#397cd5" + , style "color" "white" + , style "padding" "14px 20px" + , style "margin-top" "10px" + , style "border" "none" + , style "border-radius" "4px" + , style "font-size" "16px" + ] + + +type Msg + = SaveName String + | SaveEmail String + | SavePassword String + | Signup + + +update : Msg -> User -> User +update message user = + case message of + SaveName name -> + { user | name = name } + + SaveEmail email -> + { user | email = email } + + SavePassword password -> + { user | password = password } + + Signup -> + { user | loggedIn = True } + + +main : Program () User Msg +main = + Browser.sandbox + { init = initialModel + , view = view + , update = update + } diff --git a/shell.nix b/shell.nix index 18f1a26..9fc2961 100644 --- a/shell.nix +++ b/shell.nix @@ -3,6 +3,7 @@ let pkgs = import sources.nixpkgs { overlays = [ (import sources.nixpkgs-mozilla) ]; }; rust = import ./nix/rust.nix { }; + gruvbox = pkgs.callPackage sources.gruvbox-css { }; in pkgs.mkShell rec { buildInputs = with pkgs; [ # rust @@ -26,6 +27,8 @@ in pkgs.mkShell rec { bashInteractive ]; + GRUVBOX_CSS = "${gruvbox}/gruvbox.css"; + DATABASE_URL = "./mi.db"; ROCKET_DATABASES = ''{ main_data = { url = "${DATABASE_URL}" } }''; RUST_LOG = "info"; diff --git a/static/.gitignore b/static/.gitignore new file mode 100644 index 0000000..0ae95bc --- /dev/null +++ b/static/.gitignore @@ -0,0 +1 @@ +elm.js diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000..8e07b55 Binary files /dev/null and b/static/favicon.ico differ diff --git a/static/favicon.png b/static/favicon.png new file mode 100644 index 0000000..26e8303 Binary files /dev/null and b/static/favicon.png differ diff --git a/static/index.html b/static/index.html new file mode 120000 index 0000000..23bb9cb --- /dev/null +++ b/static/index.html @@ -0,0 +1 @@ +../frontend/app.html \ No newline at end of file