feat: added delete endpoints for lists and list items
This commit is contained in:
@@ -47,7 +47,7 @@ func (h *ListHandler) CreateList(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
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 {
|
if err != nil {
|
||||||
slog.ErrorContext(ctx, "failed to create list", slog.Any("error", err))
|
slog.ErrorContext(ctx, "failed to create list", slog.Any("error", err))
|
||||||
response.Error(ctx, w, http.StatusInternalServerError, "failed to create list")
|
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)
|
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
|
// GetLists handles fetching lists for the current user
|
||||||
//
|
//
|
||||||
// @Summary Returns all lists of the 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)
|
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
|
// UpdateListItem handles updating of a list item
|
||||||
//
|
//
|
||||||
// @Summary Update list item
|
// @Summary Update list item
|
||||||
@@ -274,6 +354,7 @@ func (h *ListHandler) UpdateListItem(w http.ResponseWriter, r *http.Request) {
|
|||||||
// @Tags List
|
// @Tags List
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
|
// @Param id path int true "List Item ID"
|
||||||
// @Param payload body request.SetListItemCompletedPayload true "Update list item is completed payload"
|
// @Param payload body request.SetListItemCompletedPayload true "Update list item is completed payload"
|
||||||
// @Success 204 {object} nil
|
// @Success 204 {object} nil
|
||||||
// @Error 400 {object} response.ErrorResponse "failed to decode request url"
|
// @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 /users", protected(userHandler.CreateUser))
|
||||||
|
|
||||||
apiMux.Handle("POST /lists", protected(listHandler.CreateList))
|
apiMux.Handle("POST /lists", protected(listHandler.CreateList))
|
||||||
|
apiMux.Handle("DELETE /lists/{id}", protected(listHandler.DeleteList))
|
||||||
apiMux.Handle("GET /lists", protected(listHandler.GetLists))
|
apiMux.Handle("GET /lists", protected(listHandler.GetLists))
|
||||||
apiMux.Handle("POST /lists/user", protected(listHandler.AddUserToList))
|
apiMux.Handle("POST /lists/user", protected(listHandler.AddUserToList))
|
||||||
apiMux.Handle("DELETE /lists/user", protected(listHandler.RemoveUserFromList))
|
apiMux.Handle("DELETE /lists/user", protected(listHandler.RemoveUserFromList))
|
||||||
apiMux.Handle("POST /lists/item", protected(listHandler.CreateListItem))
|
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("PUT /lists/item", protected(listHandler.UpdateListItem))
|
||||||
apiMux.Handle("POST /lists/items/{id}", protected(listHandler.SetListItemCompleted))
|
apiMux.Handle("POST /lists/items/{id}", protected(listHandler.SetListItemCompleted))
|
||||||
apiMux.Handle("GET /lists/{id}", protected(listHandler.GetListItems))
|
apiMux.Handle("GET /lists/{id}", protected(listHandler.GetListItems))
|
||||||
|
|||||||
+27
-1
@@ -37,12 +37,14 @@ type ListItem struct {
|
|||||||
|
|
||||||
type ListRepository interface {
|
type ListRepository interface {
|
||||||
CreateList(ctx context.Context, name string, userIDs []string) (*List, error)
|
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)
|
GetLists(ctx context.Context, userID string) ([]List, error)
|
||||||
AddUserToList(ctx context.Context, listID string, userID string) error
|
AddUserToList(ctx context.Context, listID string, userID string) error
|
||||||
RemoveUserFromList(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)
|
IsUserInList(ctx context.Context, listID string, userID string) (bool, error)
|
||||||
IsUserInListByItemID(ctx context.Context, listItemID 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)
|
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
|
UpdateListItem(ctx context.Context, listItemID string, title string, isCompleted bool) error
|
||||||
SetListItemCompleted(ctx context.Context, listItemID string, isCompleted bool) error
|
SetListItemCompleted(ctx context.Context, listItemID string, isCompleted bool) error
|
||||||
GetListItems(ctx context.Context, listID string) ([]ListItem, error)
|
GetListItems(ctx context.Context, listID string) ([]ListItem, error)
|
||||||
@@ -56,7 +58,7 @@ func NewListService(r ListRepository) *ListService {
|
|||||||
return &ListService{repo: r}
|
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 == "" {
|
if name == "" {
|
||||||
return nil, ErrListNameEmpty
|
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)
|
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) {
|
func (s *ListService) GetLists(ctx context.Context, authUserID string) ([]List, error) {
|
||||||
return s.repo.GetLists(ctx, authUserID)
|
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)
|
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 {
|
func (s *ListService) UpdateListItem(ctx context.Context, authUserID string, listItemID string, title string, isCompleted bool) error {
|
||||||
if listItemID == "" {
|
if listItemID == "" {
|
||||||
return ErrListItemIDEmpty
|
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)
|
repo.On("CreateList", mock.Anything, "My List", []string{"user1", "user2"}).Return(expectedList, nil)
|
||||||
|
|
||||||
service := domain.NewListService(repo)
|
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)
|
require.NoError(t, err)
|
||||||
assert.Equal(t, expectedList, list)
|
assert.Equal(t, expectedList, list)
|
||||||
@@ -48,7 +48,7 @@ func TestListService_Create_EmptyName(t *testing.T) {
|
|||||||
repo := new(mockListRepo)
|
repo := new(mockListRepo)
|
||||||
service := domain.NewListService(repo)
|
service := domain.NewListService(repo)
|
||||||
|
|
||||||
list, err := service.Create(context.Background(), "", []string{"user1"})
|
list, err := service.CreateList(context.Background(), "", []string{"user1"})
|
||||||
|
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
assert.EqualError(t, err, "list name must not be empty")
|
assert.EqualError(t, err, "list name must not be empty")
|
||||||
@@ -60,7 +60,7 @@ func TestListService_Create_EmptyUsers(t *testing.T) {
|
|||||||
repo := new(mockListRepo)
|
repo := new(mockListRepo)
|
||||||
service := domain.NewListService(repo)
|
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)
|
require.Error(t, err)
|
||||||
assert.EqualError(t, err, "users must have at least one associated user")
|
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)
|
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)
|
require.Error(t, err)
|
||||||
assert.ErrorIs(t, err, expectedErr)
|
assert.ErrorIs(t, err, expectedErr)
|
||||||
|
|||||||
@@ -59,6 +59,15 @@ func (r *ListRepo) CreateList(ctx context.Context, name string, userIDs []string
|
|||||||
return list, nil
|
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) {
|
func (r *ListRepo) GetLists(ctx context.Context, userID string) ([]domain.List, error) {
|
||||||
rows, err := r.db.QueryContext(ctx,
|
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",
|
"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
|
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 {
|
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",
|
_, err := r.db.ExecContext(ctx, "UPDATE list_items SET title = $1, is_completed = $2, modified_at = NOW() WHERE id = $3",
|
||||||
title, isCompleted, listItemID,
|
title, isCompleted, listItemID,
|
||||||
|
|||||||
Reference in New Issue
Block a user