feat: add GET /api/saved-filters/:id endpoint with comprehensive tests
Implement missing GET endpoint for retrieving individual saved filters by ID. This completes the CRUD API for saved filters and enables mobile/SPA clients to fetch filter details on-demand. Backend Implementation: - Add GetSavedFilterByID() handler method (internal/handlers/filters.go) - Parse filter ID from URL parameter - Validate UUID format, return 400 for invalid IDs - Call service layer for business logic + ownership verification - Return 404 if filter not found or doesn't belong to user - Return 200 with filter object including filters JSONB - Add GetSavedFilterByID() service method (internal/services/filters.go) - Call existing database query GetSavedFilterByID - Verify filter exists and belongs to user - Return descriptive error: "filter not found or access denied" - Reuses existing database query (no new SQL needed) - Register GET /:id route (internal/router/filters.go) - Add route before existing GET "" route - Follows RESTful routing conventions Integration Tests (cmd/server/tests/filters_test.go): - Test success case: Create filter, retrieve by ID, verify data - Test error case: Invalid UUID format returns 400 - Test error case: Non-existent filter returns 404 - Test error case: No authentication returns 401 - Test security case: Cross-user access returns 404 (not 403) - Admin creates filter, regular user tries to access - Uses setup.Token (admin) and setup.RegularToken - Verifies information leakage prevention API Design: - Endpoint: GET /api/saved-filters/:id - Authentication: JWT token required - Response format: SavedFilterResponse with filters as JSON - Error responses: 400 (invalid ID), 401 (no auth), 404 (not found) - Security: Returns 404 for cross-user access (hides existence) Benefits: - Completes CRUD API for saved filters - Enables future mobile/SPA clients - Follows existing handler/service/test patterns - Comprehensive security testing - No database changes required (reuses existing queries) Follows PROJECT_GUIDELINES.md service layer architecture and testing patterns.
This commit is contained in:
@@ -86,6 +86,46 @@ func (h *FiltersHandler) GetSavedFilters(c *echo.Context) error {
|
||||
return c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// GetSavedFilterByID - GET /api/saved-filters/:id
|
||||
// Retrieve a single saved filter by ID
|
||||
func (h *FiltersHandler) GetSavedFilterByID(c *echo.Context) error {
|
||||
user := c.Get("user").(database.Users)
|
||||
userUUID := uuid.UUID(user.ID.Bytes)
|
||||
|
||||
// Parse filter ID from URL parameter
|
||||
filterID, err := uuid.Parse(c.Param("id"))
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{
|
||||
"error": "invalid filter ID",
|
||||
})
|
||||
}
|
||||
|
||||
// Use service layer (includes business logic + ownership verification)
|
||||
filter, err := h.filtersService.GetSavedFilterByID(c.Request().Context(), userUUID, filterID)
|
||||
if err != nil {
|
||||
if err.Error() == "filter not found or access denied" {
|
||||
return c.JSON(http.StatusNotFound, map[string]string{
|
||||
"error": "filter not found",
|
||||
})
|
||||
}
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{
|
||||
"error": "failed to fetch filter",
|
||||
})
|
||||
}
|
||||
|
||||
// Return JSON response (JSONB handled automatically)
|
||||
response := SavedFilterResponse{
|
||||
ID: filterID,
|
||||
Name: filter.Name,
|
||||
ResourceType: filter.ResourceType,
|
||||
Filters: json.RawMessage(filter.Filters), // Return JSONB as-is
|
||||
CreatedAt: filter.CreatedAt.Time.Format(time.RFC3339),
|
||||
UpdatedAt: filter.UpdatedAt.Time.Format(time.RFC3339),
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// CreateSavedFilter - POST /api/saved-filters
|
||||
// Supports both JSON (API) and HTML (HTMX) responses
|
||||
func (h *FiltersHandler) CreateSavedFilter(c *echo.Context) error {
|
||||
|
||||
Reference in New Issue
Block a user