feat: delete list
This commit is contained in:
parent
e8d34fde3f
commit
4f5873934a
3 changed files with 118 additions and 6 deletions
|
|
@ -38,6 +38,21 @@ func NewSpaceHandler(ss *service.SpaceService, ts *service.TagService, sls *serv
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getListForSpace fetches a shopping list and verifies it belongs to the given space.
|
||||||
|
// Returns the list on success, or writes an error response and returns nil.
|
||||||
|
func (h *SpaceHandler) getListForSpace(w http.ResponseWriter, spaceID, listID string) *model.ShoppingList {
|
||||||
|
list, err := h.listService.GetList(listID)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "List not found", http.StatusNotFound)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if list.SpaceID != spaceID {
|
||||||
|
http.Error(w, "Not Found", http.StatusNotFound)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
func (h *SpaceHandler) StreamEvents(w http.ResponseWriter, r *http.Request) {
|
func (h *SpaceHandler) StreamEvents(w http.ResponseWriter, r *http.Request) {
|
||||||
spaceID := r.PathValue("spaceID")
|
spaceID := r.PathValue("spaceID")
|
||||||
|
|
||||||
|
|
@ -150,6 +165,10 @@ func (h *SpaceHandler) UpdateList(w http.ResponseWriter, r *http.Request) {
|
||||||
spaceID := r.PathValue("spaceID")
|
spaceID := r.PathValue("spaceID")
|
||||||
listID := r.PathValue("listID")
|
listID := r.PathValue("listID")
|
||||||
|
|
||||||
|
if h.getListForSpace(w, spaceID, listID) == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
http.Error(w, "Bad Request", http.StatusBadRequest)
|
http.Error(w, "Bad Request", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
|
|
@ -171,6 +190,25 @@ func (h *SpaceHandler) UpdateList(w http.ResponseWriter, r *http.Request) {
|
||||||
ui.Render(w, r, shoppinglist.ListNameHeader(spaceID, updatedList))
|
ui.Render(w, r, shoppinglist.ListNameHeader(spaceID, updatedList))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *SpaceHandler) DeleteList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
spaceID := r.PathValue("spaceID")
|
||||||
|
listID := r.PathValue("listID")
|
||||||
|
|
||||||
|
if h.getListForSpace(w, spaceID, listID) == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err := h.listService.DeleteList(listID)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to delete list", "error", err, "list_id", listID)
|
||||||
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("HX-Redirect", "/app/spaces/"+spaceID+"/lists")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
func (h *SpaceHandler) ListPage(w http.ResponseWriter, r *http.Request) {
|
func (h *SpaceHandler) ListPage(w http.ResponseWriter, r *http.Request) {
|
||||||
spaceID := r.PathValue("spaceID")
|
spaceID := r.PathValue("spaceID")
|
||||||
listID := r.PathValue("listID")
|
listID := r.PathValue("listID")
|
||||||
|
|
@ -181,10 +219,8 @@ func (h *SpaceHandler) ListPage(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
list, err := h.listService.GetList(listID)
|
list := h.getListForSpace(w, spaceID, listID)
|
||||||
if err != nil {
|
if list == nil {
|
||||||
slog.Error("failed to get list", "error", err, "list_id", listID)
|
|
||||||
http.Error(w, "List not found", http.StatusNotFound)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -201,6 +237,11 @@ func (h *SpaceHandler) ListPage(w http.ResponseWriter, r *http.Request) {
|
||||||
func (h *SpaceHandler) AddItemToList(w http.ResponseWriter, r *http.Request) {
|
func (h *SpaceHandler) AddItemToList(w http.ResponseWriter, r *http.Request) {
|
||||||
spaceID := r.PathValue("spaceID")
|
spaceID := r.PathValue("spaceID")
|
||||||
listID := r.PathValue("listID")
|
listID := r.PathValue("listID")
|
||||||
|
|
||||||
|
if h.getListForSpace(w, spaceID, listID) == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
user := ctxkeys.User(r.Context())
|
user := ctxkeys.User(r.Context())
|
||||||
|
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
|
|
@ -226,8 +267,13 @@ func (h *SpaceHandler) AddItemToList(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
func (h *SpaceHandler) ToggleItem(w http.ResponseWriter, r *http.Request) {
|
func (h *SpaceHandler) ToggleItem(w http.ResponseWriter, r *http.Request) {
|
||||||
spaceID := r.PathValue("spaceID")
|
spaceID := r.PathValue("spaceID")
|
||||||
|
listID := r.PathValue("listID")
|
||||||
itemID := r.PathValue("itemID")
|
itemID := r.PathValue("itemID")
|
||||||
|
|
||||||
|
if h.getListForSpace(w, spaceID, listID) == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
item, err := h.listService.GetItem(itemID)
|
item, err := h.listService.GetItem(itemID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("failed to get item", "error", err, "item_id", itemID)
|
slog.Error("failed to get item", "error", err, "item_id", itemID)
|
||||||
|
|
@ -235,6 +281,11 @@ func (h *SpaceHandler) ToggleItem(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if item.ListID != listID {
|
||||||
|
http.Error(w, "Not Found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
updatedItem, err := h.listService.UpdateItem(itemID, item.Name, !item.IsChecked)
|
updatedItem, err := h.listService.UpdateItem(itemID, item.Name, !item.IsChecked)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("failed to toggle item", "error", err, "item_id", itemID)
|
slog.Error("failed to toggle item", "error", err, "item_id", itemID)
|
||||||
|
|
@ -246,9 +297,27 @@ func (h *SpaceHandler) ToggleItem(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *SpaceHandler) DeleteItem(w http.ResponseWriter, r *http.Request) {
|
func (h *SpaceHandler) DeleteItem(w http.ResponseWriter, r *http.Request) {
|
||||||
|
spaceID := r.PathValue("spaceID")
|
||||||
|
listID := r.PathValue("listID")
|
||||||
itemID := r.PathValue("itemID")
|
itemID := r.PathValue("itemID")
|
||||||
|
|
||||||
err := h.listService.DeleteItem(itemID)
|
if h.getListForSpace(w, spaceID, listID) == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
item, err := h.listService.GetItem(itemID)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to get item", "error", err, "item_id", itemID)
|
||||||
|
http.Error(w, "Item not found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if item.ListID != listID {
|
||||||
|
http.Error(w, "Not Found", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.listService.DeleteItem(itemID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("failed to delete item", "error", err, "item_id", itemID)
|
slog.Error("failed to delete item", "error", err, "item_id", itemID)
|
||||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||||
|
|
@ -550,6 +619,10 @@ func (h *SpaceHandler) GetShoppingListItems(w http.ResponseWriter, r *http.Reque
|
||||||
spaceID := r.PathValue("spaceID")
|
spaceID := r.PathValue("spaceID")
|
||||||
listID := r.PathValue("listID")
|
listID := r.PathValue("listID")
|
||||||
|
|
||||||
|
if h.getListForSpace(w, spaceID, listID) == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
items, err := h.listService.GetItemsForList(listID)
|
items, err := h.listService.GetItemsForList(listID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error("failed to get items", "error", err, "list_id", listID)
|
slog.Error("failed to get items", "error", err, "list_id", listID)
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,10 @@ func SetupRoutes(a *app.App) http.Handler {
|
||||||
updateListWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(updateListHandler)
|
updateListWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(updateListHandler)
|
||||||
mux.Handle("PATCH /app/spaces/{spaceID}/lists/{listID}", updateListWithAccess)
|
mux.Handle("PATCH /app/spaces/{spaceID}/lists/{listID}", updateListWithAccess)
|
||||||
|
|
||||||
|
deleteListHandler := middleware.RequireAuth(space.DeleteList)
|
||||||
|
deleteListWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(deleteListHandler)
|
||||||
|
mux.Handle("DELETE /app/spaces/{spaceID}/lists/{listID}", deleteListWithAccess)
|
||||||
|
|
||||||
addItemHandler := middleware.RequireAuth(space.AddItemToList)
|
addItemHandler := middleware.RequireAuth(space.AddItemToList)
|
||||||
addItemWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(addItemHandler)
|
addItemWithAccess := middleware.RequireSpaceAccess(a.SpaceService)(addItemHandler)
|
||||||
mux.Handle("POST /app/spaces/{spaceID}/lists/{listID}/items", addItemWithAccess)
|
mux.Handle("POST /app/spaces/{spaceID}/lists/{listID}/items", addItemWithAccess)
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"git.juancwu.dev/juancwu/budgit/internal/model"
|
"git.juancwu.dev/juancwu/budgit/internal/model"
|
||||||
"git.juancwu.dev/juancwu/budgit/internal/ui/components/button"
|
"git.juancwu.dev/juancwu/budgit/internal/ui/components/button"
|
||||||
"git.juancwu.dev/juancwu/budgit/internal/ui/components/csrf"
|
"git.juancwu.dev/juancwu/budgit/internal/ui/components/csrf"
|
||||||
|
"git.juancwu.dev/juancwu/budgit/internal/ui/components/dialog"
|
||||||
"git.juancwu.dev/juancwu/budgit/internal/ui/components/input"
|
"git.juancwu.dev/juancwu/budgit/internal/ui/components/input"
|
||||||
"git.juancwu.dev/juancwu/budgit/internal/ui/components/shoppinglist"
|
"git.juancwu.dev/juancwu/budgit/internal/ui/components/shoppinglist"
|
||||||
"git.juancwu.dev/juancwu/budgit/internal/ui/layouts"
|
"git.juancwu.dev/juancwu/budgit/internal/ui/layouts"
|
||||||
|
|
@ -12,7 +13,41 @@ import (
|
||||||
templ SpaceListDetailPage(space *model.Space, list *model.ShoppingList, items []*model.ListItem) {
|
templ SpaceListDetailPage(space *model.Space, list *model.ShoppingList, items []*model.ListItem) {
|
||||||
@layouts.Space(list.Name, space) {
|
@layouts.Space(list.Name, space) {
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
@shoppinglist.ListNameHeader(space.ID, list)
|
<div class="flex items-center justify-between">
|
||||||
|
@shoppinglist.ListNameHeader(space.ID, list)
|
||||||
|
@dialog.Dialog(dialog.Props{ID: "delete-list-dialog"}) {
|
||||||
|
@dialog.Trigger() {
|
||||||
|
@button.Button(button.Props{Variant: button.VariantGhost}) {
|
||||||
|
Delete List
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@dialog.Content() {
|
||||||
|
@dialog.Header() {
|
||||||
|
@dialog.Title() {
|
||||||
|
Delete Shopping List
|
||||||
|
}
|
||||||
|
@dialog.Description() {
|
||||||
|
Are you sure you want to delete "{ list.Name }"? This will permanently remove the list and all its items. This action cannot be undone.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@dialog.Footer() {
|
||||||
|
@dialog.Close() {
|
||||||
|
@button.Button(button.Props{Variant: button.VariantOutline}) {
|
||||||
|
Cancel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@button.Button(button.Props{
|
||||||
|
Variant: button.VariantDestructive,
|
||||||
|
Attributes: templ.Attributes{
|
||||||
|
"hx-delete": "/app/spaces/" + space.ID + "/lists/" + list.ID,
|
||||||
|
},
|
||||||
|
}) {
|
||||||
|
Delete
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
<form
|
<form
|
||||||
hx-post={ "/app/spaces/" + space.ID + "/lists/" + list.ID + "/items" }
|
hx-post={ "/app/spaces/" + space.ID + "/lists/" + list.ID + "/items" }
|
||||||
hx-target="#items-container"
|
hx-target="#items-container"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue