update deps, fuck with errors

This commit is contained in:
Joe Ardent 2025-08-28 18:20:01 -07:00
parent 72ca947cf6
commit fc26533460
11 changed files with 788 additions and 559 deletions

780
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

@ -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();

View file

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

View file

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

View file

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

View file

@ -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 = "&#10003;";
//-************************************************************************
// 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())
}

View file

@ -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(),
}
}
}