update deps, fuck with errors
This commit is contained in:
parent
72ca947cf6
commit
fc26533460
11 changed files with 788 additions and 559 deletions
780
Cargo.lock
generated
780
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
28
Cargo.toml
28
Cargo.toml
|
@ -1,7 +1,7 @@
|
||||||
[package]
|
[package]
|
||||||
name = "what2watch"
|
name = "what2watch"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
default-run = "what2watch"
|
default-run = "what2watch"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -10,16 +10,14 @@ optional_optional_user = {path = "optional_optional_user"}
|
||||||
|
|
||||||
# regular external deps
|
# regular external deps
|
||||||
argon2 = "0.5"
|
argon2 = "0.5"
|
||||||
askama = { version = "0.12", features = ["with-axum"] }
|
askama = { version = "0.14" }
|
||||||
askama_axum = "0.4"
|
axum = { version = "0.8", features = ["macros"] }
|
||||||
async-trait = "0.1"
|
axum-login = "0.18"
|
||||||
axum = { version = "0.7", features = ["macros"] }
|
axum-macros = "0.5"
|
||||||
axum-login = "0.14"
|
|
||||||
axum-macros = "0.4"
|
|
||||||
chrono = { version = "0.4", default-features = false, features = ["std", "clock", "serde"] }
|
chrono = { version = "0.4", default-features = false, features = ["std", "clock", "serde"] }
|
||||||
clap = { version = "4", features = ["derive", "env", "unicode", "suggestions", "usage"] }
|
clap = { version = "4", features = ["derive", "env", "unicode", "suggestions", "usage"] }
|
||||||
confy = "0.6"
|
confy = "1"
|
||||||
dirs = "5"
|
dirs = "6"
|
||||||
http = "1"
|
http = "1"
|
||||||
julid-rs = "1"
|
julid-rs = "1"
|
||||||
justerror = "1"
|
justerror = "1"
|
||||||
|
@ -29,18 +27,16 @@ password-hash = { version = "0.5", features = ["std", "getrandom"] }
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
sha256 = { version = "1", default-features = false }
|
sha256 = { version = "1", default-features = false }
|
||||||
sqlx = { version = "0.7", default-features = false, features = ["runtime-tokio", "sqlite", "tls-none", "migrate", "chrono"] }
|
sqlx = { version = "0.8", default-features = false, features = ["runtime-tokio", "sqlite", "tls-none", "migrate", "chrono"] }
|
||||||
thiserror = "1"
|
thiserror = "1"
|
||||||
tokio = { version = "1", features = ["rt-multi-thread", "signal", "tracing"], default-features = false }
|
tokio = { version = "1", features = ["rt-multi-thread", "signal", "tracing"], default-features = false }
|
||||||
tower = { version = "0.4", features = ["util", "timeout"], default-features = false }
|
tower = { version = "0.5", features = ["util", "timeout"], default-features = false }
|
||||||
tower-http = { version = "0.5", features = ["add-extension", "trace", "tracing", "fs"], default-features = false }
|
tower-http = { version = "0.6", features = ["add-extension", "trace", "tracing", "fs"], default-features = false }
|
||||||
tower-sessions = { version = "0.11", default-features = false }
|
tower-sessions = { version = "0.14", default-features = false }
|
||||||
tower-sessions-sqlx-store = { version = "0.11.0", default-features = false, features = ["sqlite"] }
|
tower-sessions-sqlx-store = { version = "0.15.0", default-features = false, features = ["sqlite"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
unicode-segmentation = "1"
|
unicode-segmentation = "1"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
axum-test = "14"
|
axum-test = "14"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use async_trait::async_trait;
|
|
||||||
use axum_login::{AuthUser, AuthnBackend, UserId};
|
use axum_login::{AuthUser, AuthnBackend, UserId};
|
||||||
use julid::Julid;
|
use julid::Julid;
|
||||||
use password_auth::verify_password;
|
use password_auth::verify_password;
|
||||||
|
@ -37,7 +36,6 @@ pub enum AuthErrorKind {
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl AuthnBackend for AuthStore {
|
impl AuthnBackend for AuthStore {
|
||||||
type User = User;
|
type User = User;
|
||||||
type Credentials = Credentials;
|
type Credentials = Credentials;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
use askama::Template;
|
||||||
use axum::response::{IntoResponse, Redirect};
|
use axum::response::{IntoResponse, Redirect};
|
||||||
|
|
||||||
use crate::{AuthSession, MainPage};
|
use crate::{AuthSession, MainPage, Wender};
|
||||||
|
|
||||||
pub async fn handle_slash_redir() -> impl IntoResponse {
|
pub async fn handle_slash_redir() -> impl IntoResponse {
|
||||||
Redirect::to("/")
|
Redirect::to("/")
|
||||||
|
@ -14,7 +15,7 @@ pub async fn handle_slash(auth: AuthSession) -> impl IntoResponse {
|
||||||
} else {
|
} else {
|
||||||
tracing::debug!("Not logged in.");
|
tracing::debug!("Not logged in.");
|
||||||
}
|
}
|
||||||
MainPage { user: auth.user }
|
MainPage { user: auth.user }.render().wender()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
70
src/lib.rs
70
src/lib.rs
|
@ -1,8 +1,11 @@
|
||||||
use axum::{
|
use axum::{
|
||||||
|
body::Body,
|
||||||
middleware,
|
middleware,
|
||||||
|
response::IntoResponse,
|
||||||
routing::{get, post, IntoMakeService},
|
routing::{get, post, IntoMakeService},
|
||||||
};
|
};
|
||||||
use axum_login::AuthManagerLayerBuilder;
|
use axum_login::AuthManagerLayerBuilder;
|
||||||
|
use http::StatusCode;
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate justerror;
|
extern crate justerror;
|
||||||
|
@ -42,6 +45,14 @@ use optional_optional_user::OptionalOptionalUser;
|
||||||
use templates::*;
|
use templates::*;
|
||||||
use watches::templates::*;
|
use watches::templates::*;
|
||||||
|
|
||||||
|
#[Error]
|
||||||
|
pub enum WatchError {
|
||||||
|
Auth(auth::AuthError),
|
||||||
|
Signup(signup::SignupError),
|
||||||
|
Watches(watches::WatchesError),
|
||||||
|
Render,
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the router to be used as a service or test object, you do you.
|
/// Returns the router to be used as a service or test object, you do you.
|
||||||
pub async fn app(db_pool: sqlx::SqlitePool) -> IntoMakeService<axum::Router> {
|
pub async fn app(db_pool: sqlx::SqlitePool) -> IntoMakeService<axum::Router> {
|
||||||
// don't bother bringing handlers into the whole crate namespace
|
// don't bother bringing handlers into the whole crate namespace
|
||||||
|
@ -71,19 +82,19 @@ pub async fn app(db_pool: sqlx::SqlitePool) -> IntoMakeService<axum::Router> {
|
||||||
axum::Router::new()
|
axum::Router::new()
|
||||||
.route("/", get(handle_slash).post(handle_slash))
|
.route("/", get(handle_slash).post(handle_slash))
|
||||||
.nest_service("/assets", assets_svc)
|
.nest_service("/assets", assets_svc)
|
||||||
.route("/signup", post(post_create_user))
|
.route("/signup", get(get_create_user).post(post_create_user))
|
||||||
.route("/signup/:invitation", get(get_create_user))
|
.route("/signup/{invitation}", get(get_create_user))
|
||||||
.route("/signup_success/:user", get(get_signup_success))
|
.route("/signup_success/{user}", get(get_signup_success))
|
||||||
.route("/login", get(get_login).post(post_login))
|
.route("/login", get(get_login).post(post_login))
|
||||||
.route("/logout", get(get_logout).post(post_logout))
|
.route("/logout", get(get_logout).post(post_logout))
|
||||||
.route("/watches", get(get_watches))
|
.route("/watches", get(get_watches))
|
||||||
.route("/watch", get(get_watch))
|
.route("/watch", get(get_watch))
|
||||||
.route("/watch/:watch", get(get_watch))
|
.route("/watch/{watch}", get(get_watch))
|
||||||
.route(
|
.route(
|
||||||
"/watch/add",
|
"/watch/add",
|
||||||
get(get_add_new_watch).post(post_add_new_watch),
|
get(get_add_new_watch).post(post_add_new_watch),
|
||||||
)
|
)
|
||||||
.route("/watch/status/:watch", get(get_watch_status))
|
.route("/watch/status/{watch}", get(get_watch_status))
|
||||||
.route(
|
.route(
|
||||||
"/quest/add",
|
"/quest/add",
|
||||||
get(get_search_watch).post(post_add_watch_quest),
|
get(get_search_watch).post(post_add_watch_quest),
|
||||||
|
@ -100,6 +111,55 @@ pub async fn app(db_pool: sqlx::SqlitePool) -> IntoMakeService<axum::Router> {
|
||||||
.into_make_service()
|
.into_make_service()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-************************************************************************
|
||||||
|
// internal stuff
|
||||||
|
//-************************************************************************
|
||||||
|
|
||||||
|
pub(crate) trait Wender {
|
||||||
|
fn wender(self) -> Body;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Wender for askama::Result<String> {
|
||||||
|
fn wender(self) -> Body {
|
||||||
|
let b = self.unwrap_or_else(|e| {
|
||||||
|
tracing::error!("got error rendering template: {e}");
|
||||||
|
"".to_string()
|
||||||
|
});
|
||||||
|
Body::new(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoResponse for WatchError {
|
||||||
|
fn into_response(self) -> axum::response::Response {
|
||||||
|
match self {
|
||||||
|
Self::Auth(ae) => ae.into_response(),
|
||||||
|
Self::Signup(se) => se.into_response(),
|
||||||
|
Self::Watches(we) => we.into_response(),
|
||||||
|
Self::Render => {
|
||||||
|
(StatusCode::INTERNAL_SERVER_ERROR, "could not render page").into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<auth::AuthError> for WatchError {
|
||||||
|
fn from(value: auth::AuthError) -> Self {
|
||||||
|
Self::Auth(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<signup::SignupError> for WatchError {
|
||||||
|
fn from(value: signup::SignupError) -> Self {
|
||||||
|
Self::Signup(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<watches::WatchesError> for WatchError {
|
||||||
|
fn from(value: watches::WatchesError) -> Self {
|
||||||
|
Self::Watches(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod test_utils;
|
pub mod test_utils;
|
||||||
|
|
||||||
|
|
21
src/login.rs
21
src/login.rs
|
@ -1,3 +1,4 @@
|
||||||
|
use askama::Template;
|
||||||
use axum::{
|
use axum::{
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
response::{IntoResponse, Redirect, Response},
|
response::{IntoResponse, Redirect, Response},
|
||||||
|
@ -7,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
auth::{AuthError, AuthErrorKind, Credentials},
|
auth::{AuthError, AuthErrorKind, Credentials},
|
||||||
AuthSession, LoginPage, LogoutPage, LogoutSuccessPage,
|
AuthSession, LoginPage, LogoutPage, LogoutSuccessPage, WatchError, Wender,
|
||||||
};
|
};
|
||||||
|
|
||||||
//-************************************************************************
|
//-************************************************************************
|
||||||
|
@ -53,16 +54,20 @@ impl From<LoginPostForm> for Credentials {
|
||||||
pub async fn post_login(
|
pub async fn post_login(
|
||||||
mut auth: AuthSession,
|
mut auth: AuthSession,
|
||||||
Form(mut login_form): Form<LoginPostForm>,
|
Form(mut login_form): Form<LoginPostForm>,
|
||||||
) -> Result<impl IntoResponse, AuthError> {
|
) -> Result<impl IntoResponse, WatchError> {
|
||||||
let dest = login_form.destination.take();
|
let dest = login_form.destination.take();
|
||||||
let user = match auth.authenticate(login_form.clone().into()).await {
|
let user = match auth.authenticate(login_form.clone().into()).await {
|
||||||
Ok(Some(user)) => user,
|
Ok(Some(user)) => user,
|
||||||
Ok(None) => return Ok(LoginPage::default().into_response()),
|
Ok(None) => return Ok(LoginPage::default().render().wender().into_response()),
|
||||||
Err(_) => return Err(AuthErrorKind::Internal.into()),
|
Err(_) => {
|
||||||
|
let err: AuthError = AuthErrorKind::Internal.into();
|
||||||
|
return Err(err.into());
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if auth.login(&user).await.is_err() {
|
if auth.login(&user).await.is_err() {
|
||||||
return Err(AuthErrorKind::Internal.into());
|
let err: AuthError = AuthErrorKind::Internal.into();
|
||||||
|
return Err(err.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref next) = dest {
|
if let Some(ref next) = dest {
|
||||||
|
@ -73,16 +78,16 @@ pub async fn post_login(
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_login() -> impl IntoResponse {
|
pub async fn get_login() -> impl IntoResponse {
|
||||||
LoginPage::default()
|
LoginPage::default().render().wender()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_logout() -> impl IntoResponse {
|
pub async fn get_logout() -> impl IntoResponse {
|
||||||
LogoutPage
|
LogoutPage.render().wender()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn post_logout(mut auth: AuthSession) -> impl IntoResponse {
|
pub async fn post_logout(mut auth: AuthSession) -> impl IntoResponse {
|
||||||
match auth.logout().await {
|
match auth.logout().await {
|
||||||
Ok(_) => LogoutSuccessPage.into_response(),
|
Ok(_) => LogoutSuccessPage.render().wender().into_response(),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::debug!("{e}");
|
tracing::debug!("{e}");
|
||||||
let e: AuthError = AuthErrorKind::Internal.into();
|
let e: AuthError = AuthErrorKind::Internal.into();
|
||||||
|
|
|
@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
misc_util::empty_string_as_none, AuthSession, OptionalOptionalUser, Star, User, Watch,
|
misc_util::empty_string_as_none, AuthSession, OptionalOptionalUser, Star, User, Watch, Wender,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)]
|
#[derive(Debug, Default, Template, Deserialize, Serialize, PartialEq, Eq, OptionalOptionalUser)]
|
||||||
|
@ -60,6 +60,8 @@ pub async fn get_search_watch(
|
||||||
results: watches,
|
results: watches,
|
||||||
user,
|
user,
|
||||||
}
|
}
|
||||||
|
.render()
|
||||||
|
.wender()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_search_star(
|
pub async fn get_search_star(
|
||||||
|
@ -83,4 +85,6 @@ pub async fn get_search_star(
|
||||||
results: watches,
|
results: watches,
|
||||||
user,
|
user,
|
||||||
}
|
}
|
||||||
|
.render()
|
||||||
|
.wender()
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ use argon2::{
|
||||||
password_hash::{rand_core::OsRng, PasswordHasher, SaltString},
|
password_hash::{rand_core::OsRng, PasswordHasher, SaltString},
|
||||||
Argon2,
|
Argon2,
|
||||||
};
|
};
|
||||||
|
use askama::Template;
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Form, Path, State},
|
extract::{Form, Path, State},
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
|
@ -12,48 +13,8 @@ use serde::Deserialize;
|
||||||
use sqlx::{query_as, Sqlite, SqlitePool};
|
use sqlx::{query_as, Sqlite, SqlitePool};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use super::{templates::*, Invitation};
|
use super::{templates::*, CreateUserError, CreateUserErrorKind, Invitation, SignupError};
|
||||||
use crate::{misc_util::empty_string_as_none, User};
|
use crate::{misc_util::empty_string_as_none, User, WatchError, Wender};
|
||||||
|
|
||||||
//-************************************************************************
|
|
||||||
// Error types for user creation
|
|
||||||
//-************************************************************************
|
|
||||||
|
|
||||||
#[Error(desc = "Could not create user.")]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub struct CreateUserError(#[from] CreateUserErrorKind);
|
|
||||||
|
|
||||||
impl IntoResponse for CreateUserError {
|
|
||||||
fn into_response(self) -> Response {
|
|
||||||
match self.0 {
|
|
||||||
CreateUserErrorKind::UnknownDBError => {
|
|
||||||
(StatusCode::INTERNAL_SERVER_ERROR, format!("{self}")).into_response()
|
|
||||||
}
|
|
||||||
CreateUserErrorKind::BadInvitation => (
|
|
||||||
StatusCode::OK,
|
|
||||||
SignupErrorPage("Sorry, that invitation isn't valid.".to_string()),
|
|
||||||
)
|
|
||||||
.into_response(),
|
|
||||||
_ => (StatusCode::OK, format!("{self}")).into_response(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Error]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum CreateUserErrorKind {
|
|
||||||
BadInvitation,
|
|
||||||
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,
|
|
||||||
UnknownDBError,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Deserialize, PartialEq, Eq)]
|
#[derive(Debug, Default, Deserialize, PartialEq, Eq)]
|
||||||
pub struct SignupForm {
|
pub struct SignupForm {
|
||||||
|
@ -76,15 +37,25 @@ pub struct SignupForm {
|
||||||
pub async fn get_create_user(
|
pub async fn get_create_user(
|
||||||
State(_pool): State<SqlitePool>,
|
State(_pool): State<SqlitePool>,
|
||||||
invitation: Option<Path<String>>,
|
invitation: Option<Path<String>>,
|
||||||
) -> Result<impl IntoResponse, CreateUserError> {
|
) -> Result<impl IntoResponse, WatchError> {
|
||||||
let invitation = invitation.ok_or(CreateUserErrorKind::BadInvitation)?;
|
let Path(invitation) = invitation.ok_or_else(|| {
|
||||||
let invitation =
|
let e: CreateUserError = CreateUserErrorKind::BadInvitation.into();
|
||||||
Julid::from_str(&invitation.0).map_err(|_| CreateUserErrorKind::BadInvitation)?;
|
let e: SignupError = e.into();
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
let invitation = Julid::from_str(&invitation).map_err(|_| {
|
||||||
|
let e: CreateUserError = CreateUserErrorKind::BadInvitation.into();
|
||||||
|
let e: SignupError = e.into();
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(SignupPage {
|
Ok(SignupPage {
|
||||||
invitation,
|
invitation,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
}
|
||||||
|
.render()
|
||||||
|
.wender()
|
||||||
|
.into_response())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Post Handler: validates form values and calls the actual, private user
|
/// Post Handler: validates form values and calls the actual, private user
|
||||||
|
@ -93,7 +64,7 @@ pub async fn get_create_user(
|
||||||
pub async fn post_create_user(
|
pub async fn post_create_user(
|
||||||
State(pool): State<SqlitePool>,
|
State(pool): State<SqlitePool>,
|
||||||
Form(signup): Form<SignupForm>,
|
Form(signup): Form<SignupForm>,
|
||||||
) -> Result<impl IntoResponse, CreateUserError> {
|
) -> Result<impl IntoResponse, SignupError> {
|
||||||
use crate::misc_util::validate_optional_length;
|
use crate::misc_util::validate_optional_length;
|
||||||
let username = signup.username.trim();
|
let username = signup.username.trim();
|
||||||
let password = signup.password.trim();
|
let password = signup.password.trim();
|
||||||
|
@ -156,7 +127,10 @@ pub async fn get_signup_success(
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut resp = SignupSuccessPage(user.clone()).into_response();
|
let mut resp = SignupSuccessPage(user.clone())
|
||||||
|
.render()
|
||||||
|
.wender()
|
||||||
|
.into_response();
|
||||||
|
|
||||||
if user.username.is_empty() || id.is_alpha() {
|
if user.username.is_empty() || id.is_alpha() {
|
||||||
// redirect to front page if we got here without a valid user ID
|
// redirect to front page if we got here without a valid user ID
|
||||||
|
@ -178,7 +152,7 @@ pub(crate) async fn create_user(
|
||||||
password: &[u8],
|
password: &[u8],
|
||||||
pool: &SqlitePool,
|
pool: &SqlitePool,
|
||||||
invitation: &str,
|
invitation: &str,
|
||||||
) -> Result<User, CreateUserError> {
|
) -> Result<User, SignupError> {
|
||||||
const CREATE_QUERY: &str =
|
const CREATE_QUERY: &str =
|
||||||
"insert into users (username, displayname, email, pwhash, invited_by) values ($1, $2, $3, $4, $5) returning *";
|
"insert into users (username, displayname, email, pwhash, invited_by) values ($1, $2, $3, $4, $5) returning *";
|
||||||
|
|
||||||
|
@ -192,7 +166,7 @@ pub(crate) async fn create_user(
|
||||||
|
|
||||||
let mut tx = pool.begin().await.map_err(|e| {
|
let mut tx = pool.begin().await.map_err(|e| {
|
||||||
tracing::debug!("db error: {e}");
|
tracing::debug!("db error: {e}");
|
||||||
CreateUserErrorKind::UnknownDBError
|
CreateUserErrorKind::DBError
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let invitation = Julid::from_str(invitation).map_err(|_| CreateUserErrorKind::BadInvitation)?;
|
let invitation = Julid::from_str(invitation).map_err(|_| CreateUserErrorKind::BadInvitation)?;
|
||||||
|
@ -215,16 +189,16 @@ pub(crate) async fn create_user(
|
||||||
if exit == 2067u32 || exit == 1555 {
|
if exit == 2067u32 || exit == 1555 {
|
||||||
CreateUserErrorKind::AlreadyExists
|
CreateUserErrorKind::AlreadyExists
|
||||||
} else {
|
} else {
|
||||||
CreateUserErrorKind::UnknownDBError
|
CreateUserErrorKind::DBError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => CreateUserErrorKind::UnknownDBError,
|
_ => CreateUserErrorKind::DBError,
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
tx.commit().await.map_err(|e| {
|
tx.commit().await.map_err(|e| {
|
||||||
tracing::debug!("db error: {e}");
|
tracing::debug!("db error: {e}");
|
||||||
CreateUserErrorKind::UnknownDBError
|
CreateUserErrorKind::DBError
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(user)
|
Ok(user)
|
||||||
|
@ -240,7 +214,7 @@ async fn validate_invitation(
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
tracing::debug!("db error: {e}");
|
tracing::debug!("db error: {e}");
|
||||||
CreateUserErrorKind::UnknownDBError
|
CreateUserErrorKind::DBError
|
||||||
})?
|
})?
|
||||||
.ok_or(CreateUserErrorKind::BadInvitation)?;
|
.ok_or(CreateUserErrorKind::BadInvitation)?;
|
||||||
|
|
||||||
|
@ -263,7 +237,7 @@ async fn validate_invitation(
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
tracing::debug!("db error: {e}");
|
tracing::debug!("db error: {e}");
|
||||||
CreateUserErrorKind::UnknownDBError
|
CreateUserErrorKind::DBError
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(invitation.owner)
|
Ok(invitation.owner)
|
||||||
|
|
|
@ -1,15 +1,29 @@
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use askama::Template;
|
||||||
|
use axum::response::{IntoResponse, Response};
|
||||||
|
use http::StatusCode;
|
||||||
use julid::Julid;
|
use julid::Julid;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
|
use templates::{SignupErrorPage, SignupPage};
|
||||||
|
|
||||||
use crate::WatchDate;
|
use crate::{WatchDate, WatchError};
|
||||||
|
|
||||||
pub mod handlers;
|
pub mod handlers;
|
||||||
pub mod templates;
|
pub mod templates;
|
||||||
|
|
||||||
#[Error(desc = "Could not create user.")]
|
//-************************************************************************
|
||||||
|
// Error types for user creation
|
||||||
|
//-************************************************************************
|
||||||
|
|
||||||
|
#[Error]
|
||||||
|
pub enum SignupError {
|
||||||
|
Invite(CreateInviteError),
|
||||||
|
User(CreateUserError),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Error(desc = "Could not create invitation.")]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(PartialEq, Eq)]
|
#[derive(PartialEq, Eq)]
|
||||||
pub struct CreateInviteError(#[from] CreateInviteErrorKind);
|
pub struct CreateInviteError(#[from] CreateInviteErrorKind);
|
||||||
|
@ -23,6 +37,26 @@ pub enum CreateInviteErrorKind {
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Error(desc = "Could not create user.")]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub struct CreateUserError(#[from] CreateUserErrorKind);
|
||||||
|
|
||||||
|
#[Error]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum CreateUserErrorKind {
|
||||||
|
BadInvitation,
|
||||||
|
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,
|
||||||
|
DBError,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, sqlx::FromRow)]
|
||||||
pub struct Invitation {
|
pub struct Invitation {
|
||||||
id: Julid,
|
id: Julid,
|
||||||
|
@ -95,6 +129,87 @@ impl Invitation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-************************************************************************
|
||||||
|
// internal impls
|
||||||
|
//-************************************************************************
|
||||||
|
|
||||||
|
impl From<CreateUserError> for SignupError {
|
||||||
|
fn from(value: CreateUserError) -> Self {
|
||||||
|
SignupError::User(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CreateUserErrorKind> for SignupError {
|
||||||
|
fn from(value: CreateUserErrorKind) -> Self {
|
||||||
|
let e: CreateUserError = value.into();
|
||||||
|
e.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CreateInviteError> for SignupError {
|
||||||
|
fn from(value: CreateInviteError) -> Self {
|
||||||
|
SignupError::Invite(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CreateInviteErrorKind> for SignupError {
|
||||||
|
fn from(value: CreateInviteErrorKind) -> Self {
|
||||||
|
let e: CreateInviteError = value.into();
|
||||||
|
e.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoResponse for SignupError {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
match self {
|
||||||
|
Self::Invite(ie) => ie.into_response(),
|
||||||
|
Self::User(ue) => ue.into_response(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoResponse for CreateInviteError {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
match self.0 {
|
||||||
|
CreateInviteErrorKind::DBError => {
|
||||||
|
(StatusCode::INTERNAL_SERVER_ERROR, format!("{self}")).into_response()
|
||||||
|
}
|
||||||
|
CreateInviteErrorKind::NoOwner => (
|
||||||
|
StatusCode::OK,
|
||||||
|
SignupErrorPage("Sorry, invitations require an owner".to_string())
|
||||||
|
.render()
|
||||||
|
.map_err(|e| {
|
||||||
|
tracing::error!("could not render create user error page, got {e}");
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.into_response(),
|
||||||
|
_ => (StatusCode::OK, format!("{self}")).into_response(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoResponse for CreateUserError {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
match self.0 {
|
||||||
|
CreateUserErrorKind::DBError => {
|
||||||
|
(StatusCode::INTERNAL_SERVER_ERROR, format!("{self}")).into_response()
|
||||||
|
}
|
||||||
|
CreateUserErrorKind::BadInvitation => (
|
||||||
|
StatusCode::OK,
|
||||||
|
SignupErrorPage("Sorry, that invitation isn't valid.".to_string())
|
||||||
|
.render()
|
||||||
|
.map_err(|e| {
|
||||||
|
tracing::error!("could not render create user error page, got {e}");
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.into_response(),
|
||||||
|
_ => (StatusCode::OK, format!("{self}")).into_response(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use tokio::runtime::Runtime;
|
use tokio::runtime::Runtime;
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
|
use askama::Template;
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{Form, Path, State},
|
extract::{Form, Path, State},
|
||||||
http::StatusCode,
|
http::StatusCode,
|
||||||
response::{IntoResponse, Redirect, Response},
|
response::{IntoResponse, Redirect},
|
||||||
};
|
};
|
||||||
use http::HeaderValue;
|
use http::HeaderValue;
|
||||||
use julid::Julid;
|
use julid::Julid;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::{query, query_as, query_scalar, SqlitePool};
|
use sqlx::{query, query_as, query_scalar, SqlitePool};
|
||||||
|
|
||||||
use super::templates::{AddNewWatchPage, GetWatchPage, WatchStatusMenus};
|
use super::{
|
||||||
|
templates::{AddNewWatchPage, GetWatchPage, WatchStatusMenus},
|
||||||
|
AddError, AddErrorKind, EditError, EditErrorKind, WatchesError,
|
||||||
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
misc_util::empty_string_as_none, AuthSession, MyWatchesPage, ShowKind, Watch, WatchQuest,
|
misc_util::empty_string_as_none, AuthSession, MyWatchesPage, ShowKind, Watch, WatchError,
|
||||||
|
WatchQuest, Wender,
|
||||||
};
|
};
|
||||||
|
|
||||||
//-************************************************************************
|
//-************************************************************************
|
||||||
|
@ -28,62 +33,6 @@ const ADD_WATCH_QUEST_QUERY: &str =
|
||||||
|
|
||||||
const CHECKMARK: &str = "✓";
|
const CHECKMARK: &str = "✓";
|
||||||
|
|
||||||
//-************************************************************************
|
|
||||||
// Error types for Watch creation
|
|
||||||
//-************************************************************************
|
|
||||||
|
|
||||||
#[Error]
|
|
||||||
pub struct AddError(#[from] AddErrorKind);
|
|
||||||
|
|
||||||
#[Error]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum AddErrorKind {
|
|
||||||
UnknownDBError,
|
|
||||||
NotSignedIn,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoResponse for AddError {
|
|
||||||
fn into_response(self) -> Response {
|
|
||||||
match &self.0 {
|
|
||||||
AddErrorKind::UnknownDBError => {
|
|
||||||
(StatusCode::INTERNAL_SERVER_ERROR, format!("{self}")).into_response()
|
|
||||||
}
|
|
||||||
AddErrorKind::NotSignedIn => (
|
|
||||||
StatusCode::OK,
|
|
||||||
"Ope, you need to sign in first!".to_string(),
|
|
||||||
)
|
|
||||||
.into_response(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Error]
|
|
||||||
pub struct EditError(#[from] EditErrorKind);
|
|
||||||
|
|
||||||
#[Error]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum EditErrorKind {
|
|
||||||
UnknownDBError,
|
|
||||||
NotSignedIn,
|
|
||||||
NotFound,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoResponse for EditError {
|
|
||||||
fn into_response(self) -> Response {
|
|
||||||
match &self.0 {
|
|
||||||
EditErrorKind::UnknownDBError => {
|
|
||||||
(StatusCode::INTERNAL_SERVER_ERROR, format!("{self}")).into_response()
|
|
||||||
}
|
|
||||||
EditErrorKind::NotSignedIn => (
|
|
||||||
StatusCode::OK,
|
|
||||||
"Ope, you need to sign in first!".to_string(),
|
|
||||||
)
|
|
||||||
.into_response(),
|
|
||||||
EditErrorKind::NotFound => (StatusCode::OK, "Could not find watch").into_response(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//-************************************************************************
|
//-************************************************************************
|
||||||
// Types for receiving arguments from forms
|
// Types for receiving arguments from forms
|
||||||
//-************************************************************************
|
//-************************************************************************
|
||||||
|
@ -120,7 +69,7 @@ pub struct PostEditQuest {
|
||||||
//-************************************************************************
|
//-************************************************************************
|
||||||
|
|
||||||
pub async fn get_add_new_watch(auth: AuthSession) -> impl IntoResponse {
|
pub async fn get_add_new_watch(auth: AuthSession) -> impl IntoResponse {
|
||||||
AddNewWatchPage { user: auth.user }
|
AddNewWatchPage { user: auth.user }.render().wender()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a Watch to your watchlist (side effects system-add)
|
/// Add a Watch to your watchlist (side effects system-add)
|
||||||
|
@ -128,7 +77,7 @@ pub async fn post_add_new_watch(
|
||||||
auth: AuthSession,
|
auth: AuthSession,
|
||||||
State(pool): State<SqlitePool>,
|
State(pool): State<SqlitePool>,
|
||||||
Form(form): Form<PostAddNewWatch>,
|
Form(form): Form<PostAddNewWatch>,
|
||||||
) -> Result<impl IntoResponse, AddError> {
|
) -> Result<impl IntoResponse, WatchesError> {
|
||||||
if let Some(user) = auth.user {
|
if let Some(user) = auth.user {
|
||||||
{
|
{
|
||||||
let watch = Watch {
|
let watch = Watch {
|
||||||
|
@ -147,15 +96,14 @@ pub async fn post_add_new_watch(
|
||||||
watched: false,
|
watched: false,
|
||||||
watch: watch_id,
|
watch: watch_id,
|
||||||
};
|
};
|
||||||
add_watch_quest_impl(&pool, &quest)
|
add_watch_quest_impl(&pool, &quest).await?;
|
||||||
.await
|
|
||||||
.map_err(|_| AddErrorKind::UnknownDBError)?;
|
|
||||||
|
|
||||||
let location = format!("/watch/{watch_id}");
|
let location = format!("/watch/{watch_id}");
|
||||||
Ok(Redirect::to(&location))
|
Ok(Redirect::to(&location))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(AddErrorKind::NotSignedIn.into())
|
let e: AddError = AddErrorKind::NotSignedIn.into();
|
||||||
|
Err(e.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,7 +119,7 @@ async fn add_new_watch_impl(db_pool: &SqlitePool, watch: &Watch) -> Result<Julid
|
||||||
.await
|
.await
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
tracing::error!("Got error: {err}");
|
tracing::error!("Got error: {err}");
|
||||||
AddErrorKind::UnknownDBError
|
AddErrorKind::DBError
|
||||||
})?;
|
})?;
|
||||||
Ok(watch_id)
|
Ok(watch_id)
|
||||||
}
|
}
|
||||||
|
@ -181,7 +129,7 @@ pub async fn post_add_watch_quest(
|
||||||
auth: AuthSession,
|
auth: AuthSession,
|
||||||
State(pool): State<SqlitePool>,
|
State(pool): State<SqlitePool>,
|
||||||
Form(form): Form<PostAddExistingWatch>,
|
Form(form): Form<PostAddExistingWatch>,
|
||||||
) -> Result<impl IntoResponse, AddError> {
|
) -> Result<impl IntoResponse, WatchesError> {
|
||||||
if let Some(user) = auth.user {
|
if let Some(user) = auth.user {
|
||||||
let quest = WatchQuest {
|
let quest = WatchQuest {
|
||||||
user: user.id,
|
user: user.id,
|
||||||
|
@ -189,9 +137,8 @@ pub async fn post_add_watch_quest(
|
||||||
public: form.public,
|
public: form.public,
|
||||||
watched: false,
|
watched: false,
|
||||||
};
|
};
|
||||||
add_watch_quest_impl(&pool, &quest)
|
add_watch_quest_impl(&pool, &quest).await?;
|
||||||
.await
|
|
||||||
.map_err(|_| AddErrorKind::UnknownDBError)?;
|
|
||||||
let resp = checkmark(form.public);
|
let resp = checkmark(form.public);
|
||||||
Ok(resp.into_response())
|
Ok(resp.into_response())
|
||||||
} else {
|
} else {
|
||||||
|
@ -203,7 +150,7 @@ pub async fn post_add_watch_quest(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_watch_quest_impl(pool: &SqlitePool, quest: &WatchQuest) -> Result<(), ()> {
|
async fn add_watch_quest_impl(pool: &SqlitePool, quest: &WatchQuest) -> Result<(), AddError> {
|
||||||
query(ADD_WATCH_QUEST_QUERY)
|
query(ADD_WATCH_QUEST_QUERY)
|
||||||
.bind(quest.user)
|
.bind(quest.user)
|
||||||
.bind(quest.watch)
|
.bind(quest.watch)
|
||||||
|
@ -213,6 +160,7 @@ pub async fn add_watch_quest_impl(pool: &SqlitePool, quest: &WatchQuest) -> Resu
|
||||||
.await
|
.await
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
tracing::error!("Got error: {err}");
|
tracing::error!("Got error: {err}");
|
||||||
|
AddErrorKind::DBError
|
||||||
})?;
|
})?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -222,13 +170,14 @@ pub async fn edit_watch_quest(
|
||||||
auth: AuthSession,
|
auth: AuthSession,
|
||||||
State(pool): State<SqlitePool>,
|
State(pool): State<SqlitePool>,
|
||||||
Form(form): Form<PostEditQuest>,
|
Form(form): Form<PostEditQuest>,
|
||||||
) -> Result<impl IntoResponse, EditError> {
|
) -> Result<impl IntoResponse, WatchesError> {
|
||||||
if let Some(user) = auth.user {
|
if let Some(user) = auth.user {
|
||||||
let watch = Julid::from_str(form.watch.trim()).map_err(|_| EditErrorKind::NotFound)?;
|
let watch = Julid::from_str(form.watch.trim()).map_err(|_| EditErrorKind::NotFound)?;
|
||||||
Ok(
|
Ok(
|
||||||
edit_watch_quest_impl(&pool, form.act.trim(), user.id, watch)
|
edit_watch_quest_impl(&pool, form.act.trim(), user.id, watch)
|
||||||
.await?
|
.await?
|
||||||
.into_response(),
|
.render()
|
||||||
|
.wender(),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Err(EditErrorKind::NotSignedIn.into())
|
Err(EditErrorKind::NotSignedIn.into())
|
||||||
|
@ -240,7 +189,7 @@ async fn edit_watch_quest_impl(
|
||||||
action: &str,
|
action: &str,
|
||||||
user: Julid,
|
user: Julid,
|
||||||
watch: Julid,
|
watch: Julid,
|
||||||
) -> Result<WatchStatusMenus, EditErrorKind> {
|
) -> Result<WatchStatusMenus, EditError> {
|
||||||
let quest: Option<WatchQuest> = query_as(GET_QUEST_QUERY)
|
let quest: Option<WatchQuest> = query_as(GET_QUEST_QUERY)
|
||||||
.bind(user)
|
.bind(user)
|
||||||
.bind(watch)
|
.bind(watch)
|
||||||
|
@ -248,7 +197,7 @@ async fn edit_watch_quest_impl(
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
tracing::error!("Got error from checking watch status: {e:?}");
|
tracing::error!("Got error from checking watch status: {e:?}");
|
||||||
EditErrorKind::UnknownDBError
|
EditErrorKind::DBError
|
||||||
})?;
|
})?;
|
||||||
if let Some(quest) = quest {
|
if let Some(quest) = quest {
|
||||||
match action {
|
match action {
|
||||||
|
@ -262,7 +211,7 @@ async fn edit_watch_quest_impl(
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
tracing::error!("Error removing quest: {e}");
|
tracing::error!("Error removing quest: {e}");
|
||||||
EditErrorKind::UnknownDBError
|
EditErrorKind::DBError
|
||||||
})?;
|
})?;
|
||||||
Ok(WatchStatusMenus { watch, quest: None })
|
Ok(WatchStatusMenus { watch, quest: None })
|
||||||
}
|
}
|
||||||
|
@ -278,7 +227,7 @@ async fn edit_watch_quest_impl(
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
tracing::error!("Error updating quest: {e}");
|
tracing::error!("Error updating quest: {e}");
|
||||||
EditErrorKind::UnknownDBError
|
EditErrorKind::DBError
|
||||||
})?;
|
})?;
|
||||||
let quest = WatchQuest { watched, ..quest };
|
let quest = WatchQuest { watched, ..quest };
|
||||||
Ok(WatchStatusMenus {
|
Ok(WatchStatusMenus {
|
||||||
|
@ -298,7 +247,7 @@ async fn edit_watch_quest_impl(
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
tracing::error!("Error updating quest: {e}");
|
tracing::error!("Error updating quest: {e}");
|
||||||
EditErrorKind::UnknownDBError
|
EditErrorKind::DBError
|
||||||
})?;
|
})?;
|
||||||
let quest = WatchQuest { public, ..quest };
|
let quest = WatchQuest { public, ..quest };
|
||||||
Ok(WatchStatusMenus {
|
Ok(WatchStatusMenus {
|
||||||
|
@ -312,7 +261,7 @@ async fn edit_watch_quest_impl(
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(EditErrorKind::NotFound)
|
Err(EditErrorKind::NotFound.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -336,14 +285,20 @@ pub async fn get_watch(
|
||||||
watch,
|
watch,
|
||||||
user: auth.user,
|
user: auth.user,
|
||||||
}
|
}
|
||||||
|
.render()
|
||||||
|
.map_err(|e| {
|
||||||
|
tracing::error!("could not render watch page, got {e}");
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
})
|
||||||
|
.into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// everything the user has saved
|
/// everything the user has saved
|
||||||
pub async fn get_watches(auth: AuthSession, State(pool): State<SqlitePool>) -> impl IntoResponse {
|
pub async fn get_watches(auth: AuthSession, State(pool): State<SqlitePool>) -> impl IntoResponse {
|
||||||
let user = auth.user;
|
let user = auth.user;
|
||||||
let watches: Vec<Watch> = if (user).is_some() {
|
let watches: Vec<Watch> = if let Some(ref user) = user {
|
||||||
query_as(GET_SAVED_WATCHES_QUERY)
|
query_as(GET_SAVED_WATCHES_QUERY)
|
||||||
.bind(user.as_ref().unwrap().id)
|
.bind(user.id)
|
||||||
.fetch_all(&pool)
|
.fetch_all(&pool)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
|
@ -352,6 +307,9 @@ pub async fn get_watches(auth: AuthSession, State(pool): State<SqlitePool>) -> i
|
||||||
};
|
};
|
||||||
|
|
||||||
MyWatchesPage { watches, user }
|
MyWatchesPage { watches, user }
|
||||||
|
.render()
|
||||||
|
.wender()
|
||||||
|
.into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_watch_status(
|
pub async fn get_watch_status(
|
||||||
|
@ -369,7 +327,10 @@ pub async fn get_watch_status(
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
tracing::error!("Got error from checking watch status: {e:?}");
|
tracing::error!("Got error from checking watch status: {e:?}");
|
||||||
})?;
|
})?;
|
||||||
Ok(WatchStatusMenus { watch, quest }.into_response())
|
Ok(WatchStatusMenus { watch, quest }
|
||||||
|
.render()
|
||||||
|
.wender()
|
||||||
|
.into_response())
|
||||||
} else {
|
} else {
|
||||||
Ok("<a href='/login'>Login to add</a>".into_response())
|
Ok("<a href='/login'>Login to add</a>".into_response())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,44 @@
|
||||||
|
use axum::response::{IntoResponse, Response};
|
||||||
|
use http::StatusCode;
|
||||||
use julid::Julid;
|
use julid::Julid;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::WatchError;
|
||||||
|
|
||||||
pub mod handlers;
|
pub mod handlers;
|
||||||
pub mod templates;
|
pub mod templates;
|
||||||
|
|
||||||
|
//-************************************************************************
|
||||||
|
// Error types for Watch creation
|
||||||
|
//-************************************************************************
|
||||||
|
|
||||||
|
#[Error]
|
||||||
|
pub enum WatchesError {
|
||||||
|
Add(AddError),
|
||||||
|
Edit(EditError),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Error]
|
||||||
|
pub struct AddError(#[from] AddErrorKind);
|
||||||
|
|
||||||
|
#[Error]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum AddErrorKind {
|
||||||
|
DBError,
|
||||||
|
NotSignedIn,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Error]
|
||||||
|
pub struct EditError(#[from] EditErrorKind);
|
||||||
|
|
||||||
|
#[Error]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum EditErrorKind {
|
||||||
|
DBError,
|
||||||
|
NotSignedIn,
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, sqlx::Type,
|
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, sqlx::Type,
|
||||||
)]
|
)]
|
||||||
|
@ -104,3 +139,73 @@ impl WatchQuest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-************************************************************************
|
||||||
|
// error impls
|
||||||
|
//-************************************************************************
|
||||||
|
|
||||||
|
impl IntoResponse for WatchesError {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
match self {
|
||||||
|
Self::Add(ae) => ae.into_response(),
|
||||||
|
Self::Edit(ee) => ee.into_response(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AddError> for WatchesError {
|
||||||
|
fn from(value: AddError) -> Self {
|
||||||
|
WatchesError::Add(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<AddErrorKind> for WatchesError {
|
||||||
|
fn from(value: AddErrorKind) -> Self {
|
||||||
|
let e: AddError = value.into();
|
||||||
|
e.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<EditError> for WatchesError {
|
||||||
|
fn from(value: EditError) -> Self {
|
||||||
|
WatchesError::Edit(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<EditErrorKind> for WatchesError {
|
||||||
|
fn from(value: EditErrorKind) -> Self {
|
||||||
|
let e: EditError = value.into();
|
||||||
|
e.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoResponse for EditError {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
match &self.0 {
|
||||||
|
EditErrorKind::DBError => {
|
||||||
|
(StatusCode::INTERNAL_SERVER_ERROR, format!("{self}")).into_response()
|
||||||
|
}
|
||||||
|
EditErrorKind::NotSignedIn => (
|
||||||
|
StatusCode::OK,
|
||||||
|
"Ope, you need to sign in first!".to_string(),
|
||||||
|
)
|
||||||
|
.into_response(),
|
||||||
|
EditErrorKind::NotFound => (StatusCode::OK, "Could not find watch").into_response(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntoResponse for AddError {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
match &self.0 {
|
||||||
|
AddErrorKind::DBError => {
|
||||||
|
(StatusCode::INTERNAL_SERVER_ERROR, format!("{self}")).into_response()
|
||||||
|
}
|
||||||
|
AddErrorKind::NotSignedIn => (
|
||||||
|
StatusCode::OK,
|
||||||
|
"Ope, you need to sign in first!".to_string(),
|
||||||
|
)
|
||||||
|
.into_response(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue