more groundwork for the frontend

This commit is contained in:
Cadey Ratio 2020-11-10 17:11:05 -05:00
parent d0dadfea39
commit afc1d2c764
23 changed files with 390 additions and 18 deletions

98
backend/Cargo.lock generated
View File

@ -103,6 +103,65 @@ dependencies = [
"winapi 0.3.9", "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]] [[package]]
name = "atty" name = "atty"
version = "0.2.14" version = "0.2.14"
@ -742,6 +801,12 @@ version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
[[package]]
name = "humansize"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6cab2627acfc432780848602f3f558f7e9dd427352224b0d9324025796d2a5e"
[[package]] [[package]]
name = "hyper" name = "hyper"
version = "0.10.16" version = "0.10.16"
@ -974,6 +1039,8 @@ checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
name = "mi" name = "mi"
version = "1.0.0" version = "1.0.0"
dependencies = [ dependencies = [
"askama",
"askama_rocket",
"chrono", "chrono",
"color-eyre", "color-eyre",
"diesel", "diesel",
@ -1016,6 +1083,16 @@ version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 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]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.4.1" version = "0.4.1"
@ -1079,6 +1156,16 @@ dependencies = [
"winapi 0.3.9", "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]] [[package]]
name = "notify" name = "notify"
version = "4.0.15" version = "4.0.15"
@ -1539,7 +1626,7 @@ dependencies = [
"rocket_http", "rocket_http",
"state", "state",
"time 0.1.44", "time 0.1.44",
"toml", "toml 0.4.10",
"version_check 0.9.2", "version_check 0.9.2",
"yansi", "yansi",
] ]
@ -2053,6 +2140,15 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "toml"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75cf45bb0bef80604d001caaec0d09da99611b3c0fd39d3080468875cdb65645"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "tracing" name = "tracing"
version = "0.1.19" version = "0.1.19"

View File

@ -7,6 +7,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
askama_rocket = "0.10"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
color-eyre = "0.5" color-eyre = "0.5"
futures-io = "0.3" futures-io = "0.3"
@ -31,6 +32,10 @@ twapi-ureq = "0.1.5"
ureq = { version = "1", features = ["json", "charset"] } ureq = { version = "1", features = ["json", "charset"] }
url = "2" url = "2"
[dependencies.askama]
version = "0.10"
features = [ "with-rocket" ]
[dependencies.diesel] [dependencies.diesel]
version = "1" version = "1"
features = ["sqlite", "r2d2", "chrono"] features = ["sqlite", "r2d2", "chrono"]

View File

@ -3,6 +3,7 @@
address = "0.0.0.0" address = "0.0.0.0"
switchcounter_webhook = "..." switchcounter_webhook = "..."
discord_webhook = "" discord_webhook = ""
asset_path = "../static"
# I don't know how to generate these without using Go, sorry. These are hex-encoded # I don't know how to generate these without using Go, sorry. These are hex-encoded
# ED25519 public/private keys. # ED25519 public/private keys.

View File

@ -6,6 +6,7 @@ let
rustc = rust; rustc = rust;
cargo = rust; cargo = rust;
}; };
gruvbox = pkgs.callPackage sources.gruvbox-css { };
src = builtins.filterSource src = builtins.filterSource
(path: type: type != "directory" || builtins.baseNameOf path != "target") (path: type: type != "directory" || builtins.baseNameOf path != "target")
./.; ./.;
@ -13,6 +14,7 @@ in naersk.buildPackage {
name = "mi_backend"; name = "mi_backend";
inherit src; inherit src;
buildInputs = with pkgs; [ openssl pkg-config sqlite libsodium ]; buildInputs = with pkgs; [ openssl pkg-config sqlite libsodium ];
GRUVBOX_CSS = "${gruvbox}/gruvbox.css";
SODIUM_USE_PKG_CONFIG = "1"; SODIUM_USE_PKG_CONFIG = "1";
SODIUM_SHARED = "1"; SODIUM_SHARED = "1";
} }

45
backend/src/frontend.rs Normal file
View File

@ -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<String> {
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]))
})
}

View File

