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]
|
||||
name = "what2watch"
|
||||
version = "0.0.1"
|
||||
edition = "2021"
|
||||
edition = "2024"
|
||||
default-run = "what2watch"
|
||||
|
||||
[dependencies]
|
||||
|
@ -10,16 +10,14 @@ optional_optional_user = {path = "optional_optional_user"}
|
|||
|
||||
# regular external deps
|
||||
argon2 = "0.5"
|
||||
askama = { version = "0.12", features = ["with-axum"] }
|
||||
askama_axum = "0.4"
|
||||
async-trait = "0.1"
|
||||
axum = { version = "0.7", features = ["macros"] }
|
||||
axum-login = "0.14"
|
||||
axum-macros = "0.4"
|
||||
askama = { version = "0.14" }
|
||||
axum = { version = "0.8", features = ["macros"] }
|
||||
axum-login = "0.18"
|
||||
axum-macros = "0.5"
|
||||
chrono = { version = "0.4", default-features = false, features = ["std", "clock", "serde"] }
|
||||
clap = { version = "4", features = ["derive", "env", "unicode", "suggestions", "usage"] }
|
||||
confy = "0.6"
|
||||
dirs = "5"
|
||||
confy = "1"
|
||||
dirs = "6"
|
||||
http = "1"
|
||||
julid-rs = "1"
|
||||
justerror = "1"
|
||||
|
@ -29,18 +27,16 @@ password-hash = { version = "0.5", features = ["std", "getrandom"] }
|
|||
rand = "0.8"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
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"
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "signal", "tracing"], default-features = false }
|
||||
tower = { version = "0.4", features = ["util", "timeout"], default-features = false }
|
||||
tower-http = { version = "0.5", features = ["add-extension", "trace", "tracing", "fs"], default-features = false }
|
||||
tower-sessions = { version = "0.11", default-features = false }
|
||||
tower-sessions-sqlx-store = { version = "0.11.0", default-features = false, features = ["sqlite"] }
|
||||
tower = { version = "0.5", features = ["util", "timeout"], default-features = false }
|
||||
tower-http = { version = "0.6", features = ["add-extension", "trace", "tracing", "fs"], default-features = false }
|
||||
tower-sessions = { version = "0.14", default-features = false }
|
||||
tower-sessions-sqlx-store = { version = "0.15.0", default-features = false, features = ["sqlite"] }
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
unicode-segmentation = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
axum-test = "14"
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use async_trait::async_trait;
|
||||
use axum_login::{AuthUser, AuthnBackend, UserId};
|
||||
use julid::Julid;
|
||||
use password_auth::verify_password;
|
||||
|
@ -37,7 +36,6 @@ pub enum AuthErrorKind {
|
|||
Unknown,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl AuthnBackend for AuthStore {
|
||||
type User = User;
|
||||
type Credentials = Credentials;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use askama::Template;
|
||||
use axum::response::{IntoResponse, Redirect};
|
||||
|
||||
use crate::{AuthSession, MainPage};
|
||||
use crate::{AuthSession, MainPage, Wender};
|
||||
|
||||
pub async fn handle_slash_redir() -> impl IntoResponse {
|
||||
Redirect::to("/")
|
||||
|
@ -14,7 +15,7 @@ pub async fn handle_slash(auth: AuthSession) -> impl IntoResponse {
|
|||
} else {
|
||||
tracing::debug!("Not logged in.");
|
||||
}
|
||||
MainPage { user: auth.user }
|
||||
MainPage { user: auth.user }.render().wender()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
70
src/lib.rs
70
src/lib.rs
|
@ -1,8 +1,11 @@
|
|||
use axum::{
|
||||
body::Body,
|
||||
middleware,
|
||||
response::IntoResponse,
|
||||
routing::{get, post, IntoMakeService},
|
||||
};
|
||||
use axum_login::AuthManagerLayerBuilder;
|
||||
use http::StatusCode;
|
||||
use sqlx::SqlitePool;
|
||||
#[macro_use]
|
||||
extern crate justerror;
|
||||
|
@ -42,6 +45,14 @@ use optional_optional_user::OptionalOptionalUser;
|
|||
use 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.
|
||||
pub async fn app(db_pool: sqlx::SqlitePool) -> IntoMakeService<axum::Router> {
|
||||
// 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()
|
||||
.route("/", get(handle_slash).post(handle_slash))
|
||||
.nest_service("/assets", assets_svc)
|
||||
.route("/signup", post(post_create_user))
|
||||
.route("/signup/:invitation", get(get_create_user))
|
||||
.route("/signup_success/:user", get(get_signup_success))
|
||||
.route("/signup", get(get_create_user).post(post_create_user))
|
||||
.route("/signup/{invitation}", get(get_create_user))
|
||||
.route("/signup_success/{user}", get(get_signup_success))
|
||||
.route("/login", get(get_login).post(post_login))
|
||||
.route("/logout", get(get_logout).post(post_logout))
|
||||
.route("/watches", get(get_watches))
|
||||
.route("/watch", get(get_watch))
|
||||
.route("/watch/:watch", get(get_watch))
|
||||
.route("/watch/{watch}", get(get_watch))
|
||||
.route(
|
||||
"/watch/add",
|
||||
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(
|
||||
"/quest/add",
|
||||
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()
|
||||
}
|
||||
|
||||
//-************************************************************************
|
||||
// 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)]
|
||||
pub mod test_utils;
|
||||
|
||||
|
|
21
src/login.rs
21
src/login.rs
|
@ -1,3 +1,4 @@
|
|||
use askama::Template;
|
||||
use axum::{
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Redirect, Response},
|
||||
|
@ -7,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
|||
|
||||
use crate::{
|
||||
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(
|
||||
mut auth: AuthSession,
|
||||
Form(mut login_form): Form<LoginPostForm>,
|
||||
) -> Result<impl IntoResponse, AuthError> {
|
||||
) -> Result<impl IntoResponse, WatchError> {
|
||||
let dest = login_form.destination.take();
|
||||
let user = match auth.authenticate(login_form.clone().into()).await {
|
||||
Ok(Some(user)) => user,
|
||||
Ok(None) => return Ok(LoginPage::default().into_response()),
|
||||
Err(_) => return Err(AuthErrorKind::Internal.into()),
|
||||
Ok(None) => return Ok(LoginPage::default().render().wender().into_response()),
|
||||
Err(_) => {
|
||||
let err: AuthError = AuthErrorKind::Internal.into();
|
||||
return Err(err.into());
|
||||
}
|
||||
};
|
||||
|
||||
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 {
|
||||
|
@ -73,16 +78,16 @@ pub async fn post_login(
|
|||
}
|
||||
|
||||
pub async fn get_login() -> impl IntoResponse {
|
||||
LoginPage::default()
|
||||
LoginPage::default().render().wender()
|
||||
}
|
||||
|
||||
pub async fn get_logout() -> impl IntoResponse {
|
||||
LogoutPage
|
||||
LogoutPage.render().wender()
|
||||
}
|
||||
|
||||
pub async fn post_logout(mut auth: AuthSession) -> impl IntoResponse {
|
||||
match auth.logout().await {
|
||||
Ok(_) => LogoutSuccessPage.into_response(),
|
||||
Ok(_) => LogoutSuccessPage.render().wender().into_response(),
|
||||
Err(e) => {
|
||||
tracing::debug!("{e}");
|
||||
let e: AuthError = AuthErrorKind::Internal.into();
|
||||
|
|
|
@ -7,7 +7,7 @@ use serde::{Deserialize, Serialize};
|
|||
use sqlx::SqlitePool;
|
||||
|
||||
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)]
|
||||
|
@ -60,6 +60,8 @@ pub async fn get_search_watch(
|
|||
results: watches,
|
||||
user,
|
||||
}
|
||||
.render()
|
||||
.wender()
|
||||
}
|
||||
|
||||
pub async fn get_search_star(
|
||||
|
@ -83,4 +85,6 @@ pub async fn get_search_star(
|
|||
results: watches,
|
||||
user,
|
||||
}
|
||||
.render()
|
||||
.wender()
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ use argon2::{
|
|||
password_hash::{rand_core::OsRng, PasswordHasher, SaltString},
|
||||
Argon2,
|
||||
};
|
||||
use askama::Template;
|
||||
use axum::{
|
||||
extract::{Form, Path, State},
|
||||
http::StatusCode,
|
||||
|
@ -12,48 +13,8 @@ use serde::Deserialize;
|
|||
use sqlx::{query_as, Sqlite, SqlitePool};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
|
||||
use super::{templates::*, Invitation};
|
||||
use crate::{misc_util::empty_string_as_none, User};
|
||||
|
||||
//-************************************************************************
|
||||
// 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,
|
||||
}
|
||||
use super::{templates::*, CreateUserError, CreateUserErrorKind, Invitation, SignupError};
|
||||
use crate::{misc_util::empty_string_as_none, User, WatchError, Wender};
|
||||
|
||||
#[derive(Debug, Default, Deserialize, PartialEq, Eq)]
|
||||
pub struct SignupForm {
|
||||
|
@ -76,15 +37,25 @@ pub struct SignupForm {
|
|||
pub async fn get_create_user(
|
||||
State(_pool): State<SqlitePool>,
|
||||
invitation: Option<Path<String>>,
|
||||
) -> Result<impl IntoResponse, CreateUserError> {
|
||||
let invitation = invitation.ok_or(CreateUserErrorKind::BadInvitation)?;
|
||||
let invitation =
|
||||
Julid::from_str(&invitation.0).map_err(|_| CreateUserErrorKind::BadInvitation)?;
|
||||
) -> Result<impl IntoResponse, WatchError> {
|
||||
let Path(invitation) = invitation.ok_or_else(|| {
|
||||
let e: CreateUserError = CreateUserErrorKind::BadInvitation.into();
|
||||
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 {
|
||||
invitation,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
.render()
|
||||
.wender()
|
||||
.into_response())
|
||||
}
|
||||
|
||||
/// 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(
|
||||
State(pool): State<SqlitePool>,
|
||||
Form(signup): Form<SignupForm>,
|
||||
) -> Result<impl IntoResponse, CreateUserError> {
|
||||
) -> Result<impl IntoResponse, SignupError> {
|
||||
use crate::misc_util::validate_optional_length;
|
||||
let username = signup.username.trim();
|
||||
let password = signup.password.trim();
|
||||
|
@ -156,7 +127,10 @@ pub async fn get_signup_success(
|
|||
.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() {
|
||||
// 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],
|
||||
pool: &SqlitePool,
|
||||
invitation: &str,
|
||||
) -> Result<User, CreateUserError> {
|
||||
) -> Result<User, SignupError> {
|
||||
const CREATE_QUERY: &str =
|
||||
"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| {
|
||||
tracing::debug!("db error: {e}");
|
||||
CreateUserErrorKind::UnknownDBError
|
||||
CreateUserErrorKind::DBError
|
||||
})?;
|
||||
|
||||
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 {
|
||||
CreateUserErrorKind::AlreadyExists
|
||||
} else {
|
||||
CreateUserErrorKind::UnknownDBError
|
||||
CreateUserErrorKind::DBError
|
||||
}
|
||||
}
|
||||
_ => CreateUserErrorKind::UnknownDBError,
|
||||
_ => CreateUserErrorKind::DBError,
|
||||
}
|
||||
})?;
|
||||
|
||||
tx.commit().await.map_err(|e| {
|
||||
tracing::debug!("db error: {e}");
|
||||
CreateUserErrorKind::UnknownDBError
|
||||
CreateUserErrorKind::DBError
|
||||
})?;
|
||||
|
||||
Ok(user)
|
||||
|
@ -240,7 +214,7 @@ async fn validate_invitation(
|
|||
.await
|
||||
.map_err(|e| {
|
||||
tracing::debug!("db error: {e}");
|
||||
CreateUserErrorKind::UnknownDBError
|
||||
CreateUserErrorKind::DBError
|
||||
})?
|
||||
.ok_or(CreateUserErrorKind::BadInvitation)?;
|
||||
|
||||
|
@ -263,7 +237,7 @@ async fn validate_invitation(
|
|||
.await
|
||||
.map_err(|e| {
|
||||
tracing::debug!("db error: {e}");
|
||||
CreateUserErrorKind::UnknownDBError
|
||||
CreateUserErrorKind::DBError
|
||||
})?;
|
||||
|
||||
Ok(invitation.owner)
|
||||
|
|
|
@ -1,15 +1,29 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use askama::Template;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use http::StatusCode;
|
||||
use julid::Julid;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::SqlitePool;
|
||||
use templates::{SignupErrorPage, SignupPage};
|
||||
|
||||
use crate::WatchDate;
|
||||
use crate::{WatchDate, WatchError};
|
||||
|
||||
pub mod handlers;
|
||||
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]
|
||||
#[derive(PartialEq, Eq)]
|
||||
pub struct CreateInviteError(#[from] CreateInviteErrorKind);
|
||||
|
@ -23,6 +37,26 @@ pub enum CreateInviteErrorKind {
|
|||
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)]
|
||||
pub struct Invitation {
|
||||
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)]
|
||||
mod test {
|
||||
use tokio::runtime::Runtime;
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
use askama::Template;
|
||||
use axum::{
|
||||
extract::{Form, Path, State},
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Redirect, Response},
|
||||
response::{IntoResponse, Redirect},
|
||||
};
|
||||
use http::HeaderValue;
|
||||
use julid::Julid;
|
||||
use serde::Deserialize;
|
||||
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::{
|
||||
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 = "✓";
|
||||
|
||||
//-************************************************************************
|
||||
// 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
|
||||
//-************************************************************************
|
||||
|
@ -120,7 +69,7 @@ pub struct PostEditQuest {
|
|||
//-************************************************************************
|
||||
|
||||
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)
|
||||
|
@ -128,7 +77,7 @@ pub async fn post_add_new_watch(
|
|||
auth: AuthSession,
|
||||
State(pool): State<SqlitePool>,
|
||||
Form(form): Form<PostAddNewWatch>,
|
||||
) -> Result<impl IntoResponse, AddError> {
|
||||
) -> Result<impl IntoResponse, WatchesError> {
|
||||
if let Some(user) = auth.user {
|
||||
{
|
||||
let watch = Watch {
|
||||
|
@ -147,15 +96,14 @@ pub async fn post_add_new_watch(
|
|||
watched: false,
|
||||
watch: watch_id,
|
||||
};
|
||||
add_watch_quest_impl(&pool, &quest)
|
||||
.await
|
||||
.map_err(|_| AddErrorKind::UnknownDBError)?;
|
||||
add_watch_quest_impl(&pool, &quest).await?;
|
||||
|
||||
let location = format!("/watch/{watch_id}");
|
||||
Ok(Redirect::to(&location))
|
||||
}
|
||||
} 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
|
||||
.map_err(|err| {
|
||||
tracing::error!("Got error: {err}");
|
||||
AddErrorKind::UnknownDBError
|
||||
AddErrorKind::DBError
|
||||
})?;
|
||||
Ok(watch_id)
|
||||
}
|
||||
|
@ -181,7 +129,7 @@ pub async fn post_add_watch_quest(
|
|||
auth: AuthSession,
|
||||
State(pool): State<SqlitePool>,
|
||||
Form(form): Form<PostAddExistingWatch>,
|
||||
) -> Result<impl IntoResponse, AddError> {
|
||||
) -> Result<impl IntoResponse, WatchesError> {
|
||||
if let Some(user) = auth.user {
|
||||
let quest = WatchQuest {
|
||||
user: user.id,
|
||||
|
@ -189,9 +137,8 @@ pub async fn post_add_watch_quest(
|
|||
public: form.public,
|
||||
watched: false,
|
||||
};
|
||||
add_watch_quest_impl(&pool, &quest)
|
||||
.await
|
||||
.map_err(|_| AddErrorKind::UnknownDBError)?;
|
||||
add_watch_quest_impl(&pool, &quest).await?;
|
||||
|
||||
let resp = checkmark(form.public);
|
||||
Ok(resp.into_response())
|
||||
} 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)
|
||||
.bind(quest.user)
|
||||
.bind(quest.watch)
|
||||
|
@ -213,6 +160,7 @@ pub async fn add_watch_quest_impl(pool: &SqlitePool, quest: &WatchQuest) -> Resu
|
|||
.await
|
||||
.map_err(|err| {
|
||||
tracing::error!("Got error: {err}");
|
||||
AddErrorKind::DBError
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -222,13 +170,14 @@ pub async fn edit_watch_quest(
|
|||
auth: AuthSession,
|
||||
State(pool): State<SqlitePool>,
|
||||
Form(form): Form<PostEditQuest>,
|
||||
) -> Result<impl IntoResponse, EditError> {
|
||||
) -> Result<impl IntoResponse, WatchesError> {
|
||||
if let Some(user) = auth.user {
|
||||
let watch = Julid::from_str(form.watch.trim()).map_err(|_| EditErrorKind::NotFound)?;
|
||||
Ok(
|
||||
edit_watch_quest_impl(&pool, form.act.trim(), user.id, watch)
|
||||
.await?
|
||||
.into_response(),
|
||||
.render()
|
||||
.wender(),
|
||||
)
|
||||
} else {
|
||||
Err(EditErrorKind::NotSignedIn.into())
|
||||
|
@ -240,7 +189,7 @@ async fn edit_watch_quest_impl(
|
|||
action: &str,
|
||||
user: Julid,
|
||||
watch: Julid,
|
||||
) -> Result<WatchStatusMenus, EditErrorKind> {
|
||||
) -> Result<WatchStatusMenus, EditError> {
|
||||
let quest: Option<WatchQuest> = query_as(GET_QUEST_QUERY)
|
||||
.bind(user)
|
||||
.bind(watch)
|
||||
|
@ -248,7 +197,7 @@ async fn edit_watch_quest_impl(
|
|||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Got error from checking watch status: {e:?}");
|
||||
EditErrorKind::UnknownDBError
|
||||
EditErrorKind::DBError
|
||||
})?;
|
||||
if let Some(quest) = quest {
|
||||
match action {
|
||||
|
@ -262,7 +211,7 @@ async fn edit_watch_quest_impl(
|
|||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Error removing quest: {e}");
|
||||
EditErrorKind::UnknownDBError
|
||||
EditErrorKind::DBError
|
||||
})?;
|
||||
Ok(WatchStatusMenus { watch, quest: None })
|
||||
}
|
||||
|
@ -278,7 +227,7 @@ async fn edit_watch_quest_impl(
|
|||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Error updating quest: {e}");
|
||||
EditErrorKind::UnknownDBError
|
||||
EditErrorKind::DBError
|
||||
})?;
|
||||
let quest = WatchQuest { watched, ..quest };
|
||||
Ok(WatchStatusMenus {
|
||||
|
@ -298,7 +247,7 @@ async fn edit_watch_quest_impl(
|
|||
.await
|
||||
.map_err(|e| {
|
||||
tracing::error!("Error updating quest: {e}");
|
||||
EditErrorKind::UnknownDBError
|
||||
EditErrorKind::DBError
|
||||
})?;
|
||||
let quest = WatchQuest { public, ..quest };
|
||||
Ok(WatchStatusMenus {
|
||||
|
@ -312,7 +261,7 @@ async fn edit_watch_quest_impl(
|
|||
}),
|
||||
}
|
||||
} else {
|
||||
Err(EditErrorKind::NotFound)
|
||||
Err(EditErrorKind::NotFound.into())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -336,14 +285,20 @@ pub async fn get_watch(
|
|||
watch,
|
||||
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
|
||||
pub async fn get_watches(auth: AuthSession, State(pool): State<SqlitePool>) -> impl IntoResponse {
|
||||
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)
|
||||
.bind(user.as_ref().unwrap().id)
|
||||
.bind(user.id)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
|
@ -352,6 +307,9 @@ pub async fn get_watches(auth: AuthSession, State(pool): State<SqlitePool>) -> i
|
|||
};
|
||||
|
||||
MyWatchesPage { watches, user }
|
||||
.render()
|
||||
.wender()
|
||||
.into_response()
|
||||
}
|
||||
|
||||
pub async fn get_watch_status(
|
||||
|
@ -369,7 +327,10 @@ pub async fn get_watch_status(
|
|||
.map_err(|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 {
|
||||
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 serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::WatchError;
|
||||
|
||||
pub mod handlers;
|
||||
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(
|
||||
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