2023-09-17 17:34:06 +02:00
|
|
|
#![allow(unused_variables)]
|
|
|
|
|
|
2023-09-16 12:55:51 +02:00
|
|
|
pub mod list;
|
|
|
|
|
pub use list::List;
|
|
|
|
|
|
2023-09-16 00:45:51 +02:00
|
|
|
use axum::{
|
2023-09-17 15:45:02 +02:00
|
|
|
body::{BoxBody, HttpBody},
|
2023-09-17 16:00:42 +02:00
|
|
|
extract::{Form, Path, State as StateExtractor},
|
2023-09-16 00:45:51 +02:00
|
|
|
http::HeaderMap,
|
|
|
|
|
response::{IntoResponse, Redirect, Response},
|
2023-09-17 17:34:06 +02:00
|
|
|
routing::post,
|
2023-09-16 00:45:51 +02:00
|
|
|
Extension,
|
|
|
|
|
};
|
2023-09-13 17:09:09 +02:00
|
|
|
use maud::{html, Markup};
|
2023-09-16 00:45:51 +02:00
|
|
|
use serde::Deserialize;
|
2023-09-13 00:44:59 +02:00
|
|
|
use uuid::Uuid;
|
|
|
|
|
|
2023-09-16 00:45:51 +02:00
|
|
|
use crate::{
|
|
|
|
|
components::{
|
2023-09-17 19:00:22 +02:00
|
|
|
self,
|
2023-09-17 16:00:42 +02:00
|
|
|
crud::{self, Read, Update},
|
2023-09-17 19:00:22 +02:00
|
|
|
route::{self, Toggle},
|
2023-09-16 12:55:51 +02:00
|
|
|
view::{self, View},
|
2023-09-16 00:45:51 +02:00
|
|
|
},
|
|
|
|
|
htmx,
|
2023-09-17 16:00:42 +02:00
|
|
|
models::{user::User, Error},
|
|
|
|
|
routing::get_referer,
|
2023-09-16 00:45:51 +02:00
|
|
|
sqlite, AppState, Context, RequestError,
|
|
|
|
|
};
|
2023-09-15 13:13:56 +02:00
|
|
|
|
|
|
|
|
use async_trait::async_trait;
|
|
|
|
|
|
2023-09-16 12:55:51 +02:00
|
|
|
use crate::models::trips::Trip;
|
2023-09-13 17:09:09 +02:00
|
|
|
|
2023-09-13 00:44:59 +02:00
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
|
|
|
pub enum State {
|
|
|
|
|
Todo,
|
|
|
|
|
Done,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<bool> for State {
|
|
|
|
|
fn from(done: bool) -> Self {
|
|
|
|
|
if done {
|
|
|
|
|
Self::Done
|
|
|
|
|
} else {
|
|
|
|
|
Self::Todo
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<State> for bool {
|
|
|
|
|
fn from(value: State) -> Self {
|
|
|
|
|
match value {
|
|
|
|
|
State::Todo => false,
|
|
|
|
|
State::Done => true,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
pub struct Todo {
|
|
|
|
|
pub id: Uuid,
|
|
|
|
|
pub description: String,
|
|
|
|
|
pub state: State,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct TodoRow {
|
|
|
|
|
id: String,
|
|
|
|
|
description: String,
|
|
|
|
|
done: bool,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl TryFrom<TodoRow> for Todo {
|
|
|
|
|
type Error = Error;
|
|
|
|
|
|
|
|
|
|
fn try_from(row: TodoRow) -> Result<Self, Self::Error> {
|
|
|
|
|
Ok(Todo {
|
|
|
|
|
id: Uuid::try_parse(&row.id)?,
|
|
|
|
|
description: row.description,
|
|
|
|
|
state: row.done.into(),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 19:24:42 +02:00
|
|
|
#[derive(Debug, Clone)]
|
2023-09-16 12:55:51 +02:00
|
|
|
pub struct Filter {
|
2023-09-15 13:13:56 +02:00
|
|
|
pub trip_id: Uuid,
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-17 19:00:22 +02:00
|
|
|
impl From<(Uuid, Uuid)> for Filter {
|
|
|
|
|
fn from((trip_id, _todo_id): (Uuid, Uuid)) -> Self {
|
|
|
|
|
Self { trip_id }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Copy, Clone)]
|
2023-09-17 22:54:59 +02:00
|
|
|
pub struct Id(Uuid);
|
2023-09-17 19:00:22 +02:00
|
|
|
|
|
|
|
|
impl std::fmt::Display for Id {
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
write!(f, "{}", self.0)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-17 22:54:59 +02:00
|
|
|
impl Id {
|
|
|
|
|
pub fn new(id: Uuid) -> Self {
|
|
|
|
|
Self(id)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-17 19:00:22 +02:00
|
|
|
impl From<(Uuid, Uuid)> for Id {
|
|
|
|
|
fn from((_trip_id, todo_id): (Uuid, Uuid)) -> Self {
|
|
|
|
|
Self(todo_id)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-13 00:44:59 +02:00
|
|
|
impl Todo {
|
|
|
|
|
pub fn is_done(&self) -> bool {
|
|
|
|
|
self.state == State::Done
|
|
|
|
|
}
|
2023-09-15 13:13:56 +02:00
|
|
|
}
|
2023-09-13 00:44:59 +02:00
|
|
|
|
2023-09-15 13:13:56 +02:00
|
|
|
#[async_trait]
|
|
|
|
|
impl crud::Read for Todo {
|
2023-09-16 12:55:51 +02:00
|
|
|
type Filter = Filter;
|
2023-09-17 19:00:22 +02:00
|
|
|
type Id = Id;
|
2023-09-15 13:13:56 +02:00
|
|
|
|
|
|
|
|
async fn findall(
|
2023-09-13 00:44:59 +02:00
|
|
|
ctx: &Context,
|
|
|
|
|
pool: &sqlite::Pool,
|
2023-09-16 12:55:51 +02:00
|
|
|
filter: Filter,
|
2023-09-13 00:44:59 +02:00
|
|
|
) -> Result<Vec<Self>, Error> {
|
2023-09-15 13:13:56 +02:00
|
|
|
let trip_id_param = filter.trip_id.to_string();
|
2023-09-13 00:44:59 +02:00
|
|
|
let user_id = ctx.user.id.to_string();
|
|
|
|
|
|
|
|
|
|
let todos: Vec<Todo> = crate::query_all!(
|
|
|
|
|
&sqlite::QueryClassification {
|
|
|
|
|
query_type: sqlite::QueryType::Select,
|
|
|
|
|
component: sqlite::Component::Todo,
|
|
|
|
|
},
|
|
|
|
|
pool,
|
|
|
|
|
TodoRow,
|
|
|
|
|
Todo,
|
|
|
|
|
r#"
|
|
|
|
|
SELECT
|
|
|
|
|
todo.id AS id,
|
|
|
|
|
todo.description AS description,
|
|
|
|
|
todo.done AS done
|
|
|
|
|
FROM trip_todos AS todo
|
|
|
|
|
INNER JOIN trips
|
|
|
|
|
ON trips.id = todo.trip_id
|
|
|
|
|
WHERE
|
|
|
|
|
trips.id = $1
|
|
|
|
|
AND trips.user_id = $2
|
|
|
|
|
"#,
|
|
|
|
|
trip_id_param,
|
|
|
|
|
user_id,
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(todos)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tracing::instrument]
|
2023-09-15 13:13:56 +02:00
|
|
|
async fn find(
|
2023-09-13 00:44:59 +02:00
|
|
|
ctx: &Context,
|
|
|
|
|
pool: &sqlite::Pool,
|
2023-09-16 12:55:51 +02:00
|
|
|
filter: Filter,
|
2023-09-17 19:00:22 +02:00
|
|
|
todo_id: Id,
|
2023-09-13 00:44:59 +02:00
|
|
|
) -> Result<Option<Self>, Error> {
|
2023-09-15 13:13:56 +02:00
|
|
|
let trip_id_param = filter.trip_id.to_string();
|
2023-09-17 19:00:22 +02:00
|
|
|
let todo_id_param = todo_id.0.to_string();
|
2023-09-13 00:44:59 +02:00
|
|
|
let user_id = ctx.user.id.to_string();
|
|
|
|
|
crate::query_one!(
|
|
|
|
|
&sqlite::QueryClassification {
|
|
|
|
|
query_type: sqlite::QueryType::Select,
|
|
|
|
|
component: sqlite::Component::Todo,
|
|
|
|
|
},
|
|
|
|
|
pool,
|
|
|
|
|
TodoRow,
|
|
|
|
|
Self,
|
|
|
|
|
r#"
|
|
|
|
|
SELECT
|
|
|
|
|
todo.id AS id,
|
|
|
|
|
todo.description AS description,
|
|
|
|
|
todo.done AS done
|
|
|
|
|
FROM trip_todos AS todo
|
|
|
|
|
INNER JOIN trips
|
|
|
|
|
ON trips.id = todo.trip_id
|
|
|
|
|
WHERE
|
|
|
|
|
trips.id = $1
|
|
|
|
|
AND todo.id = $2
|
|
|
|
|
AND trips.user_id = $3
|
|
|
|
|
"#,
|
|
|
|
|
trip_id_param,
|
|
|
|
|
todo_id_param,
|
|
|
|
|
user_id,
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
}
|
2023-09-15 13:13:56 +02:00
|
|
|
}
|
2023-09-13 00:44:59 +02:00
|
|
|
|
2023-09-15 13:13:56 +02:00
|
|
|
pub struct TodoNew {
|
|
|
|
|
pub description: String,
|
|
|
|
|
}
|
2023-09-13 00:44:59 +02:00
|
|
|
|
2023-09-15 13:13:56 +02:00
|
|
|
#[async_trait]
|
|
|
|
|
impl crud::Create for Todo {
|
2023-09-17 19:00:22 +02:00
|
|
|
type Id = Id;
|
2023-09-16 12:55:51 +02:00
|
|
|
type Filter = Filter;
|
2023-09-15 13:13:56 +02:00
|
|
|
type Info = TodoNew;
|
2023-09-13 17:09:09 +02:00
|
|
|
|
2023-09-15 13:13:56 +02:00
|
|
|
async fn create(
|
2023-09-13 17:09:09 +02:00
|
|
|
ctx: &Context,
|
|
|
|
|
pool: &sqlite::Pool,
|
2023-09-15 13:13:56 +02:00
|
|
|
filter: Self::Filter,
|
|
|
|
|
info: Self::Info,
|
|
|
|
|
) -> Result<Self::Id, Error> {
|
2023-09-13 17:09:09 +02:00
|
|
|
let user_id = ctx.user.id.to_string();
|
|
|
|
|
let id = Uuid::new_v4();
|
|
|
|
|
tracing::info!("adding new todo with id {id}");
|
|
|
|
|
let id_param = id.to_string();
|
2023-09-15 13:13:56 +02:00
|
|
|
let trip_id_param = filter.trip_id.to_string();
|
2023-09-13 17:09:09 +02:00
|
|
|
crate::execute!(
|
|
|
|
|
&sqlite::QueryClassification {
|
|
|
|
|
query_type: sqlite::QueryType::Insert,
|
|
|
|
|
component: sqlite::Component::Todo,
|
|
|
|
|
},
|
|
|
|
|
pool,
|
2023-09-15 13:13:56 +02:00
|
|
|
r#"
|
|
|
|
|
INSERT INTO trip_todos
|
|
|
|
|
(id, description, done, trip_id)
|
|
|
|
|
SELECT ?, ?, false, id as trip_id
|
|
|
|
|
FROM trips
|
|
|
|
|
WHERE trip_id = ? AND EXISTS(SELECT 1 FROM trips WHERE id = ? and user_id = ?)
|
|
|
|
|
LIMIT 1
|
|
|
|
|
"#,
|
2023-09-13 17:09:09 +02:00
|
|
|
id_param,
|
2023-09-15 13:13:56 +02:00
|
|
|
info.description,
|
2023-09-13 17:09:09 +02:00
|
|
|
trip_id_param,
|
|
|
|
|
trip_id_param,
|
|
|
|
|
user_id,
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
2023-09-17 19:00:22 +02:00
|
|
|
Ok(components::trips::todos::Id(id))
|
2023-09-13 17:09:09 +02:00
|
|
|
}
|
2023-09-15 13:13:56 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-17 17:34:06 +02:00
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
|
|
|
pub struct StateUpdate {
|
|
|
|
|
new_state: State,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<bool> for StateUpdate {
|
|
|
|
|
fn from(state: bool) -> Self {
|
|
|
|
|
Self {
|
|
|
|
|
new_state: state.into(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl From<State> for StateUpdate {
|
|
|
|
|
fn from(new_state: State) -> Self {
|
|
|
|
|
Self { new_state }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
|
|
|
pub struct DescriptionUpdate(String);
|
|
|
|
|
|
|
|
|
|
impl From<String> for DescriptionUpdate {
|
|
|
|
|
fn from(new_description: String) -> Self {
|
|
|
|
|
Self(new_description)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 13:13:56 +02:00
|
|
|
#[derive(Debug)]
|
2023-09-17 15:45:02 +02:00
|
|
|
pub enum UpdateElement {
|
2023-09-17 17:34:06 +02:00
|
|
|
State(StateUpdate),
|
|
|
|
|
Description(DescriptionUpdate),
|
2023-09-15 13:13:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
|
impl crud::Update for Todo {
|
2023-09-17 19:00:22 +02:00
|
|
|
type Id = Id;
|
2023-09-16 12:55:51 +02:00
|
|
|
type Filter = Filter;
|
2023-09-17 15:45:02 +02:00
|
|
|
type UpdateElement = UpdateElement;
|
2023-09-15 13:13:56 +02:00
|
|
|
|
|
|
|
|
#[tracing::instrument]
|
|
|
|
|
async fn update(
|
|
|
|
|
ctx: &Context,
|
|
|
|
|
pool: &sqlite::Pool,
|
|
|
|
|
filter: Self::Filter,
|
|
|
|
|
id: Self::Id,
|
2023-09-17 15:45:02 +02:00
|
|
|
update_element: Self::UpdateElement,
|
2023-09-15 13:13:56 +02:00
|
|
|
) -> Result<Option<Self>, Error> {
|
|
|
|
|
let user_id = ctx.user.id.to_string();
|
|
|
|
|
let trip_id_param = filter.trip_id.to_string();
|
|
|
|
|
let todo_id_param = id.to_string();
|
2023-09-17 15:45:02 +02:00
|
|
|
match update_element {
|
|
|
|
|
UpdateElement::State(state) => {
|
2023-09-17 17:34:06 +02:00
|
|
|
let done = state == State::Done.into();
|
2023-09-15 13:13:56 +02:00
|
|
|
|
|
|
|
|
let result = crate::query_one!(
|
|
|
|
|
&sqlite::QueryClassification {
|
|
|
|
|
query_type: sqlite::QueryType::Update,
|
|
|
|
|
component: sqlite::Component::Trips,
|
|
|
|
|
},
|
|
|
|
|
pool,
|
|
|
|
|
TodoRow,
|
|
|
|
|
Todo,
|
|
|
|
|
r#"
|
|
|
|
|
UPDATE trip_todos
|
|
|
|
|
SET done = ?
|
|
|
|
|
WHERE trip_id = ?
|
|
|
|
|
AND id = ?
|
|
|
|
|
AND EXISTS(SELECT 1 FROM trips WHERE id = ? AND user_id = ?)
|
|
|
|
|
RETURNING
|
|
|
|
|
id,
|
|
|
|
|
description,
|
|
|
|
|
done
|
|
|
|
|
"#,
|
|
|
|
|
done,
|
|
|
|
|
trip_id_param,
|
|
|
|
|
todo_id_param,
|
|
|
|
|
trip_id_param,
|
|
|
|
|
user_id
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(result)
|
|
|
|
|
}
|
2023-09-17 15:45:02 +02:00
|
|
|
UpdateElement::Description(new_description) => {
|
2023-09-15 13:13:56 +02:00
|
|
|
let user_id = ctx.user.id.to_string();
|
|
|
|
|
let trip_id_param = filter.trip_id.to_string();
|
|
|
|
|
let todo_id_param = id.to_string();
|
|
|
|
|
|
|
|
|
|
let result = crate::query_one!(
|
|
|
|
|
&sqlite::QueryClassification {
|
|
|
|
|
query_type: sqlite::QueryType::Update,
|
|
|
|
|
component: sqlite::Component::Todo,
|
|
|
|
|
},
|
|
|
|
|
pool,
|
|
|
|
|
TodoRow,
|
|
|
|
|
Todo,
|
|
|
|
|
r#"
|
|
|
|
|
UPDATE trip_todos
|
|
|
|
|
SET description = ?
|
|
|
|
|
WHERE
|
|
|
|
|
id = ?
|
|
|
|
|
AND trip_id = ?
|
|
|
|
|
AND EXISTS(SELECT 1 FROM trips WHERE trip_id = ? AND user_id = ?)
|
|
|
|
|
RETURNING
|
|
|
|
|
id,
|
|
|
|
|
description,
|
|
|
|
|
done
|
|
|
|
|
"#,
|
2023-09-17 17:34:06 +02:00
|
|
|
new_description.0,
|
2023-09-15 13:13:56 +02:00
|
|
|
todo_id_param,
|
|
|
|
|
trip_id_param,
|
|
|
|
|
trip_id_param,
|
|
|
|
|
user_id,
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(result)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[async_trait]
|
2023-09-15 19:24:42 +02:00
|
|
|
impl crud::Delete for Todo {
|
2023-09-17 19:00:22 +02:00
|
|
|
type Id = Id;
|
2023-09-16 12:55:51 +02:00
|
|
|
type Filter = Filter;
|
2023-09-13 17:09:09 +02:00
|
|
|
|
|
|
|
|
#[tracing::instrument]
|
2023-09-17 19:00:22 +02:00
|
|
|
async fn delete<'c, T>(ctx: &Context, db: T, filter: &Filter, id: Id) -> Result<bool, Error>
|
2023-09-15 18:30:19 +02:00
|
|
|
where
|
|
|
|
|
T: sqlx::Acquire<'c, Database = sqlx::Sqlite> + Send + std::fmt::Debug,
|
|
|
|
|
{
|
2023-09-17 19:00:22 +02:00
|
|
|
let id_param = id.0.to_string();
|
2023-09-13 17:09:09 +02:00
|
|
|
let user_id = ctx.user.id.to_string();
|
2023-09-15 13:13:56 +02:00
|
|
|
let trip_id_param = filter.trip_id.to_string();
|
2023-09-15 18:30:19 +02:00
|
|
|
|
2023-09-13 17:09:09 +02:00
|
|
|
let results = crate::execute!(
|
|
|
|
|
&sqlite::QueryClassification {
|
|
|
|
|
query_type: sqlite::QueryType::Delete,
|
|
|
|
|
component: sqlite::Component::Todo,
|
|
|
|
|
},
|
2023-09-15 19:24:42 +02:00
|
|
|
&mut *(db.acquire().await?),
|
2023-09-15 13:13:56 +02:00
|
|
|
r#"
|
|
|
|
|
DELETE FROM trip_todos
|
2023-09-15 18:30:19 +02:00
|
|
|
WHERE
|
2023-09-15 13:13:56 +02:00
|
|
|
id = ?
|
|
|
|
|
AND EXISTS (SELECT 1 FROM trips WHERE trip_id = ? AND user_id = ?)
|
|
|
|
|
"#,
|
2023-09-13 17:09:09 +02:00
|
|
|
id_param,
|
|
|
|
|
trip_id_param,
|
|
|
|
|
user_id,
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(results.rows_affected() != 0)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, Eq)]
|
2023-09-16 12:55:51 +02:00
|
|
|
pub enum UiState {
|
2023-09-13 17:09:09 +02:00
|
|
|
Default,
|
|
|
|
|
Edit,
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-15 13:13:56 +02:00
|
|
|
#[derive(Debug)]
|
2023-09-16 12:55:51 +02:00
|
|
|
pub struct BuildInput {
|
2023-09-15 13:13:56 +02:00
|
|
|
pub trip_id: Uuid,
|
2023-09-16 12:55:51 +02:00
|
|
|
pub state: UiState,
|
2023-09-15 13:13:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl view::View for Todo {
|
2023-09-16 12:55:51 +02:00
|
|
|
type Input = BuildInput;
|
2023-09-15 13:13:56 +02:00
|
|
|
|
2023-09-13 17:09:09 +02:00
|
|
|
#[tracing::instrument]
|
2023-09-15 13:13:56 +02:00
|
|
|
fn build(&self, input: Self::Input) -> Markup {
|
2023-09-13 17:09:09 +02:00
|
|
|
let done = self.is_done();
|
|
|
|
|
html!(
|
|
|
|
|
li
|
|
|
|
|
."flex"
|
|
|
|
|
."flex-row"
|
|
|
|
|
."justify-start"
|
|
|
|
|
."items-stretch"
|
|
|
|
|
."bg-green-50"[done]
|
|
|
|
|
."bg-red-50"[!done]
|
|
|
|
|
."h-full"
|
|
|
|
|
{
|
2023-09-16 12:55:51 +02:00
|
|
|
@if input.state == UiState::Edit {
|
2023-09-13 17:09:09 +02:00
|
|
|
form
|
|
|
|
|
name="edit-todo"
|
|
|
|
|
id="edit-todo"
|
|
|
|
|
action={
|
2023-09-15 13:13:56 +02:00
|
|
|
"/trips/" (input.trip_id)
|
2023-09-13 17:09:09 +02:00
|
|
|
"/todo/" (self.id)
|
|
|
|
|
"/edit/save"
|
|
|
|
|
}
|
|
|
|
|
target="_self"
|
|
|
|
|
method="post"
|
|
|
|
|
hx-post={
|
2023-09-15 13:13:56 +02:00
|
|
|
"/trips/" (input.trip_id)
|
2023-09-13 17:09:09 +02:00
|
|
|
"/todo/" (self.id)
|
|
|
|
|
"/edit/save"
|
|
|
|
|
}
|
|
|
|
|
hx-target="closest li"
|
|
|
|
|
hx-swap="outerHTML"
|
|
|
|
|
{}
|
|
|
|
|
div
|
|
|
|
|
."flex"
|
|
|
|
|
."flex-row"
|
|
|
|
|
."aspect-square"
|
|
|
|
|
{
|
|
|
|
|
span
|
|
|
|
|
."mdi"
|
|
|
|
|
."m-auto"
|
|
|
|
|
."text-xl"
|
|
|
|
|
."mdi-check"[self.is_done()]
|
|
|
|
|
."mdi-checkbox-blank-outline"[!self.is_done()]
|
|
|
|
|
{}
|
|
|
|
|
}
|
|
|
|
|
div
|
|
|
|
|
."p-2"
|
|
|
|
|
.grow
|
|
|
|
|
{
|
|
|
|
|
input
|
|
|
|
|
."w-full"
|
|
|
|
|
type="text"
|
|
|
|
|
form="edit-todo"
|
|
|
|
|
id="todo-description"
|
|
|
|
|
name="todo-description"
|
|
|
|
|
value=(self.description)
|
|
|
|
|
{}
|
|
|
|
|
}
|
|
|
|
|
button
|
|
|
|
|
type="submit"
|
|
|
|
|
form="edit-todo"
|
|
|
|
|
."bg-green-200"
|
|
|
|
|
."hover:bg-green-300"
|
|
|
|
|
."flex"
|
|
|
|
|
."flex-row"
|
|
|
|
|
."aspect-square"
|
|
|
|
|
{
|
|
|
|
|
span
|
|
|
|
|
."mdi"
|
|
|
|
|
."m-auto"
|
|
|
|
|
."mdi-content-save"
|
|
|
|
|
."text-xl"
|
|
|
|
|
{}
|
|
|
|
|
}
|
|
|
|
|
a
|
|
|
|
|
href="."
|
|
|
|
|
hx-post={
|
2023-09-15 13:13:56 +02:00
|
|
|
"/trips/" (input.trip_id)
|
2023-09-13 17:09:09 +02:00
|
|
|
"/todo/" (self.id)
|
|
|
|
|
"/edit/cancel"
|
|
|
|
|
}
|
|
|
|
|
hx-target="closest li"
|
|
|
|
|
hx-swap="outerHTML"
|
|
|
|
|
."flex"
|
|
|
|
|
."flex-row"
|
|
|
|
|
."aspect-square"
|
|
|
|
|
."bg-red-200"
|
|
|
|
|
."hover:bg-red-300"
|
|
|
|
|
{
|
|
|
|
|
span
|
|
|
|
|
."mdi"
|
|
|
|
|
."mdi-cancel"
|
|
|
|
|
."text-xl"
|
|
|
|
|
."m-auto"
|
|
|
|
|
{}
|
|
|
|
|
}
|
|
|
|
|
} @else {
|
|
|
|
|
@if done {
|
|
|
|
|
a
|
|
|
|
|
."flex"
|
|
|
|
|
."flex-row"
|
|
|
|
|
."aspect-square"
|
|
|
|
|
."hover:bg-red-50"
|
|
|
|
|
href={
|
2023-09-15 13:13:56 +02:00
|
|
|
"/trips/" (input.trip_id)
|
2023-09-13 17:09:09 +02:00
|
|
|
"/todo/" (self.id)
|
2023-09-17 17:34:06 +02:00
|
|
|
"/done/false"
|
2023-09-13 17:09:09 +02:00
|
|
|
}
|
|
|
|
|
hx-post={
|
2023-09-15 13:13:56 +02:00
|
|
|
"/trips/" (input.trip_id)
|
2023-09-13 17:09:09 +02:00
|
|
|
"/todo/" (self.id)
|
2023-09-17 17:34:06 +02:00
|
|
|
"/done/htmx/false"
|
2023-09-13 17:09:09 +02:00
|
|
|
}
|
|
|
|
|
hx-target="closest li"
|
|
|
|
|
hx-swap="outerHTML"
|
|
|
|
|
{
|
|
|
|
|
span
|
|
|
|
|
."mdi"
|
|
|
|
|
."m-auto"
|
|
|
|
|
."text-xl"
|
|
|
|
|
."mdi-check"
|
|
|
|
|
{}
|
|
|
|
|
}
|
|
|
|
|
} @else {
|
|
|
|
|
a
|
|
|
|
|
."flex"
|
|
|
|
|
."flex-row"
|
|
|
|
|
."aspect-square"
|
|
|
|
|
."hover:bg-green-50"
|
|
|
|
|
href={
|
2023-09-15 13:13:56 +02:00
|
|
|
"/trips/" (input.trip_id)
|
2023-09-13 17:09:09 +02:00
|
|
|
"/todo/" (self.id)
|
2023-09-17 17:34:06 +02:00
|
|
|
"/done/true"
|
2023-09-13 17:09:09 +02:00
|
|
|
}
|
|
|
|
|
hx-post={
|
2023-09-15 13:13:56 +02:00
|
|
|
"/trips/" (input.trip_id)
|
2023-09-13 17:09:09 +02:00
|
|
|
"/todo/" (self.id)
|
2023-09-17 17:34:06 +02:00
|
|
|
"/done/htmx/true"
|
2023-09-13 17:09:09 +02:00
|
|
|
}
|
|
|
|
|
hx-target="closest li"
|
|
|
|
|
hx-swap="outerHTML"
|
|
|
|
|
{
|
|
|
|
|
span
|
|
|
|
|
."mdi"
|
|
|
|
|
."m-auto"
|
|
|
|
|
."text-xl"
|
|
|
|
|
."mdi-checkbox-blank-outline"
|
|
|
|
|
{}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
span
|
|
|
|
|
."p-2"
|
|
|
|
|
."grow"
|
|
|
|
|
{
|
|
|
|
|
(self.description)
|
|
|
|
|
}
|
|
|
|
|
a
|
|
|
|
|
."flex"
|
|
|
|
|
."flex-row"
|
|
|
|
|
."aspect-square"
|
|
|
|
|
."bg-blue-200"
|
|
|
|
|
."hover:bg-blue-400"
|
|
|
|
|
href=(format!("?edit_todo={id}", id = self.id))
|
|
|
|
|
hx-post={
|
2023-09-15 13:13:56 +02:00
|
|
|
"/trips/" (input.trip_id)
|
2023-09-13 17:09:09 +02:00
|
|
|
"/todo/" (self.id)
|
|
|
|
|
"/edit"
|
|
|
|
|
}
|
|
|
|
|
hx-target="closest li"
|
|
|
|
|
hx-swap="outerHTML"
|
|
|
|
|
{
|
|
|
|
|
span ."m-auto" ."mdi" ."mdi-pencil" ."text-xl" {}
|
|
|
|
|
}
|
|
|
|
|
a
|
|
|
|
|
."flex"
|
|
|
|
|
."flex-row"
|
|
|
|
|
."aspect-square"
|
|
|
|
|
."bg-red-100"
|
|
|
|
|
."hover:bg-red-200"
|
|
|
|
|
href=(format!("?delete_todo={id}", id = self.id))
|
|
|
|
|
hx-post={
|
2023-09-15 13:13:56 +02:00
|
|
|
"/trips/" (input.trip_id)
|
2023-09-13 17:09:09 +02:00
|
|
|
"/todo/" (self.id)
|
|
|
|
|
"/delete"
|
|
|
|
|
}
|
|
|
|
|
hx-target="#todolist"
|
|
|
|
|
hx-swap="outerHTML"
|
|
|
|
|
{
|
|
|
|
|
span ."m-auto" ."mdi" ."mdi-delete-outline" ."text-xl" {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-16 00:45:51 +02:00
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
|
pub struct TripTodoNew {
|
|
|
|
|
#[serde(rename = "new-todo-description")]
|
|
|
|
|
description: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
|
impl route::Create for Todo {
|
2023-09-16 10:28:50 +02:00
|
|
|
type Form = TripTodoNew;
|
2023-09-17 15:45:02 +02:00
|
|
|
type UrlParams = (Uuid,);
|
2023-09-16 00:45:51 +02:00
|
|
|
|
2023-09-17 15:45:02 +02:00
|
|
|
const URL: &'static str = "/:id/todo/new";
|
2023-09-16 00:45:51 +02:00
|
|
|
|
|
|
|
|
#[tracing::instrument]
|
|
|
|
|
async fn create(
|
2023-09-17 16:00:42 +02:00
|
|
|
Extension(current_user): Extension<User>,
|
|
|
|
|
StateExtractor(state): StateExtractor<AppState>,
|
2023-09-16 00:45:51 +02:00
|
|
|
headers: HeaderMap,
|
2023-09-17 15:45:02 +02:00
|
|
|
Path((trip_id,)): Path<Self::UrlParams>,
|
2023-09-16 10:28:50 +02:00
|
|
|
Form(form): Form<Self::Form>,
|
2023-09-16 00:45:51 +02:00
|
|
|
) -> Result<Response<BoxBody>, crate::Error> {
|
|
|
|
|
let ctx = Context::build(current_user);
|
|
|
|
|
// method output is not required as we reload the whole trip todos anyway
|
|
|
|
|
let _todo_item = <Self as crud::Create>::create(
|
|
|
|
|
&ctx,
|
|
|
|
|
&state.database_pool,
|
2023-09-16 12:55:51 +02:00
|
|
|
Filter { trip_id },
|
2023-09-16 00:45:51 +02:00
|
|
|
TodoNew {
|
|
|
|
|
description: form.description,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
if htmx::is_htmx(&headers) {
|
|
|
|
|
let trip = Trip::find(&ctx, &state.database_pool, trip_id).await?;
|
|
|
|
|
match trip {
|
|
|
|
|
None => Err(crate::Error::Request(RequestError::NotFound {
|
|
|
|
|
message: format!("trip with id {trip_id} not found"),
|
|
|
|
|
})),
|
|
|
|
|
Some(mut trip) => {
|
|
|
|
|
trip.load_todos(&ctx, &state.database_pool).await?;
|
2023-09-16 12:55:51 +02:00
|
|
|
Ok(list::List {
|
2023-09-16 00:45:51 +02:00
|
|
|
trip: &trip,
|
2023-09-16 12:56:42 +02:00
|
|
|
todos: trip.todos(),
|
2023-09-16 00:45:51 +02:00
|
|
|
}
|
2023-09-16 12:55:51 +02:00
|
|
|
.build(list::BuildInput { edit_todo: None })
|
2023-09-16 00:45:51 +02:00
|
|
|
.into_response())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Ok(Redirect::to(&format!("/trips/{trip_id}/")).into_response())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-16 10:28:50 +02:00
|
|
|
#[async_trait]
|
|
|
|
|
impl route::Delete for Todo {
|
2023-09-17 15:45:02 +02:00
|
|
|
type UrlParams = (Uuid, Uuid);
|
2023-09-16 10:28:50 +02:00
|
|
|
|
2023-09-17 15:45:02 +02:00
|
|
|
const URL: &'static str = "/:id/todo/:id/delete";
|
2023-09-16 10:28:50 +02:00
|
|
|
|
|
|
|
|
#[tracing::instrument]
|
|
|
|
|
async fn delete(
|
2023-09-17 16:00:42 +02:00
|
|
|
Extension(current_user): Extension<User>,
|
|
|
|
|
StateExtractor(state): StateExtractor<AppState>,
|
2023-09-16 10:28:50 +02:00
|
|
|
_headers: HeaderMap,
|
2023-09-17 15:45:02 +02:00
|
|
|
Path((trip_id, todo_id)): Path<Self::UrlParams>,
|
2023-09-16 10:28:50 +02:00
|
|
|
) -> Result<Response<BoxBody>, crate::Error> {
|
|
|
|
|
let ctx = Context::build(current_user);
|
|
|
|
|
let deleted = <Self as crud::Delete>::delete(
|
|
|
|
|
&ctx,
|
|
|
|
|
&state.database_pool,
|
2023-09-16 12:55:51 +02:00
|
|
|
&Filter { trip_id },
|
2023-09-17 19:00:22 +02:00
|
|
|
components::trips::todos::Id(todo_id),
|
2023-09-16 10:28:50 +02:00
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
if !deleted {
|
|
|
|
|
return Err(crate::Error::Request(RequestError::NotFound {
|
|
|
|
|
message: format!("todo with id {todo_id} not found"),
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let trip = crate::models::trips::Trip::find(&ctx, &state.database_pool, trip_id).await?;
|
|
|
|
|
match trip {
|
|
|
|
|
None => Err(crate::Error::Request(RequestError::NotFound {
|
|
|
|
|
message: format!("trip with id {trip_id} not found"),
|
|
|
|
|
})),
|
|
|
|
|
Some(mut trip) => {
|
|
|
|
|
trip.load_todos(&ctx, &state.database_pool).await?;
|
2023-09-16 12:55:51 +02:00
|
|
|
Ok(list::List {
|
2023-09-16 10:28:50 +02:00
|
|
|
trip: &trip,
|
2023-09-16 12:56:42 +02:00
|
|
|
todos: trip.todos(),
|
2023-09-16 10:28:50 +02:00
|
|
|
}
|
2023-09-16 12:55:51 +02:00
|
|
|
.build(list::BuildInput { edit_todo: None })
|
2023-09-16 10:28:50 +02:00
|
|
|
.into_response())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-09-17 15:45:02 +02:00
|
|
|
|
|
|
|
|
impl route::Router for Todo {
|
2023-09-17 19:00:22 +02:00
|
|
|
fn router<B>() -> axum::Router<AppState, B>
|
2023-09-17 15:45:02 +02:00
|
|
|
where
|
|
|
|
|
B: HttpBody + Send + 'static,
|
|
|
|
|
<B as HttpBody>::Data: Send,
|
|
|
|
|
<B as HttpBody>::Error: std::error::Error + Sync + Send,
|
|
|
|
|
{
|
|
|
|
|
axum::Router::new()
|
|
|
|
|
.route("/new", axum::routing::post(<Self as route::Create>::create))
|
|
|
|
|
.route(
|
|
|
|
|
"/:id/delete",
|
|
|
|
|
axum::routing::post(<Self as route::Delete>::delete),
|
|
|
|
|
)
|
2023-09-17 19:00:22 +02:00
|
|
|
.merge(StateUpdate::router())
|
2023-09-17 15:45:02 +02:00
|
|
|
}
|
|
|
|
|
}
|
2023-09-17 16:00:42 +02:00
|
|
|
|
|
|
|
|
#[tracing::instrument]
|
|
|
|
|
pub async fn trip_todo_done(
|
|
|
|
|
Extension(current_user): Extension<User>,
|
|
|
|
|
StateExtractor(state): StateExtractor<AppState>,
|
|
|
|
|
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
|
|
|
|
|
headers: HeaderMap,
|
|
|
|
|
) -> Result<impl IntoResponse, crate::Error> {
|
|
|
|
|
let ctx = Context::build(current_user);
|
|
|
|
|
Todo::update(
|
|
|
|
|
&ctx,
|
|
|
|
|
&state.database_pool,
|
|
|
|
|
Filter { trip_id },
|
2023-09-17 19:00:22 +02:00
|
|
|
Id(todo_id),
|
2023-09-17 17:34:06 +02:00
|
|
|
UpdateElement::State(State::Done.into()),
|
2023-09-17 16:00:42 +02:00
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(Redirect::to(get_referer(&headers)?))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tracing::instrument]
|
|
|
|
|
pub async fn trip_todo_undone_htmx(
|
|
|
|
|
Extension(current_user): Extension<User>,
|
|
|
|
|
StateExtractor(state): StateExtractor<AppState>,
|
|
|
|
|
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
|
|
|
|
|
) -> Result<impl IntoResponse, crate::Error> {
|
|
|
|
|
let ctx = Context::build(current_user);
|
|
|
|
|
Todo::update(
|
|
|
|
|
&ctx,
|
|
|
|
|
&state.database_pool,
|
|
|
|
|
Filter { trip_id },
|
2023-09-17 19:00:22 +02:00
|
|
|
Id(todo_id),
|
2023-09-17 17:34:06 +02:00
|
|
|
UpdateElement::State(State::Todo.into()),
|
2023-09-17 16:00:42 +02:00
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
2023-09-17 19:00:22 +02:00
|
|
|
let todo_item = Todo::find(&ctx, &state.database_pool, Filter { trip_id }, Id(todo_id))
|
2023-09-17 16:00:42 +02:00
|
|
|
.await?
|
|
|
|
|
.ok_or_else(|| {
|
|
|
|
|
crate::Error::Request(RequestError::NotFound {
|
|
|
|
|
message: format!("todo with id {todo_id} not found"),
|
|
|
|
|
})
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
Ok(todo_item.build(BuildInput {
|
|
|
|
|
trip_id,
|
|
|
|
|
state: UiState::Default,
|
|
|
|
|
}))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tracing::instrument]
|
|
|
|
|
pub async fn trip_todo_undone(
|
|
|
|
|
Extension(current_user): Extension<User>,
|
|
|
|
|
StateExtractor(state): StateExtractor<AppState>,
|
|
|
|
|
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
|
|
|
|
|
headers: HeaderMap,
|
|
|
|
|
) -> Result<impl IntoResponse, crate::Error> {
|
|
|
|
|
let ctx = Context::build(current_user);
|
|
|
|
|
Todo::update(
|
|
|
|
|
&ctx,
|
|
|
|
|
&state.database_pool,
|
|
|
|
|
Filter { trip_id },
|
2023-09-17 19:00:22 +02:00
|
|
|
Id(todo_id),
|
2023-09-17 17:34:06 +02:00
|
|
|
UpdateElement::State(State::Todo.into()),
|
2023-09-17 16:00:42 +02:00
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(Redirect::to(get_referer(&headers)?))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
|
#[serde(deny_unknown_fields)]
|
|
|
|
|
pub struct TripTodoDescription {
|
|
|
|
|
#[serde(rename = "todo-description")]
|
|
|
|
|
description: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tracing::instrument]
|
|
|
|
|
pub async fn trip_todo_edit(
|
|
|
|
|
Extension(current_user): Extension<User>,
|
|
|
|
|
StateExtractor(state): StateExtractor<AppState>,
|
|
|
|
|
headers: HeaderMap,
|
|
|
|
|
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
|
|
|
|
|
) -> Result<impl IntoResponse, crate::Error> {
|
|
|
|
|
let ctx = Context::build(current_user);
|
2023-09-17 19:00:22 +02:00
|
|
|
let todo_item = Todo::find(&ctx, &state.database_pool, Filter { trip_id }, Id(todo_id)).await?;
|
2023-09-17 16:00:42 +02:00
|
|
|
|
|
|
|
|
match todo_item {
|
|
|
|
|
None => Err(crate::Error::Request(RequestError::NotFound {
|
|
|
|
|
message: format!("todo with id {todo_id} not found"),
|
|
|
|
|
})),
|
|
|
|
|
Some(todo_item) => Ok(todo_item
|
|
|
|
|
.build(BuildInput {
|
|
|
|
|
trip_id,
|
|
|
|
|
state: UiState::Edit,
|
|
|
|
|
})
|
|
|
|
|
.into_response()),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tracing::instrument]
|
|
|
|
|
pub async fn trip_todo_edit_save(
|
|
|
|
|
Extension(current_user): Extension<User>,
|
|
|
|
|
StateExtractor(state): StateExtractor<AppState>,
|
|
|
|
|
headers: HeaderMap,
|
|
|
|
|
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
|
|
|
|
|
Form(form): Form<TripTodoDescription>,
|
|
|
|
|
) -> Result<impl IntoResponse, crate::Error> {
|
|
|
|
|
let ctx = Context::build(current_user);
|
|
|
|
|
let todo_item = Todo::update(
|
|
|
|
|
&ctx,
|
|
|
|
|
&state.database_pool,
|
|
|
|
|
Filter { trip_id },
|
2023-09-17 19:00:22 +02:00
|
|
|
Id(todo_id),
|
2023-09-17 17:34:06 +02:00
|
|
|
UpdateElement::Description(form.description.into()),
|
2023-09-17 16:00:42 +02:00
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
match todo_item {
|
|
|
|
|
None => Err(crate::Error::Request(RequestError::NotFound {
|
|
|
|
|
message: format!("todo with id {todo_id} not found"),
|
|
|
|
|
})),
|
|
|
|
|
Some(todo_item) => {
|
|
|
|
|
if htmx::is_htmx(&headers) {
|
|
|
|
|
Ok(todo_item
|
|
|
|
|
.build(BuildInput {
|
|
|
|
|
trip_id,
|
|
|
|
|
state: UiState::Default,
|
|
|
|
|
})
|
|
|
|
|
.into_response())
|
|
|
|
|
} else {
|
|
|
|
|
Ok(Redirect::to(&format!("/trips/{trip_id}/")).into_response())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tracing::instrument]
|
|
|
|
|
pub async fn trip_todo_edit_cancel(
|
|
|
|
|
Extension(current_user): Extension<User>,
|
|
|
|
|
StateExtractor(state): StateExtractor<AppState>,
|
|
|
|
|
headers: HeaderMap,
|
|
|
|
|
Path((trip_id, todo_id)): Path<(Uuid, Uuid)>,
|
|
|
|
|
) -> Result<impl IntoResponse, crate::Error> {
|
|
|
|
|
let ctx = Context::build(current_user);
|
2023-09-17 19:00:22 +02:00
|
|
|
let todo_item = Todo::find(&ctx, &state.database_pool, Filter { trip_id }, Id(todo_id)).await?;
|
2023-09-17 16:00:42 +02:00
|
|
|
|
|
|
|
|
match todo_item {
|
|
|
|
|
None => Err(crate::Error::Request(RequestError::NotFound {
|
|
|
|
|
message: format!("todo with id {todo_id} not found"),
|
|
|
|
|
})),
|
|
|
|
|
Some(todo_item) => Ok(todo_item
|
|
|
|
|
.build(BuildInput {
|
|
|
|
|
trip_id,
|
|
|
|
|
state: UiState::Default,
|
|
|
|
|
})
|
|
|
|
|
.into_response()),
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-09-17 17:34:06 +02:00
|
|
|
|
2023-09-17 19:00:22 +02:00
|
|
|
#[async_trait]
|
|
|
|
|
impl crud::Toggle for StateUpdate {
|
|
|
|
|
type Id = Id;
|
|
|
|
|
type Filter = Filter;
|
|
|
|
|
|
|
|
|
|
async fn set(
|
|
|
|
|
ctx: &Context,
|
|
|
|
|
pool: &sqlite::Pool,
|
|
|
|
|
filter: Self::Filter,
|
|
|
|
|
id: Self::Id,
|
|
|
|
|
value: bool,
|
|
|
|
|
) -> Result<(), crate::Error> {
|
2023-09-17 22:54:59 +02:00
|
|
|
Todo::update(&ctx, &pool, filter, id, UpdateElement::State(value.into())).await?;
|
2023-09-17 19:00:22 +02:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-17 17:34:06 +02:00
|
|
|
#[async_trait]
|
|
|
|
|
impl route::ToggleFallback for StateUpdate {
|
|
|
|
|
type UrlParams = (Uuid, Uuid);
|
|
|
|
|
|
|
|
|
|
const URL_TRUE: &'static str = "/:id/done/true";
|
|
|
|
|
const URL_FALSE: &'static str = "/:id/done/false";
|
|
|
|
|
|
|
|
|
|
async fn set(
|
|
|
|
|
current_user: User,
|
|
|
|
|
state: AppState,
|
|
|
|
|
headers: HeaderMap,
|
|
|
|
|
(trip_id, todo_id): (Uuid, Uuid),
|
|
|
|
|
value: bool,
|
|
|
|
|
) -> Result<Response<BoxBody>, crate::Error> {
|
|
|
|
|
let ctx = Context::build(current_user);
|
2023-09-17 19:00:22 +02:00
|
|
|
<Self as crud::Toggle>::set(
|
2023-09-17 17:34:06 +02:00
|
|
|
&ctx,
|
|
|
|
|
&state.database_pool,
|
|
|
|
|
Filter { trip_id },
|
2023-09-17 19:00:22 +02:00
|
|
|
Id(todo_id),
|
|
|
|
|
value,
|
2023-09-17 17:34:06 +02:00
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
|
|
Ok(Redirect::to(get_referer(&headers)?).into_response())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn router<B>() -> axum::Router<AppState, B>
|
|
|
|
|
where
|
|
|
|
|
B: HttpBody + Send + 'static,
|
|
|
|
|
<B as HttpBody>::Data: Send,
|
|
|
|
|
<B as HttpBody>::Error: std::error::Error + Sync + Send,
|
|
|
|
|
{
|
|
|
|
|
axum::Router::new()
|
|
|
|
|
.route(Self::URL_TRUE, post(Self::set_true))
|
|
|
|
|
.route(Self::URL_FALSE, post(Self::set_false))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
|
impl route::ToggleHtmx for StateUpdate {
|
2023-09-17 19:00:22 +02:00
|
|
|
type Id = Id;
|
|
|
|
|
type Filter = Filter;
|
2023-09-17 17:34:06 +02:00
|
|
|
type UrlParams = (Uuid, Uuid);
|
|
|
|
|
|
|
|
|
|
const URL_TRUE: &'static str = "/:id/done/htmx/true";
|
|
|
|
|
const URL_FALSE: &'static str = "/:id/done/htmx/false";
|
|
|
|
|
|
|
|
|
|
async fn set(
|
|
|
|
|
current_user: User,
|
|
|
|
|
state: AppState,
|
2023-09-17 19:00:22 +02:00
|
|
|
params: Self::UrlParams,
|
2023-09-17 17:34:06 +02:00
|
|
|
value: bool,
|
2023-09-17 19:00:22 +02:00
|
|
|
) -> Result<(crate::Context, AppState, Self::UrlParams, bool), crate::Error> {
|
2023-09-17 17:34:06 +02:00
|
|
|
let ctx = Context::build(current_user);
|
2023-09-17 19:00:22 +02:00
|
|
|
<Self as crud::Toggle>::set(
|
2023-09-17 17:34:06 +02:00
|
|
|
&ctx,
|
|
|
|
|
&state.database_pool,
|
2023-09-17 19:00:22 +02:00
|
|
|
params.into(),
|
|
|
|
|
params.into(),
|
|
|
|
|
value,
|
2023-09-17 17:34:06 +02:00
|
|
|
)
|
|
|
|
|
.await?;
|
|
|
|
|
|
2023-09-17 19:00:22 +02:00
|
|
|
Ok((ctx, state, params, value))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async fn response(
|
|
|
|
|
ctx: &Context,
|
|
|
|
|
state: AppState,
|
2023-09-17 22:54:59 +02:00
|
|
|
(trip_id, todo_id): Self::UrlParams,
|
2023-09-17 19:00:22 +02:00
|
|
|
value: bool,
|
|
|
|
|
) -> Result<Response<BoxBody>, crate::Error> {
|
2023-09-17 22:54:59 +02:00
|
|
|
let todo_item = Todo::find(&ctx, &state.database_pool, Filter { trip_id }, Id(todo_id))
|
2023-09-17 17:34:06 +02:00
|
|
|
.await?
|
|
|
|
|
.ok_or_else(|| {
|
|
|
|
|
crate::Error::Request(RequestError::NotFound {
|
|
|
|
|
message: format!("todo with id {todo_id} not found"),
|
|
|
|
|
})
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
Ok(todo_item
|
|
|
|
|
.build(BuildInput {
|
|
|
|
|
trip_id,
|
|
|
|
|
state: UiState::Default,
|
|
|
|
|
})
|
|
|
|
|
.into_response())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn router<B>() -> axum::Router<AppState, B>
|
|
|
|
|
where
|
|
|
|
|
B: HttpBody + Send + 'static,
|
|
|
|
|
<B as HttpBody>::Data: Send,
|
|
|
|
|
<B as HttpBody>::Error: std::error::Error + Sync + Send,
|
|
|
|
|
{
|
|
|
|
|
axum::Router::new()
|
2023-09-17 19:00:22 +02:00
|
|
|
.route(Self::URL_TRUE, post(Self::on))
|
|
|
|
|
.route(Self::URL_FALSE, post(Self::off))
|
2023-09-17 17:34:06 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
|
impl route::Toggle for StateUpdate {}
|