@ -19,6 +19,7 @@ pub const APPLICATION_NAME: &str = concat!(
); );
pub mod api; pub mod api;
pub mod frontend;
pub mod models; pub mod models;
pub mod paseto; pub mod paseto;
pub mod rocket_trace; pub mod rocket_trace;

View File

@ -11,7 +11,7 @@ use rocket_contrib::helmet::SpaceHelmet;
use rocket_cors::{AllowedHeaders, AllowedOrigins}; use rocket_cors::{AllowedHeaders, AllowedOrigins};
use rocket_prometheus::PrometheusMetrics; 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")] #[get("/.within/botinfo")]
fn botinfo() -> &'static str { fn botinfo() -> &'static str {
@ -58,8 +58,10 @@ fn main() -> Result<()> {
rocket::ignite() rocket::ignite()
.attach(prometheus.clone()) .attach(prometheus.clone())
.attach(cors) .attach(cors)
.attach(MainDatabase::fairing())
.attach(SpaceHelmet::default()) .attach(SpaceHelmet::default())
.attach(static_files())
.attach(frontend::fairing())
.attach(MainDatabase::fairing())
.attach(RequestId {}) .attach(RequestId {})
.attach(paseto::ed25519_keypair()) .attach(paseto::ed25519_keypair())
.attach(DiscordWebhook::fairing()) .attach(DiscordWebhook::fairing())

View File

@ -1,8 +1,10 @@
use rocket::fairing::{Fairing, Info, Kind}; use rocket::fairing::{AdHoc, Fairing, Info, Kind};
use rocket::http::Header; use rocket::http::Header;
use rocket::{Data, Request, Response}; use rocket::{Data, Request, Response};
use rocket_contrib::serve::StaticFiles;
use rusty_ulid::generate_ulid_string; use rusty_ulid::generate_ulid_string;
#[derive(Default)]
pub struct RequestId; pub struct RequestId;
impl Fairing for 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)))
})
}

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
<link rel="stylesheet" href="/static/gruvbox.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/png" href="/static/favicon.png"/>
<link rel="icon" href="/static/favicon.ico" type="image/x-icon"/>
</head>
<body id="top">
<main>
<div id="app">{{ message }}</div>
<script src="/static/elm.js"></script>
<script>
var app = Elm.Signup.init({
node: document.getElementById("app")
});
</script>
</main>
</body>
</html>

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
<link rel="stylesheet" href="/static/gruvbox.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/png" href="/static/favicon.png"/>
<link rel="icon" href="/static/favicon.ico" type="image/x-icon"/>
</head>
<body id="top">
<main>
<nav class="nav">
<a href="/">Mi</a>
</nav>
<h1>{{ title }}</h1>
<p>{{ message }}</p>
<a href="/">Go home</a>
</main>
</body>
</html>

5
frontend/.gitignore vendored
View File

@ -1,3 +1,2 @@
index.html elm-stuff
index.js elm.js
elm-stuff

View File

@ -15,6 +15,11 @@
version = "1.0.2"; version = "1.0.2";
}; };
"rtfeldman/elm-css" = {
sha256 = "0nxiyxyw3kw55whkpwhrcgc0dr6a8zlm2nqvsaqdw6mzkykg0ba6";
version = "16.1.0";
};
"elm/core" = { "elm/core" = {
sha256 = "19w0iisdd66ywjayyga4kv2p1v9rxzqjaxhckp8ni6n8i0fb2dvf"; sha256 = "19w0iisdd66ywjayyga4kv2p1v9rxzqjaxhckp8ni6n8i0fb2dvf";
version = "1.0.5"; version = "1.0.5";
@ -50,6 +55,11 @@
version = "1.0.5"; version = "1.0.5";
}; };
"rtfeldman/elm-hex" = {
sha256 = "1y0aa16asvwdqmgbskh5iba6psp43lkcjjw9mgzj3gsrg33lp00d";
version = "1.0.0";
};
"elm/parser" = { "elm/parser" = {
sha256 = "0a3cxrvbm7mwg9ykynhp7vjid58zsw03r63qxipxp3z09qks7512"; sha256 = "0a3cxrvbm7mwg9ykynhp7vjid58zsw03r63qxipxp3z09qks7512";
version = "1.1.0"; version = "1.1.0";

View File

@ -13,13 +13,15 @@
"elm/json": "1.1.3", "elm/json": "1.1.3",
"elm/time": "1.0.0", "elm/time": "1.0.0",
"elm/url": "1.0.0", "elm/url": "1.0.0",
"rtfeldman/elm-css": "16.1.0",
"rtfeldman/elm-iso8601-date-strings": "1.1.3" "rtfeldman/elm-iso8601-date-strings": "1.1.3"
}, },
"indirect": { "indirect": {
"elm/bytes": "1.0.8", "elm/bytes": "1.0.8",
"elm/file": "1.0.5", "elm/file": "1.0.5",
"elm/parser": "1.1.0", "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": { "test-dependencies": {

4
frontend/scripts/build-dev.sh Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env nix-shell
#! nix-shell -i bash -p elmPackages.elm
elm make ./src/Main.elm --output elm.js

View File

@ -6,6 +6,7 @@ import Html.Attributes exposing (autofocus, class, value)
import Html.Events exposing (onClick, onInput) import Html.Events exposing (onClick, onInput)
import Mi import Mi
import Mi.Switch import Mi.Switch
import Mi.WebMention
-- We added a new AddTodo message type. -- We added a new AddTodo message type.
@ -48,7 +49,6 @@ update msg model =
UpdateText newText -> UpdateText newText ->
{ model | text = newText } { model | text = newText }
-- We append the model.text value to the end of our list of todo strings.
AddTodo -> AddTodo ->
{ model | text = "", todos = model.todos ++ [ model.text ] } { model | text = "", todos = model.todos ++ [ model.text ] }
@ -76,4 +76,4 @@ main =
{ init = { text = "", todos = [] } { init = { text = "", todos = [] }
, view = view , view = view
, update = update , update = update
} }

View File

@ -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 Iso8601
import Json.Decode exposing (Decoder, field, int, map5, nullable, string) import Json.Decode exposing (Decoder, field, int, map5, nullable, string)
import Time exposing (..) import Time exposing (Posix)
import Url.Builder as UB import Url.Builder as UB
type alias Switch = type alias Switch =
{ id : String { id : String
, who : String , who : String
@ -36,21 +35,21 @@ switchURL =
idURL : String -> String idURL : String -> String
idURL id = idURL id =
UB.absolute UB.absolute
[ "api","switches", "id", id ] [ "api", "switches", "id", id ]
[] []
frontURL : String frontURL : String
frontURL = frontURL =
UB.absolute UB.absolute
["api", "switches", "current" ] [ "api", "switches", "current" ]
[] []
listURL : Int -> Int -> String listURL : Int -> Int -> String
listURL limit page = listURL limit page =
UB.absolute UB.absolute
["api", "switches", "" ] [ "api", "switches", "" ]
[ UB.int "limit" limit [ UB.int "limit" limit
, UB.int "page" page , UB.int "page" page
] ]

View File

@ -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
]

115
frontend/src/Signup.elm Normal file
View File

@ -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
}

View File

@ -3,6 +3,7 @@ let
pkgs = pkgs =
import sources.nixpkgs { overlays = [ (import sources.nixpkgs-mozilla) ]; }; import sources.nixpkgs { overlays = [ (import sources.nixpkgs-mozilla) ]; };
rust = import ./nix/rust.nix { }; rust = import ./nix/rust.nix { };
gruvbox = pkgs.callPackage sources.gruvbox-css { };
in pkgs.mkShell rec { in pkgs.mkShell rec {
buildInputs = with pkgs; [ buildInputs = with pkgs; [
# rust # rust
@ -26,6 +27,8 @@ in pkgs.mkShell rec {
bashInteractive bashInteractive
]; ];
GRUVBOX_CSS = "${gruvbox}/gruvbox.css";
DATABASE_URL = "./mi.db"; DATABASE_URL = "./mi.db";
ROCKET_DATABASES = ''{ main_data = { url = "${DATABASE_URL}" } }''; ROCKET_DATABASES = ''{ main_data = { url = "${DATABASE_URL}" } }'';
RUST_LOG = "info"; RUST_LOG = "info";

1
static/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
elm.js

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
static/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

1
static/index.html Symbolic link
View File

@ -0,0 +1 @@
../frontend/app.html