diff --git a/.sqlx/query-2ba6f5558e04ca1eb9dee3e9c528271da22595b60016a81d8a18f7c6d30011a4.json b/.sqlx/query-2ba6f5558e04ca1eb9dee3e9c528271da22595b60016a81d8a18f7c6d30011a4.json new file mode 100644 index 0000000..d3b8ebd --- /dev/null +++ b/.sqlx/query-2ba6f5558e04ca1eb9dee3e9c528271da22595b60016a81d8a18f7c6d30011a4.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n update lists\n set title = $1\n where id = $2", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text", + "Uuid" + ] + }, + "nullable": [] + }, + "hash": "2ba6f5558e04ca1eb9dee3e9c528271da22595b60016a81d8a18f7c6d30011a4" +} diff --git a/src/db/lists.rs b/src/db/lists.rs index 4e9ef3c..86b566e 100644 --- a/src/db/lists.rs +++ b/src/db/lists.rs @@ -1,4 +1,5 @@ use serde::Deserialize; +use sqlx::query; use sqlx::query_as; use sqlx::FromRow; use time::OffsetDateTime; @@ -69,6 +70,20 @@ pub async fn insert( Ok(list) } +pub async fn edit_title(tx: &mut AppTx, list_id: Uuid, new_title: String) -> ResponseResult<()> { + query!( + r#" + update lists + set title = $1 + where id = $2"#, + new_title, + list_id, + ) + .execute(&mut **tx) + .await?; + + Ok(()) +} pub async fn by_id(tx: &mut AppTx, list_id: Uuid) -> ResponseResult<List> { let list = query_as!( diff --git a/src/forms/lists.rs b/src/forms/lists.rs index fd9d6ec..7d6b831 100644 --- a/src/forms/lists.rs +++ b/src/forms/lists.rs @@ -12,6 +12,11 @@ pub struct CreateList { pub private: bool, } +#[derive(Deserialize, Default)] +pub struct EditTitle { + pub title: String, +} + #[derive(Deserialize)] pub struct EditListPrivate { pub private: bool, diff --git a/src/routes/lists.rs b/src/routes/lists.rs index 9945ee5..0aca477 100644 --- a/src/routes/lists.rs +++ b/src/routes/lists.rs @@ -2,13 +2,13 @@ use crate::form_errors::FormErrors; use crate::forms::lists::{CreateList, EditListPinned, EditListPrivate}; use crate::response_error::ResponseError; use crate::server::AppState; -use crate::views::lists::{CreateListTemplate, UnpinnedListsTemplate}; +use crate::views::lists::{CreateListTemplate, EditListTitleTemplate, UnpinnedListsTemplate}; use crate::{authentication::AuthUser, response_error::ResponseResult}; use crate::{ db::{self}, views::{layout, lists::ListTemplate}, }; -use crate::{extract, views}; +use crate::{extract, forms, views}; use askama_axum::IntoResponse; use axum::extract::Path; use axum::response::Redirect; @@ -25,6 +25,8 @@ pub fn router() -> Router<AppState> { .route("/lists/create", get(get_create).post(post_create)) .route("/lists/:list_id", get(list)) .route("/lists/:list_id/edit_private", post(edit_private)) + .route("/lists/:list_id/edit_title", post(post_edit_title)) + .route("/lists/:list_id/edit_title", get(get_edit_title)) .route("/lists/:list_id/edit_pinned", post(edit_pinned)) .route("/lists/unpinned", get(list_unpinned)) } @@ -96,6 +98,46 @@ async fn get_create( }) } +async fn get_edit_title( + extract::Tx(mut tx): extract::Tx, + auth_user: AuthUser, + Path(list_id): Path<Uuid>, +) -> ResponseResult<EditListTitleTemplate> { + let list = db::lists::by_id(&mut tx, list_id).await?; + + if list.user_id != auth_user.user_id { + return Err(ResponseError::NotFound); + } + + let layout = layout::Template::from_db(&mut tx, Some(&auth_user)).await?; + + Ok(EditListTitleTemplate { + layout, + errors: FormErrors::default(), + input: forms::lists::EditTitle::default(), + list_id, + }) +} + +async fn post_edit_title( + auth_user: AuthUser, + extract::Tx(mut tx): extract::Tx, + Path(list_id): Path<Uuid>, + Form(input): Form<forms::lists::EditTitle>, +) -> ResponseResult<Response> { + let list = db::lists::by_id(&mut tx, list_id).await?; + + if list.user_id != auth_user.user_id { + return Err(ResponseError::NotFound); + } + + db::lists::edit_title(&mut tx, list_id, input.title).await?; + + tx.commit().await?; + + Ok(Redirect::to(&list.path()).into_response()) +} + async fn edit_private( auth_user: AuthUser, extract::Tx(mut tx): extract::Tx, diff --git a/src/views/lists.rs b/src/views/lists.rs index 39ff38f..accd705 100644 --- a/src/views/lists.rs +++ b/src/views/lists.rs @@ -1,9 +1,10 @@ use askama::Template; +use uuid::Uuid; use crate::{ db::{self}, form_errors::FormErrors, - forms::lists::CreateList, + forms::{self, lists::CreateList}, }; use super::layout; @@ -25,6 +26,15 @@ pub struct CreateListTemplate { pub errors: FormErrors, } +#[derive(Template)] +#[template(path = "edit_list_title.html")] +pub struct EditListTitleTemplate { + pub layout: layout::Template, + pub input: forms::lists::EditTitle, + pub errors: FormErrors, + pub list_id: Uuid, +} + #[derive(Template)] #[template(path = "list_unpinned_lists.html")] pub struct UnpinnedListsTemplate { diff --git a/templates/edit_list_title.html b/templates/edit_list_title.html new file mode 100644 index 0000000..86b73b9 --- /dev/null +++ b/templates/edit_list_title.html @@ -0,0 +1,33 @@ +{% import "_form.html" as form %} +{% extends "_layout.html" %} + +{% block content %} + <form + action="/lists/{{ list_id }}/edit_title" + method="POST" + class="flex flex-col max-w-xl mx-4 mb-4 grow" + > + <header class="mt-3 mb-4"> + <h1 class="text-xl font-bold">Rename list</h1> + </header> + + <label for="title">New title</label> + {% call form::errors(errors, "title") %} + <input + type="text" + name="title" + required + value="{{ input.title }}" + class="rounded py-1.5 px-3 mt-2 bg-neutral-900" + /> + + {% call form::errors(errors, "root") %} + + <button + type="submit" + class="bg-neutral-300 py-1.5 px-3 text-neutral-900 rounded mt-4 self-end" + > + Save Changes + </button> + </form> +{% endblock %} diff --git a/templates/list.html b/templates/list.html index c44adc0..1903904 100644 --- a/templates/list.html +++ b/templates/list.html @@ -69,6 +69,11 @@ <h1 class="text-xl font-bold">{{ list.title }}</h1> {% endif %} </button> </form> + <a + href="/lists/{{ list.id }}/edit_title" + class="block px-4 py-1 border rounded hover:bg-neutral-700 border-neutral-700 w-max" + >Rename</a + > <a href="/bookmarks/create?parent_id={{ list.id }}" class="block px-4 py-1 border rounded hover:bg-neutral-700 border-neutral-700 w-max"