Compare commits
2 commits
31fb2000f0
...
a31b41cb9e
Author | SHA1 | Date | |
---|---|---|---|
|
a31b41cb9e | ||
|
ff673840e3 |
22 changed files with 1563 additions and 141 deletions
|
@ -1,4 +0,0 @@
|
||||||
# Used by "mix format"
|
|
||||||
[
|
|
||||||
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
|
|
||||||
]
|
|
27
.gitignore
vendored
27
.gitignore
vendored
|
@ -1,26 +1 @@
|
||||||
# The directory Mix will write compiled artifacts to.
|
/target
|
||||||
/_build/
|
|
||||||
|
|
||||||
# If you run "mix test --cover", coverage assets end up here.
|
|
||||||
/cover/
|
|
||||||
|
|
||||||
# The directory Mix downloads your dependencies sources to.
|
|
||||||
/deps/
|
|
||||||
|
|
||||||
# Where third-party dependencies like ExDoc output generated docs.
|
|
||||||
/doc/
|
|
||||||
|
|
||||||
# Ignore .fetch files in case you like to edit your project deps locally.
|
|
||||||
/.fetch
|
|
||||||
|
|
||||||
# If the VM crashes, it generates a dump, let's ignore it too.
|
|
||||||
erl_crash.dump
|
|
||||||
|
|
||||||
# Also ignore archive artifacts (built via "mix archive.build").
|
|
||||||
*.ez
|
|
||||||
|
|
||||||
# Ignore package tarball (built via "mix hex.build").
|
|
||||||
queen-*.tar
|
|
||||||
|
|
||||||
# Temporary files, for example, from tests.
|
|
||||||
/tmp/
|
|
||||||
|
|
4
.rustfmt.toml
Normal file
4
.rustfmt.toml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
imports_granularity = "Crate"
|
||||||
|
group_imports = "StdExternalCrate"
|
||||||
|
wrap_comments = true
|
||||||
|
edition = "2021"
|
1223
Cargo.lock
generated
Normal file
1223
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "queenie"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
askama = { version = "0.12", default-features = false, features = ["with-axum", "serde"] }
|
||||||
|
askama_axum = { version = "0.4.0", default-features = false }
|
||||||
|
axum = { version = "0.7", default-features = false, features = ["tokio", "http1", "form"] }
|
||||||
|
justerror = { version = "1" }
|
||||||
|
serde = { version = "1", default-features = false, features = ["derive"] }
|
||||||
|
thiserror = { version = "1" }
|
||||||
|
tokio = { version = "1", default-features = false, features = ["rt-multi-thread"] }
|
||||||
|
tower-sessions = { version = "0.10", default-features = false, features = ["axum-core", "memory-store"] }
|
||||||
|
unicode-segmentation = { version = "1", default-features = false }
|
|
@ -1 +0,0 @@
|
||||||
meow
|
|
1
assets/htmx.min.js
vendored
Normal file
1
assets/htmx.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
40
assets/ww.css
Normal file
40
assets/ww.css
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
body {
|
||||||
|
background-color: darkgrey
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: ghostwhite;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
text-align: left;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(even) {background-color: ghostwhite;}
|
||||||
|
|
||||||
|
#header {
|
||||||
|
text-align: end;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.warning {
|
||||||
|
background-color: goldenrod;
|
||||||
|
}
|
||||||
|
|
||||||
|
.watchtitle {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header_logged_in {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
|
@ -1,3 +0,0 @@
|
||||||
import Config
|
|
||||||
|
|
||||||
config(:queen, :secret_key_base, System.fetch_env!("QUEEN_COOKIE_SECRET"))
|
|
27
lib/queen.ex
27
lib/queen.ex
|
@ -1,27 +0,0 @@
|
||||||
defmodule QueenRouter do
|
|
||||||
use Plug.Router
|
|
||||||
|
|
||||||
plug(Plug.Logger)
|
|
||||||
plug(:match)
|
|
||||||
plug(:dispatch)
|
|
||||||
|
|
||||||
plug(Plug.Session, store: :cookie, key: "_queen_session", signing_salt: "J6PHP10BHF23")
|
|
||||||
plug(:fetch_session)
|
|
||||||
plug(Plug.CSRFProtection)
|
|
||||||
|
|
||||||
get "/signup" do
|
|
||||||
fetch_session(conn) |> put_session(:verify, "meow") |> send_resp(200, "signup")
|
|
||||||
end
|
|
||||||
|
|
||||||
get "/success/:payment" do
|
|
||||||
conn = fetch_session(conn)
|
|
||||||
verify = get_session(conn, :verify)
|
|
||||||
:logger.info("got verify: #{verify}")
|
|
||||||
:logger.info("got payment receipt code #{payment}")
|
|
||||||
send_resp(conn, 200, "huzzah")
|
|
||||||
end
|
|
||||||
|
|
||||||
match _ do
|
|
||||||
send_resp(conn, 404, "you lost, kitty-cat?")
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,21 +0,0 @@
|
||||||
defmodule Queen.Application do
|
|
||||||
# See https://hexdocs.pm/elixir/Application.html
|
|
||||||
# for more information on OTP Applications
|
|
||||||
@moduledoc false
|
|
||||||
|
|
||||||
use Application
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def start(_type, _args) do
|
|
||||||
children = [
|
|
||||||
{Bandit, plug: QueenRouter}
|
|
||||||
# Starts a worker by calling: Queen.Worker.start_link(arg)
|
|
||||||
# {Queen.Worker, arg}
|
|
||||||
]
|
|
||||||
|
|
||||||
# See https://hexdocs.pm/elixir/Supervisor.html
|
|
||||||
# for other strategies and supported options
|
|
||||||
opts = [strategy: :one_for_one, name: Queen.Supervisor]
|
|
||||||
Supervisor.start_link(children, opts)
|
|
||||||
end
|
|
||||||
end
|
|
32
mix.exs
32
mix.exs
|
@ -1,32 +0,0 @@
|
||||||
defmodule Queen.MixProject do
|
|
||||||
use Mix.Project
|
|
||||||
|
|
||||||
def project do
|
|
||||||
[
|
|
||||||
app: :queen,
|
|
||||||
version: "0.1.0",
|
|
||||||
elixir: "~> 1.16.1",
|
|
||||||
start_permanent: Mix.env() == :prod,
|
|
||||||
deps: deps()
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Run "mix help compile.app" to learn about applications.
|
|
||||||
def application do
|
|
||||||
[
|
|
||||||
# extra_applications: [:logger, :ecto_sqlite3, :ecto, :bandit, :plug]
|
|
||||||
extra_applications: [:logger],
|
|
||||||
mod: {Queen.Application, []}
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Run "mix help deps" to learn about dependencies.
|
|
||||||
defp deps do
|
|
||||||
[
|
|
||||||
{:bandit, "~> 1.2"},
|
|
||||||
{:plug, "~> 1.5"},
|
|
||||||
{:ecto_sqlite3, "~> 0.15"},
|
|
||||||
{:ecto, "~> 3.11"}
|
|
||||||
]
|
|
||||||
end
|
|
||||||
end
|
|
18
mix.lock
18
mix.lock
|
@ -1,18 +0,0 @@
|
||||||
%{
|
|
||||||
"bandit": {:hex, :bandit, "1.2.1", "aa485b4ac175065b8e0fb5864ddd5dd7b50d52336b36f61c82f484c3718b3d15", [:mix], [{:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "27393e590a407f1b7d51c5fee4737f139fe224a30449ce25061eac70f763896b"},
|
|
||||||
"cc_precompiler": {:hex, :cc_precompiler, "0.1.9", "e8d3364f310da6ce6463c3dd20cf90ae7bbecbf6c5203b98bf9b48035592649b", [:mix], [{:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "9dcab3d0f3038621f1601f13539e7a9ee99843862e66ad62827b0c42b2f58a54"},
|
|
||||||
"db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"},
|
|
||||||
"decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"},
|
|
||||||
"ecto": {:hex, :ecto, "3.11.1", "4b4972b717e7ca83d30121b12998f5fcdc62ba0ed4f20fd390f16f3270d85c3e", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ebd3d3772cd0dfcd8d772659e41ed527c28b2a8bde4b00fe03e0463da0f1983b"},
|
|
||||||
"ecto_sql": {:hex, :ecto_sql, "3.11.1", "e9abf28ae27ef3916b43545f9578b4750956ccea444853606472089e7d169470", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ce14063ab3514424276e7e360108ad6c2308f6d88164a076aac8a387e1fea634"},
|
|
||||||
"ecto_sqlite3": {:hex, :ecto_sqlite3, "0.15.1", "40f2fbd9e246455f8c42e7e0a77009ef806caa1b3ce6f717b2a0a80e8432fcfd", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.11", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.11", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:exqlite, "~> 0.19", [hex: :exqlite, repo: "hexpm", optional: false]}], "hexpm", "28b16e177123c688948357176662bf9ff9084daddf950ef5b6baf3ee93707064"},
|
|
||||||
"elixir_make": {:hex, :elixir_make, "0.7.8", "505026f266552ee5aabca0b9f9c229cbb496c689537c9f922f3eb5431157efc7", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "7a71945b913d37ea89b06966e1342c85cfe549b15e6d6d081e8081c493062c07"},
|
|
||||||
"exqlite": {:hex, :exqlite, "0.19.0", "0f3ee29e35bed38552dd0ed59600aa81c78f867f5b5ff0e17d330148e0465483", [:make, :mix], [{:cc_precompiler, "~> 0.1", [hex: :cc_precompiler, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.7", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "55a8fbb0443f03d4a256e3458bd1203eff5037a6624b76460eaaa9080f462b06"},
|
|
||||||
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
|
|
||||||
"mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"},
|
|
||||||
"plug": {:hex, :plug, "1.15.3", "712976f504418f6dff0a3e554c40d705a9bcf89a7ccef92fc6a5ef8f16a30a97", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "cc4365a3c010a56af402e0809208873d113e9c38c401cabd88027ef4f5c01fd2"},
|
|
||||||
"plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"},
|
|
||||||
"telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"},
|
|
||||||
"thousand_island": {:hex, :thousand_island, "1.3.2", "bc27f9afba6e1a676dd36507d42e429935a142cf5ee69b8e3f90bff1383943cd", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0e085b93012cd1057b378fce40cbfbf381ff6d957a382bfdd5eca1a98eec2535"},
|
|
||||||
"websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"},
|
|
||||||
}
|
|
156
src/main.rs
Normal file
156
src/main.rs
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
use std::{
|
||||||
|
fmt::{Debug, Display},
|
||||||
|
net::SocketAddr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use askama::Template;
|
||||||
|
use axum::{
|
||||||
|
extract::{Form, Path},
|
||||||
|
http::StatusCode,
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
routing::get,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tower_sessions::{MemoryStore, Session, SessionManagerLayer};
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate justerror;
|
||||||
|
|
||||||
|
const SIGNUP_KEY: &str = "meow";
|
||||||
|
|
||||||
|
#[derive(Default, Deserialize, Serialize)]
|
||||||
|
struct Counter(usize);
|
||||||
|
|
||||||
|
/// Displays the signup form.
|
||||||
|
async fn get_signup() -> impl IntoResponse {
|
||||||
|
SignupPage::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receives the form with the user signup fields filled out.
|
||||||
|
async fn post_signup(session: Session, Form(form): Form<SignupForm>) -> impl IntoResponse {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Called from Stripe with the receipt of payment.
|
||||||
|
async fn signup_success(session: Session, receipt: Option<Path<String>>) -> impl IntoResponse {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let session_store = MemoryStore::default();
|
||||||
|
let session_layer = SessionManagerLayer::new(session_store).with_secure(false);
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
//.nest_service("/assets", assets_svc)
|
||||||
|
.route("/signup", get(get_signup).post(post_signup))
|
||||||
|
.route("/signup_success/:receipt", get(signup_success))
|
||||||
|
.layer(session_layer);
|
||||||
|
|
||||||
|
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||||
|
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
||||||
|
axum::serve(listener, app.into_make_service())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Error(desc = "Could not create user.")]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct CreateUserError(#[from] CreateUserErrorKind);
|
||||||
|
|
||||||
|
impl IntoResponse for CreateUserError {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
(StatusCode::FORBIDDEN, format!("{:?}", self.0)).into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Error]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum CreateUserErrorKind {
|
||||||
|
AlreadyExists,
|
||||||
|
#[error(desc = "Usernames must be between 1 and 20 characters long")]
|
||||||
|
BadUsername,
|
||||||
|
PasswordMismatch,
|
||||||
|
#[error(desc = "Password must have at least 4 and at most 50 characters")]
|
||||||
|
BadPassword,
|
||||||
|
#[error(desc = "Display name must be less than 100 characters long")]
|
||||||
|
BadDisplayname,
|
||||||
|
BadEmail,
|
||||||
|
BadPayment,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Deserialize, PartialEq, Eq)]
|
||||||
|
pub struct SignupForm {
|
||||||
|
pub username: String,
|
||||||
|
#[serde(default, deserialize_with = "empty_string_as_none")]
|
||||||
|
pub displayname: Option<String>,
|
||||||
|
#[serde(default, deserialize_with = "empty_string_as_none")]
|
||||||
|
pub email: Option<String>,
|
||||||
|
pub password: String,
|
||||||
|
pub pw_verify: String,
|
||||||
|
pub invitation: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct User {
|
||||||
|
pub username: String,
|
||||||
|
pub displayname: Option<String>,
|
||||||
|
pub email: Option<String>,
|
||||||
|
pub password: String,
|
||||||
|
pub pw_verify: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for User {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let pw_check = if self.password == self.pw_verify {
|
||||||
|
"password matched"
|
||||||
|
} else {
|
||||||
|
"PASSWORD MISMATCH"
|
||||||
|
};
|
||||||
|
f.debug_struct("User")
|
||||||
|
.field("username", &self.username)
|
||||||
|
.field("displayname", &self.displayname)
|
||||||
|
.field("email", &self.email)
|
||||||
|
.field("pw-check", &pw_check)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for User {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let uname = &self.username;
|
||||||
|
let dname = if let Some(ref n) = self.displayname {
|
||||||
|
n
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
};
|
||||||
|
let email = if let Some(ref e) = self.email { e } else { "" };
|
||||||
|
write!(f, "Username: {uname}\nDisplayname: {dname}\nEmail: {email}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn empty_string_as_none<'de, D, T>(de: D) -> Result<Option<T>, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
T: std::str::FromStr,
|
||||||
|
T::Err: std::fmt::Display,
|
||||||
|
{
|
||||||
|
let opt = <Option<String> as serde::Deserialize>::deserialize(de)?;
|
||||||
|
match opt.as_deref() {
|
||||||
|
None | Some("") => Ok(None),
|
||||||
|
Some(s) => std::str::FromStr::from_str(s)
|
||||||
|
.map_err(serde::de::Error::custom)
|
||||||
|
.map(Some),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
|
#[template(path = "signup.html")]
|
||||||
|
pub struct SignupPage {
|
||||||
|
pub username: String,
|
||||||
|
pub displayname: Option<String>,
|
||||||
|
pub email: Option<String>,
|
||||||
|
pub password: String,
|
||||||
|
pub pw_verify: String,
|
||||||
|
}
|
29
templates/base.html
Normal file
29
templates/base.html
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>{% block title %}{{ title }} - What 2 Watch{% endblock %}</title>
|
||||||
|
<link rel="stylesheet" href="/assets/ww.css">
|
||||||
|
|
||||||
|
{% block head %}{% endblock %}
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="header">
|
||||||
|
{% block header %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
<div id="content">
|
||||||
|
<hr />
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
<div id="footer">
|
||||||
|
{% block footer %}{% endblock %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="/assets/htmx.min.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
26
templates/index.html
Normal file
26
templates/index.html
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Welcome to What 2 Watch, Bish{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<h1>Welcome to What 2 Watch</h1>
|
||||||
|
|
||||||
|
{% match user %}
|
||||||
|
{% when Some with (usr) %}
|
||||||
|
<p>
|
||||||
|
Hello, {{ usr.username }}! It's nice to see you. <a href="watches">Let's get watchin'!</a>
|
||||||
|
</p>
|
||||||
|
</br>
|
||||||
|
<p>
|
||||||
|
<form action="/logout" enctype="application/x-www-form-urlencoded" method="post">
|
||||||
|
<input type="submit" value="sign out?">
|
||||||
|
</form>
|
||||||
|
</p>
|
||||||
|
{% when None %}
|
||||||
|
<p>
|
||||||
|
Heya, why don't you <a href="/login">log in</a> or <a href="/signup">sign up</a>?
|
||||||
|
</p>
|
||||||
|
{% endmatch %}
|
||||||
|
|
||||||
|
{% endblock %}
|
10
templates/macros.html
Normal file
10
templates/macros.html
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{% macro get_or_default(val, def) %}
|
||||||
|
|
||||||
|
{% match val %}
|
||||||
|
{% when Some with (v) %}
|
||||||
|
{{v}}
|
||||||
|
{% else %}
|
||||||
|
{{def}}
|
||||||
|
{% endmatch %}
|
||||||
|
|
||||||
|
{% endmacro %}
|
25
templates/signup.html
Normal file
25
templates/signup.html
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Welcome, friend, to git.kittenclause.com{% endblock %}
|
||||||
|
|
||||||
|
{% block header %} {% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<form action="/signup" enctype="application/x-www-form-urlencoded" method="post">
|
||||||
|
<label for="username">Username</label>
|
||||||
|
<input type="text" name="username" id="username" minlength="1" maxlength="20" required></br>
|
||||||
|
<label for="displayname">Displayname (optional)</label>
|
||||||
|
<input type="text" name="displayname" id="displayname"></br>
|
||||||
|
<label for="email">Email (optional)</label>
|
||||||
|
<input type="text" name="email"></br>
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input type="password" name="password" id="password" required></br>
|
||||||
|
<label for="confirm_password">Confirm Password</label>
|
||||||
|
<input type="password" name="pw_verify" id="pw_verify" required></br>
|
||||||
|
<input type="submit" value="Signup">
|
||||||
|
</form>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
16
templates/signup_error.html
Normal file
16
templates/signup_error.html
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Dang, Bish{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% block header %}{% endblock %}
|
||||||
|
|
||||||
|
<h1>Oh dang!</h1>
|
||||||
|
|
||||||
|
<div id="signup_success">
|
||||||
|
<p>
|
||||||
|
Sorry, something went wrong: {{self.0}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
17
templates/signup_success.html
Normal file
17
templates/signup_success.html
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Thanks for Signing Up for What 2 Watch, Bish{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{% block header %}{% endblock %}
|
||||||
|
|
||||||
|
<h1>You did it!</h1>
|
||||||
|
|
||||||
|
<div id="signup_success"><p>
|
||||||
|
{{ self.0 }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Now, head on over to <a href="/login">the login page</a> and get watchin'!</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -1,8 +0,0 @@
|
||||||
defmodule QueenTest do
|
|
||||||
use ExUnit.Case
|
|
||||||
doctest Queen
|
|
||||||
|
|
||||||
test "greets the world" do
|
|
||||||
assert Queen.hello() == :world
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1 +0,0 @@
|
||||||
ExUnit.start()
|
|
Loading…
Reference in a new issue