Delete lists and add users via email #22
@@ -47,7 +47,7 @@ func (h *ListHandler) CreateList(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
list, err := h.ListService.Create(ctx, authContext.UserID, payload.Name, payload.UserIDs)
|
||||
list, err := h.ListService.CreateList(ctx, authContext.UserID, payload.Name, payload.UserIDs)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "failed to create list", slog.Any("error", err))
|
||||
response.Error(ctx, w, http.StatusInternalServerError, "failed to create list")
|
||||
@@ -58,6 +58,46 @@ func (h *ListHandler) CreateList(w http.ResponseWriter, r *http.Request) {
|
||||
response.JSON(ctx, w, http.StatusCreated, list)
|
||||
}
|
||||
|
||||
// DeleteList handles the deletion of a list
|
||||
//
|
||||
// @Summary Delete list route
|
||||
// @Description Deletes a list, cascading to user associations and items
|
||||
// @Tags List
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "List ID"
|
||||
// @Success 204
|
||||
// @Error 400 {object} response.ErrorResponse "failed to decode request url"
|
||||
// @Error 500 {object} response.ErrorResponse "failed to delete list"
|
||||
// @Router /lists/{id} [delete]
|
||||
func (h *ListHandler) DeleteList(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
listID := r.PathValue("id")
|
||||
if listID == "" {
|
||||
slog.ErrorContext(ctx, "failed to read list id from path")
|
||||
response.Error(ctx, w, http.StatusBadRequest, "failed to decode request url")
|
||||
return
|
||||
}
|
||||
|
||||
authContext, err := domain.GetAuthContext(ctx)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "failed to get auth context", slog.Any("error", err))
|
||||
response.Error(ctx, w, http.StatusInternalServerError, "failed to delete list")
|
||||
return
|
||||
}
|
||||
|
||||
err = h.ListService.DeleteList(ctx, authContext.UserID, listID)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "failed to delete list", slog.Any("error", err))
|
||||
response.Error(ctx, w, http.StatusInternalServerError, "failed to delete list")
|
||||
return
|
||||
}
|
||||
|
||||
slog.InfoContext(ctx, "deleted list successfully", slog.Any("list_id", listID))
|
||||
response.Status(ctx, w, http.StatusNoContent)
|
||||
}
|
||||
|
||||
// GetLists handles fetching lists for the current user
|
||||
//
|
||||
// @Summary Returns all lists of the user
|
||||
@@ -226,6 +266,46 @@ func (h *ListHandler) CreateListItem(w http.ResponseWriter, r *http.Request) {
|
||||
response.JSON(ctx, w, http.StatusCreated, item)
|
||||
}
|
||||
|
||||
// DeleteListItem handles the deletion of a list item
|
||||
//
|
||||
// @Summary Delete list item route
|
||||
// @Description Deletes an item
|
||||
// @Tags List
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "List Item ID"
|
||||
// @Success 204
|
||||
// @Error 400 {object} response.ErrorResponse "failed to decode request url"
|
||||
// @Error 500 {object} response.ErrorResponse "failed to delete list item"
|
||||
// @Router /lists/item/{id} [delete]
|
||||
func (h *ListHandler) DeleteListItem(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
listItemID := r.PathValue("id")
|
||||
if listItemID == "" {
|
||||
slog.ErrorContext(ctx, "failed to read list item id from path")
|
||||
response.Error(ctx, w, http.StatusBadRequest, "failed to decode request url")
|
||||
return
|
||||
}
|
||||
|
||||
authContext, err := domain.GetAuthContext(ctx)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "failed to get auth context", slog.Any("error", err))
|
||||
response.Error(ctx, w, http.StatusInternalServerError, "failed to delete list item")
|
||||
return
|
||||
}
|
||||
|
||||
err = h.ListService.DeleteListItem(ctx, authContext.UserID, listItemID)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "failed to delete list item", slog.Any("error", err))
|
||||
response.Error(ctx, w, http.StatusInternalServerError, "failed to delete list item")
|
||||
return
|
||||
}
|
||||
|
||||
slog.InfoContext(ctx, "deleted list item successfully", slog.Any("list_item_id", listItemID))
|
||||
response.Status(ctx, w, http.StatusNoContent)
|
||||
}
|
||||
|
||||
// UpdateListItem handles updating of a list item
|
||||
//
|
||||
// @Summary Update list item
|
||||
@@ -274,6 +354,7 @@ func (h *ListHandler) UpdateListItem(w http.ResponseWriter, r *http.Request) {
|
||||
// @Tags List
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "List Item ID"
|
||||
// @Param payload body request.SetListItemCompletedPayload true "Update list item is completed payload"
|
||||
// @Success 204 {object} nil
|
||||
// @Error 400 {object} response.ErrorResponse "failed to decode request url"
|
||||
|
||||
@@ -42,10 +42,12 @@ func NewMux(cfg Config) http.Handler {
|
||||
apiMux.Handle("POST /users", protected(userHandler.CreateUser))
|
||||
|
||||
apiMux.Handle("POST /lists", protected(listHandler.CreateList))
|
||||
apiMux.Handle("DELETE /lists/{id}", protected(listHandler.DeleteList))
|
||||
apiMux.Handle("GET /lists", protected(listHandler.GetLists))
|
||||
apiMux.Handle("POST /lists/user", protected(listHandler.AddUserToList))
|
||||
apiMux.Handle("DELETE /lists/user", protected(listHandler.RemoveUserFromList))
|
||||
apiMux.Handle("POST /lists/item", protected(listHandler.CreateListItem))
|
||||
apiMux.Handle("DELETE /lists/item/{id}", protected(listHandler.DeleteListItem))
|
||||
apiMux.Handle("PUT /lists/item", protected(listHandler.UpdateListItem))
|
||||
apiMux.Handle("POST /lists/items/{id}", protected(listHandler.SetListItemCompleted))
|
||||
apiMux.Handle("GET /lists/{id}", protected(listHandler.GetListItems))
|
||||
|
||||
+27
-1
@@ -37,12 +37,14 @@ type ListItem struct {
|
||||
|
||||
type ListRepository interface {
|
||||
CreateList(ctx context.Context, name string, userIDs []string) (*List, error)
|
||||
DeleteList(ctx context.Context, listID string) error
|
||||
GetLists(ctx context.Context, userID string) ([]List, error)
|
||||
AddUserToList(ctx context.Context, listID string, userID string) error
|
||||
RemoveUserFromList(ctx context.Context, listID string, userID string) error
|
||||
IsUserInList(ctx context.Context, listID string, userID string) (bool, error)
|
||||
IsUserInListByItemID(ctx context.Context, listItemID string, userID string) (bool, error)
|
||||
CreateListItem(ctx context.Context, listID string, title string) (*ListItem, error)
|
||||
DeleteListItem(ctx context.Context, listItemID string) error
|
||||
UpdateListItem(ctx context.Context, listItemID string, title string, isCompleted bool) error
|
||||
SetListItemCompleted(ctx context.Context, listItemID string, isCompleted bool) error
|
||||
GetListItems(ctx context.Context, listID string) ([]ListItem, error)
|
||||
@@ -56,7 +58,7 @@ func NewListService(r ListRepository) *ListService {
|
||||
return &ListService{repo: r}
|
||||
}
|
||||
|
||||
func (s *ListService) Create(ctx context.Context, authUserID string, name string, userIDs []string) (*List, error) {
|
||||
func (s *ListService) CreateList(ctx context.Context, authUserID string, name string, userIDs []string) (*List, error) {
|
||||
if name == "" {
|
||||
return nil, ErrListNameEmpty
|
||||
}
|
||||
@@ -68,6 +70,18 @@ func (s *ListService) Create(ctx context.Context, authUserID string, name string
|
||||
return s.repo.CreateList(ctx, name, userIDs)
|
||||
}
|
||||
|
||||
func (s *ListService) DeleteList(ctx context.Context, authUserID string, listID string) error {
|
||||
if listID == "" {
|
||||
return ErrListIDEmpty
|
||||
}
|
||||
|
||||
if err := s.userAllowedToAccessList(ctx, authUserID, listID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.repo.DeleteList(ctx, listID)
|
||||
}
|
||||
|
||||
func (s *ListService) GetLists(ctx context.Context, authUserID string) ([]List, error) {
|
||||
return s.repo.GetLists(ctx, authUserID)
|
||||
}
|
||||
@@ -117,6 +131,18 @@ func (s *ListService) CreateListItem(ctx context.Context, authUserID string, lis
|
||||
return s.repo.CreateListItem(ctx, listID, title)
|
||||
}
|
||||
|
||||
func (s *ListService) DeleteListItem(ctx context.Context, authUserID string, listItemID string) error {
|
||||
if listItemID == "" {
|
||||
return ErrListItemIDEmpty
|
||||
}
|
||||
|
||||
if err := s.userAllowedToAccessListItem(ctx, authUserID, listItemID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.repo.DeleteListItem(ctx, listItemID)
|
||||
}
|
||||
|
||||
func (s *ListService) UpdateListItem(ctx context.Context, authUserID string, listItemID string, title string, isCompleted bool) error {
|
||||
if listItemID == "" {
|
||||
return ErrListItemIDEmpty
|
||||
|
||||
@@ -37,7 +37,7 @@ func TestListService_Create_Success(t *testing.T) {
|
||||
repo.On("CreateList", mock.Anything, "My List", []string{"user1", "user2"}).Return(expectedList, nil)
|
||||
|
||||
service := domain.NewListService(repo)
|
||||
list, err := service.Create(context.Background(), "My List", []string{"user1", "user2"})
|
||||
list, err := service.CreateList(context.Background(), "My List", []string{"user1", "user2"})
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedList, list)
|
||||
@@ -48,7 +48,7 @@ func TestListService_Create_EmptyName(t *testing.T) {
|
||||
repo := new(mockListRepo)
|
||||
service := domain.NewListService(repo)
|
||||
|
||||
list, err := service.Create(context.Background(), "", []string{"user1"})
|
||||
list, err := service.CreateList(context.Background(), "", []string{"user1"})
|
||||
|
||||
require.Error(t, err)
|
||||
assert.EqualError(t, err, "list name must not be empty")
|
||||
@@ -60,7 +60,7 @@ func TestListService_Create_EmptyUsers(t *testing.T) {
|
||||
repo := new(mockListRepo)
|
||||
service := domain.NewListService(repo)
|
||||
|
||||
list, err := service.Create(context.Background(), "My List", []string{})
|
||||
list, err := service.CreateList(context.Background(), "My List", []string{})
|
||||
|
||||
require.Error(t, err)
|
||||
assert.EqualError(t, err, "users must have at least one associated user")
|
||||
@@ -75,7 +75,7 @@ func TestListService_Create_RepoError(t *testing.T) {
|
||||
|
||||
service := domain.NewListService(repo)
|
||||
|
||||
list, err := service.Create(context.Background(), "My List", []string{"user1"})
|
||||
list, err := service.CreateList(context.Background(), "My List", []string{"user1"})
|
||||
|
||||
require.Error(t, err)
|
||||
assert.ErrorIs(t, err, expectedErr)
|
||||
|
||||
@@ -59,6 +59,15 @@ func (r *ListRepo) CreateList(ctx context.Context, name string, userIDs []string
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (r *ListRepo) DeleteList(ctx context.Context, listID string) error {
|
||||
_, err := r.db.ExecContext(ctx, "DELETE FROM lists WHERE id = $1", listID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete list: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ListRepo) GetLists(ctx context.Context, userID string) ([]domain.List, error) {
|
||||
rows, err := r.db.QueryContext(ctx,
|
||||
"SELECT l.id, l.name, l.created_at, l.modified_at, (SELECT COUNT(*) FROM list_items WHERE list_id=l.id), (SELECT COUNT(*) FROM list_items WHERE list_id=l.id AND is_completed=true) FROM lists AS l INNER JOIN list_users ON lists.id=list_users.list_id WHERE list_users.user_id = $1",
|
||||
@@ -153,6 +162,15 @@ func (r *ListRepo) CreateListItem(ctx context.Context, listID string, title stri
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (r *ListRepo) DeleteListItem(ctx context.Context, listItemID string) error {
|
||||
_, err := r.db.ExecContext(ctx, "DELETE FROM list_items WHERE id = $1", listItemID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete list item: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ListRepo) UpdateListItem(ctx context.Context, listItemID string, title string, isCompleted bool) error {
|
||||
_, err := r.db.ExecContext(ctx, "UPDATE list_items SET title = $1, is_completed = $2, modified_at = NOW() WHERE id = $3",
|
||||
title, isCompleted, listItemID,
|
||||
|
||||
Reference in New Issue
Block a user