docs: Add Phase 4.6 - CreateCollection manual books support
Add comprehensive documentation for Phase 4.6 which enables the CreateCollection endpoint to support manual book selection alongside auto-assign rules. This is required for the Custom Section Builder. Key additions: - Phase 4.6: Update CreateCollection Endpoint (30-45 min) - Add ManualBookIDs field to CreateCollectionRequest struct - Implement graceful handling of invalid book IDs - Add validation (max 50 book IDs) to prevent DoS - Reuse existing AddBookToCollection service method - Maintain backward compatibility (field is optional) - Updated Phase 12.5: Collections Bruno tests - create-collection-with-manual-books.bru - create-collection-too-many-books.bru (validation test) - create-collection-invalid-book-id.bru - create-collection-rules-only.bru - create-collection-unauthorized.bru - Added section 13.3: Collections API documentation - manual_book_ids field documentation - Validation limits (max 50 items) - Example combining auto-assign + manual books - Error handling explanation Design decisions: - Graceful degradation: Collection created even if some books fail - Reuse existing infrastructure: No new service methods needed - Backward compatible: Optional field doesn't break existing clients - UI constraint: 50 book limit prevents abuse while allowing flexibility
This commit is contained in:
+520
-92
@@ -1069,6 +1069,138 @@ post:
|
||||
|
||||
---
|
||||
|
||||
### **Phase 4.6: Update CreateCollection Endpoint** (30-45 min)
|
||||
|
||||
**REQUIRED for Custom Section Builder**: The `CreateCollection` endpoint must support adding manual books when creating a collection.
|
||||
|
||||
**Why this is needed:**
|
||||
- Custom Section Builder allows users to select books manually AND use filter rules
|
||||
- Both features can be combined (rules + manual selection)
|
||||
- Single API call is cleaner than separate create + add operations
|
||||
|
||||
**File: `internal/handlers/collections.go`** (MODIFY existing)
|
||||
|
||||
**Step 1: Add `ManualBookIDs` field to `CreateCollectionRequest`**
|
||||
|
||||
After line 40, add the new field:
|
||||
|
||||
```go
|
||||
type CreateCollectionRequest struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Description string `json:"description"`
|
||||
Color string `json:"color"`
|
||||
Icon string `json:"icon"`
|
||||
AutoAssignRules []services.Rule `json:"auto_assign_rules"`
|
||||
ViewSettings map[string]interface{} `json:"view_settings"`
|
||||
ManualBookIDs []string `json:"manual_book_ids" validate:"max=50"` // NEW
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Update `CreateCollection` handler**
|
||||
|
||||
Modify the `CreateCollection` function (lines 73-112) to handle manual books:
|
||||
|
||||
```go
|
||||
func (h *CollectionHandler) CreateCollection(c echo.Context) error {
|
||||
user := c.Get("user").(database.Users)
|
||||
userUUID := uuid.UUID(user.ID.Bytes)
|
||||
|
||||
var req CreateCollectionRequest
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request"})
|
||||
}
|
||||
if err := c.Validate(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
collection, err := h.collectionService.CreateCollection(
|
||||
c.Request().Context(),
|
||||
userUUID,
|
||||
req.Name,
|
||||
req.Description,
|
||||
req.Color,
|
||||
req.Icon,
|
||||
req.AutoAssignRules,
|
||||
req.ViewSettings,
|
||||
)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
// NEW: Add manual books if provided
|
||||
if len(req.ManualBookIDs) > 0 {
|
||||
collectionUUID := uuid.UUID(collection.ID.Bytes)
|
||||
addedCount := 0
|
||||
|
||||
for _, bookIDStr := range req.ManualBookIDs {
|
||||
bookID, err := uuid.Parse(bookIDStr)
|
||||
if err != nil {
|
||||
// Skip invalid book IDs, log error
|
||||
c.Logger().Errorf("Invalid book ID %s: %v", bookIDStr, err)
|
||||
continue
|
||||
}
|
||||
|
||||
err = h.collectionService.AddBookToCollection(c.Request().Context(), collectionUUID, bookID, userUUID)
|
||||
if err != nil {
|
||||
// Log error but continue adding other books
|
||||
c.Logger().Errorf("Failed to add book %s to collection: %v", bookIDStr, err)
|
||||
} else {
|
||||
addedCount++
|
||||
}
|
||||
}
|
||||
|
||||
c.Logger().Infof("Added %d/%d manual books to collection %s", addedCount, len(req.ManualBookIDs), collection.Name)
|
||||
}
|
||||
|
||||
bookCount := int32(0)
|
||||
return c.JSON(http.StatusCreated, map[string]interface{}{
|
||||
"id": uuid.UUID(collection.ID.Bytes).String(),
|
||||
"user_id": uuid.UUID(collection.UserID.Bytes).String(),
|
||||
"name": collection.Name,
|
||||
"description": textToString(collection.Description),
|
||||
"color": textToString(collection.Color),
|
||||
"icon": textToString(collection.Icon),
|
||||
"auto_assign_rules": collection.AutoAssignRules,
|
||||
"view_settings": collection.ViewSettings,
|
||||
"book_count": bookCount,
|
||||
"created_at": collection.CreatedAt.Time.String(),
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
**Key Implementation Details:**
|
||||
- ✅ Reuses existing `AddBookToCollection` service method
|
||||
- ✅ Validates request (max 50 book IDs)
|
||||
- ✅ Returns 400 if more than 50 book IDs provided
|
||||
- ✅ Gracefully handles invalid book IDs (skips them, logs error)
|
||||
- ✅ Continues adding remaining books if one fails
|
||||
- ✅ Backward compatible (field is optional)
|
||||
- ✅ No database schema changes needed
|
||||
|
||||
**Validation Rule:**
|
||||
```go
|
||||
// Add to validator in main.go (around line 130)
|
||||
v.RegisterValidation("max", func(fl validator.FieldLevel) bool {
|
||||
field := fl.Field()
|
||||
if field.Kind() != reflect.Slice {
|
||||
return true
|
||||
}
|
||||
return field.Len() <= 50
|
||||
})
|
||||
```
|
||||
|
||||
**Testing:**
|
||||
```bash
|
||||
# Verify compilation
|
||||
go build ./internal/handlers/...
|
||||
|
||||
# Manual test with Bruno
|
||||
cd bruno/collections
|
||||
bru run --env local create-collection-with-manual-books.bru
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### **Phase 5: Bruno API Tests** (1 hour)
|
||||
|
||||
**File: `bruno/dashboard/**`** (update existing tests)
|
||||
@@ -1183,6 +1315,7 @@ type Config struct {
|
||||
LoginTracker *ratelimit.LoginAttemptTracker
|
||||
ScannerHandler *handlers.Handler
|
||||
DashboardService *services.DashboardService // NEW: For dashboard data fetching
|
||||
DashboardHandler *handlers.DashboardHandler // NEW: For dashboard API endpoints
|
||||
}
|
||||
```
|
||||
|
||||
@@ -2750,111 +2883,135 @@ No additional work needed - this section references the preview endpoint added e
|
||||
---
|
||||
|
||||
### **Phase 10.6: Final Integration & Testing** (1 hour)
|
||||
user := c.Get("user").(database.Users)
|
||||
userUUID := uuid.UUID(user.ID.Bytes)
|
||||
|
||||
var req struct {
|
||||
LibraryID string `json:"library_id"`
|
||||
Rules []Rule `json:"rules"`
|
||||
ManualBookIDs []string `json:"manual_book_ids"`
|
||||
Limit int `json:"limit"`
|
||||
}
|
||||
**CRITICAL**: Before proceeding to Phase 11 (Unit Tests), verify all components integrate correctly.
|
||||
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid request"})
|
||||
}
|
||||
#### Verification Checklist
|
||||
|
||||
libUUID, err := uuid.Parse(req.LibraryID)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid library_id"})
|
||||
}
|
||||
**Build Verification:**
|
||||
- [ ] TypeScript modules compile: `npm run build:ts`
|
||||
- Verify: `web/static/dashboard.js` exists
|
||||
- Verify: `web/static/custom-section-builder.js` exists
|
||||
- Check for no compilation errors
|
||||
- [ ] Templates generate successfully: `templ generate --path templates`
|
||||
- Verify: `templates/dashboard_templ.go` exists
|
||||
- Verify: `templates/custom_section_templ.go` exists
|
||||
- [ ] Go build succeeds: `go build ./cmd/server`
|
||||
- Verify: No compilation errors
|
||||
- Check all imports resolve correctly
|
||||
|
||||
if req.Limit <= 0 || req.Limit > 100 {
|
||||
req.Limit = 20
|
||||
}
|
||||
**Bruno API Tests:**
|
||||
- [ ] Dashboard endpoints pass: `cd bruno/dashboard && bru run --env local`
|
||||
- get-sections-success.bru
|
||||
- get-sections-missing-library-id.bru
|
||||
- get-sections-unauthorized.bru
|
||||
- put-preferences-success.bru
|
||||
- restore-system-collection-success.bru
|
||||
- restore-system-collection-invalid-name.bru
|
||||
- [ ] Collections preview tests pass:
|
||||
- preview-collection-success.bru
|
||||
- preview-collection-manual-selection.bru
|
||||
- preview-collection-combined.bru
|
||||
- preview-collection-invalid-library.bru
|
||||
|
||||
// Get all library items
|
||||
allItems, err := h.db.GetLibraryItems(c.Request().Context(), pgtype.UUID{Bytes: libUUID, Valid: true})
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "Failed to load library items"})
|
||||
}
|
||||
**Manual Integration Testing:**
|
||||
- [ ] Dashboard loads successfully
|
||||
- Navigate to `/dashboard?library_id=<valid_uuid>`
|
||||
- Verify 4 system collections appear
|
||||
- Verify collections show books correctly
|
||||
- [ ] Library switching works
|
||||
- Select different library from dropdown
|
||||
- Verify page updates without full reload
|
||||
- Verify loading spinner appears/disappears
|
||||
- [ ] Dashboard settings modal functions
|
||||
- Open settings modal
|
||||
- Toggle collection visibility
|
||||
- Drag to reorder collections
|
||||
- Save preferences
|
||||
- Verify changes persist on page reload
|
||||
- [ ] System collection restore works
|
||||
- Customize a system collection (hide it)
|
||||
- Click "Restore" button
|
||||
- Confirm restoration
|
||||
- Verify collection reappears with defaults
|
||||
- [ ] Custom section builder works end-to-end
|
||||
- Navigate to `/custom-section`
|
||||
- Add filter rules
|
||||
- Search and select books manually
|
||||
- Click "Refresh Preview"
|
||||
- Verify preview shows matching books
|
||||
- Save custom section
|
||||
- Verify section appears on dashboard
|
||||
|
||||
// Evaluate rules for each item
|
||||
var matchedItems []database.MediaItems
|
||||
for _, item := range allItems {
|
||||
evaluations := h.collectionService.EvaluateRules(item, req.Rules)
|
||||
for _, eval := range evaluations {
|
||||
if eval.Matches {
|
||||
matchedItems = append(matchedItems, item)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
**Type Safety Verification:**
|
||||
- [ ] API responses match TypeScript types
|
||||
- Check `is_system` is boolean (not string)
|
||||
- Check `media_item_id` field exists (not `id`)
|
||||
- Verify field names match (`hidden_collections`, `collection_order`)
|
||||
- [ ] No TypeScript type errors
|
||||
- Check browser console for type errors
|
||||
- Verify all API calls use correct field names
|
||||
|
||||
// Add manually selected books
|
||||
for _, bookID := range req.ManualBookIDs {
|
||||
bookUUID, err := uuid.Parse(bookID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
**Database Verification:**
|
||||
- [ ] System collections exist
|
||||
```sql
|
||||
SELECT name, query_type, priority, is_system_collection
|
||||
FROM collections
|
||||
WHERE user_id IS NULL;
|
||||
```
|
||||
Should return 4 rows
|
||||
- [ ] User preferences table exists
|
||||
```sql
|
||||
\d user_dashboard_preferences
|
||||
```
|
||||
Verify all columns present
|
||||
|
||||
for _, item := range allItems {
|
||||
itemUUID, _ := uuid.FromBytes(item.ID.Bytes[0:16])
|
||||
if itemUUID == bookUUID {
|
||||
// Check if already in matched items
|
||||
alreadyAdded := false
|
||||
for _, added := range matchedItems {
|
||||
addedUUID, _ := uuid.FromBytes(added.ID.Bytes[0:16])
|
||||
if addedUUID == bookUUID {
|
||||
alreadyAdded = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !alreadyAdded {
|
||||
matchedItems = append(matchedItems, item)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
**Performance Smoke Test:**
|
||||
- [ ] Dashboard loads within 2 seconds
|
||||
- Test with library containing 100+ items
|
||||
- Verify carousel scrolling is smooth
|
||||
- Check no memory leaks in browser console
|
||||
|
||||
// Limit results
|
||||
if len(matchedItems) > req.Limit {
|
||||
matchedItems = matchedItems[:req.Limit]
|
||||
}
|
||||
**Error Handling Verification:**
|
||||
- [ ] Invalid library_id shows error
|
||||
- [ ] Unauthorized requests return 401
|
||||
- [ ] Network errors show toast notifications
|
||||
- [ ] Empty collections display "No items" message
|
||||
|
||||
// Convert to BookInfo for response
|
||||
var bookCards []BookInfo
|
||||
for _, item := range matchedItems {
|
||||
itemUUID, _ := uuid.FromBytes(item.ID.Bytes[0:16])
|
||||
bookCards = append(bookCards, BookInfo{
|
||||
MediaItemID: itemUUID.String(),
|
||||
Title: item.Title,
|
||||
Author: textToString(item.Author),
|
||||
CoverImagePath: textToString(item.CoverImagePath),
|
||||
})
|
||||
}
|
||||
#### Troubleshooting Common Issues
|
||||
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{"items": bookCards})
|
||||
}
|
||||
```
|
||||
**Issue: Collections not appearing**
|
||||
- Check `show_on_dashboard = true` in database
|
||||
- Verify user hasn't hidden collection in preferences
|
||||
- Check browser console for JavaScript errors
|
||||
|
||||
Add route to `internal/router/dashboard.go`:
|
||||
**Issue: TypeScript compilation fails**
|
||||
- Verify all type definitions in `web/src/types/api.d.ts`
|
||||
- Check import statements use correct paths
|
||||
- Ensure no missing dependencies in `package.json`
|
||||
|
||||
```go
|
||||
// In registerDashboardRoutes function:
|
||||
collections.POST("/preview", cfg.CollectionHandler.PreviewCollection)
|
||||
```
|
||||
**Issue: Templates don't generate**
|
||||
- Verify template syntax is correct
|
||||
- Check for unclosed tags
|
||||
- Run `go install github.com/a-h/templ/cmd/templ@latest` to update templ
|
||||
|
||||
**Key Points**:
|
||||
- ✅ 13+ filter fields provide exceeding flexibility
|
||||
- ✅ Live preview without saving
|
||||
- ✅ Search + multi-select for manual book addition
|
||||
- ✅ AND/OR logic support
|
||||
- ✅ Procedural TypeScript (no OOP)
|
||||
- ✅ TailwindCSS classes only
|
||||
- ✅ Uses shared types (BookInfo from collections.go)
|
||||
- ✅ Inline onclick handlers acceptable per PROJECT_GUIDELINES.md flexibility
|
||||
**Issue: Bruno tests fail**
|
||||
- Verify server is running
|
||||
- Check environment variables in `bruno/.env`
|
||||
- Ensure test database has seed data
|
||||
|
||||
#### Success Criteria
|
||||
|
||||
Phase 10.6 is complete when:
|
||||
- ✅ All builds succeed (Go, TypeScript, Templates)
|
||||
- ✅ All Bruno tests pass
|
||||
- ✅ Manual testing confirms features work
|
||||
- ✅ No console errors in browser
|
||||
- ✅ Dashboard loads within 2 seconds
|
||||
- ✅ Custom section builder creates sections successfully
|
||||
- ✅ System collection restore works
|
||||
|
||||
**IMPORTANT**: Do not proceed to Phase 11 until all verification items pass. Integration issues discovered here are easier to fix before writing comprehensive unit tests.
|
||||
|
||||
---
|
||||
|
||||
@@ -3976,6 +4133,178 @@ bru run --env local
|
||||
- ✅ `media_item_id` field present (not `id`)
|
||||
- ✅ Error cases handled correctly
|
||||
|
||||
#### 12.5 Create Collections Endpoint Tests
|
||||
|
||||
**IMPORTANT**: Tests for CreateCollection with manual_book_ids support
|
||||
|
||||
**File: `bruno/collections/create-collection-with-manual-books.bru`** (NEW)
|
||||
|
||||
```yaml
|
||||
name: Create Collection with Manual Books
|
||||
meta:
|
||||
group: Collections API
|
||||
pre_request: Login as regular user
|
||||
|
||||
req:
|
||||
method: POST
|
||||
url: {{baseUrl}}/api/collections
|
||||
headers:
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: application/json
|
||||
body:
|
||||
name: "Sci-Fi Favorites"
|
||||
description: "My favorite sci-fi books"
|
||||
icon: "🚀"
|
||||
color: "#9333ea"
|
||||
auto_assign_rules:
|
||||
- id: rule1
|
||||
field: genre
|
||||
operator: equals
|
||||
value: Sci-Fi
|
||||
priority: 1
|
||||
manual_book_ids:
|
||||
- {{bookId1}}
|
||||
- {{bookId2}}
|
||||
view_settings: {}
|
||||
|
||||
assertions:
|
||||
- status: 201
|
||||
- jsonpath: "$.id"
|
||||
exists: true
|
||||
- jsonpath: "$.name"
|
||||
equals: "Sci-Fi Favorites"
|
||||
```
|
||||
|
||||
**File: `bruno/collections/create-collection-too-many-books.bru`** (NEW)
|
||||
|
||||
```yaml
|
||||
name: Create Collection - Too Many Manual Books (Validation Test)
|
||||
meta:
|
||||
group: Collections API
|
||||
pre_request: Login as regular user
|
||||
|
||||
req:
|
||||
method: POST
|
||||
url: {{baseUrl}}/api/collections
|
||||
headers:
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: application/json
|
||||
body:
|
||||
name: "Test Collection"
|
||||
manual_book_ids:
|
||||
# Generate 51 book IDs to exceed max limit
|
||||
- {{bookId1}}
|
||||
- {{bookId2}}
|
||||
- {{bookId3}}
|
||||
- {{bookId4}}
|
||||
- {{bookId5}}
|
||||
# ... (total of 51 IDs)
|
||||
|
||||
assertions:
|
||||
- status: 400
|
||||
- jsonpath: "$.error"
|
||||
exists: true
|
||||
```
|
||||
|
||||
**File: `bruno/collections/create-collection-invalid-book-id.bru`** (NEW)
|
||||
|
||||
```yaml
|
||||
name: Create Collection - Invalid Book IDs
|
||||
meta:
|
||||
group: Collections API
|
||||
pre_request: Login as regular user
|
||||
|
||||
req:
|
||||
method: POST
|
||||
url: {{baseUrl}}/api/collections
|
||||
headers:
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: application/json
|
||||
body:
|
||||
name: "Test Collection"
|
||||
manual_book_ids:
|
||||
- invalid-uuid-format
|
||||
- {{bookId1}}
|
||||
- another-invalid-uuid
|
||||
|
||||
assertions:
|
||||
- status: 201
|
||||
- jsonpath: "$.id"
|
||||
exists: true
|
||||
# Collection should be created, valid books added, invalid IDs skipped
|
||||
```
|
||||
|
||||
**File: `bruno/collections/create-collection-rules-only.bru`** (NEW)
|
||||
|
||||
```yaml
|
||||
name: Create Collection - Auto-Assign Rules Only
|
||||
meta:
|
||||
group: Collections API
|
||||
pre_request: Login as regular user
|
||||
|
||||
req:
|
||||
method: POST
|
||||
url: {{baseUrl}}/api/collections
|
||||
headers:
|
||||
Authorization: Bearer {{token}}
|
||||
Content-Type: application/json
|
||||
body:
|
||||
name: "High Rated Books"
|
||||
description: "Books with rating > 4"
|
||||
icon: "⭐"
|
||||
color: "#FFD700"
|
||||
auto_assign_rules:
|
||||
- id: rule1
|
||||
field: rating
|
||||
operator: greater_than
|
||||
value: "4"
|
||||
priority: 1
|
||||
# manual_book_ids not provided (optional field)
|
||||
|
||||
assertions:
|
||||
- status: 201
|
||||
- jsonpath: "$.auto_assign_rules"
|
||||
exists: true
|
||||
```
|
||||
|
||||
**Update Bruno test directory structure**:
|
||||
```
|
||||
bruno/
|
||||
├── dashboard/
|
||||
│ ├── get-sections-success.bru
|
||||
│ ├── get-sections-missing-library-id.bru
|
||||
│ ├── get-sections-invalid-library-id.bru
|
||||
│ ├── get-sections-unauthorized.bru
|
||||
│ ├── put-preferences-success.bru
|
||||
│ ├── put-preferences-unauthorized.bru
|
||||
│ ├── restore-system-collection-success.bru
|
||||
│ ├── restore-system-collection-invalid-name.bru
|
||||
│ ├── restore-system-collection-unauthorized.bru
|
||||
│ ├── preview-collection-success.bru
|
||||
│ ├── preview-collection-manual-selection.bru
|
||||
│ ├── preview-collection-combined.bru
|
||||
│ ├── preview-collection-invalid-library.bru
|
||||
│ └── preview-collection-unauthorized.bru
|
||||
└── collections/ # NEW DIRECTORY
|
||||
├── create-collection-with-manual-books.bru
|
||||
├── create-collection-too-many-books.bru
|
||||
├── create-collection-invalid-book-id.bru
|
||||
├── create-collection-rules-only.bru
|
||||
├── create-collection-unauthorized.bru
|
||||
└── get-collections.bru
|
||||
```
|
||||
|
||||
**Run Bruno tests**:
|
||||
```bash
|
||||
# Test dashboard endpoints
|
||||
cd bruno/dashboard
|
||||
bru run --env local
|
||||
|
||||
# Test collections endpoints
|
||||
cd bruno/collections
|
||||
bru run --env local
|
||||
```
|
||||
|
||||
#### 12.5 Create Collections Preview Tests
|
||||
|
||||
**File: `bruno/dashboard/preview-collection-success.bru`**
|
||||
@@ -4319,7 +4648,106 @@ Key features:
|
||||
- AND/OR logic support for combining rules
|
||||
```
|
||||
|
||||
#### 13.3 User Documentation
|
||||
#### 13.3 Collections API Documentation Update
|
||||
|
||||
**File: `docs/developer/api/collections/create_collection.md`** (UPDATE existing)
|
||||
|
||||
**Add `manual_book_ids` field to request body table:**
|
||||
|
||||
```markdown
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|--------|------|-----------|-------------|
|
||||
| name | string | Yes | Collection name (max 255 chars) |
|
||||
| description | string | No | Collection description |
|
||||
| color | string | No | Hex color code (e.g., "#FF5733") |
|
||||
| icon | string | No | Emoji icon (e.g., "🚀", "📖") |
|
||||
| auto_assign_rules | array | No | Array of rule objects |
|
||||
| manual_book_ids | array | No | Array of book UUIDs to manually add (max 50) |
|
||||
| view_settings | object | No | Per-device display preferences |
|
||||
```
|
||||
|
||||
**Add validation section:**
|
||||
|
||||
```markdown
|
||||
## Validation
|
||||
|
||||
- `manual_book_ids` array is limited to 50 items
|
||||
- Returns `400 Bad Request` if more than 50 book IDs provided
|
||||
- Invalid book UUIDs are skipped (don't prevent collection creation)
|
||||
- Duplicate book IDs are automatically ignored (database constraint)
|
||||
```
|
||||
|
||||
**Add example with manual books:**
|
||||
|
||||
```markdown
|
||||
### Example Request (Auto-Assign Rules + Manual Books)
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Sci-Fi Favorites",
|
||||
"description": "My favorite sci-fi books plus manual picks",
|
||||
"icon": "🚀",
|
||||
"color": "#9333ea",
|
||||
"auto_assign_rules": [
|
||||
{
|
||||
"field": "genre",
|
||||
"operator": "equals",
|
||||
"value": "Sci-Fi",
|
||||
"priority": 1
|
||||
}
|
||||
],
|
||||
"manual_book_ids": [
|
||||
"550e8400-e29b-41d4-a716-446655440000",
|
||||
"550e8400-e29b-41d4-a716-446655440001"
|
||||
],
|
||||
"view_settings": {
|
||||
"kobo": {
|
||||
"view_mode": "grid"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
- You can combine `auto_assign_rules` AND `manual_book_ids`
|
||||
- Manual books are added regardless of whether they match the auto-assign rules
|
||||
- Invalid book IDs are skipped with errors logged
|
||||
- Maximum 50 manual books per collection (UI constraint)
|
||||
```
|
||||
|
||||
**Add error response example:**
|
||||
|
||||
```markdown
|
||||
### Error Responses
|
||||
|
||||
| Code | Description |
|
||||
|------|-------------|
|
||||
| 400 | Invalid request (validation failed, > 50 manual books) |
|
||||
| 400 | Invalid request (validation failed) |
|
||||
| 401 | Authentication required |
|
||||
| 500 | Internal server error |
|
||||
|
||||
**Example: Too Many Manual Books**
|
||||
|
||||
Request:
|
||||
```json
|
||||
{
|
||||
"name": "Test",
|
||||
"manual_book_ids": [ ... 51 book IDs ... ]
|
||||
}
|
||||
```
|
||||
|
||||
Response (400):
|
||||
```json
|
||||
{
|
||||
"error": "Validation failed"
|
||||
}
|
||||
```
|
||||
```
|
||||
|
||||
#### 13.4 User Documentation
|
||||
|
||||
**File: `docs/user/dashboard.md`** (UPDATE existing)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user