doesn't not work
This commit is contained in:
parent
ff673840e3
commit
a31b41cb9e
13 changed files with 1563 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
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
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;
|
||||
}
|
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 %}
|
Loading…
Reference in a new issue