feat(dashboard): implement Phase 10.5 Custom Section Builder
Phase 10.5.1: Add /custom-section frontend route - Added route handler in internal/router/frontend.go - Fetches user libraries and renders custom section builder template Phase 10.5.2: Create custom section builder template - Created templates/custom_section.templ with full UI - Includes section details form, filter rules builder, manual book selection - Live preview functionality with preview container - Form actions for save/cancel Phase 10.5.3: Create custom-section-builder TypeScript - Created web/src/custom-section-builder.ts with 13+ filter fields - Filter fields: title, author, genre, series, progress, rating, date_added, last_read, publisher, language, format, tags, narrators - Procedural/imperative style (no OOP) as per guidelines - Rule builder with AND/OR logic support - Book search and multi-select functionality - Live preview via /api/collections/preview endpoint - Form validation and submission to /api/collections Phase 10.5.4: Build TypeScript modules - Compiled custom-section-builder.ts to web/static/custom-section-builder.js - Verified successful compilation with no errors - All existing TypeScript modules continue to compile Phase 10.5.5: Add Bruno tests for custom section creation - create-custom-section-rules.bru: Test creating section with filter rules - create-custom-section-manual.bru: Test creating section with manual book selection - create-custom-section-missing-fields.bru: Test error handling for missing required fields Phase 10.6: Build Verification - ✅ TypeScript modules compile successfully - ✅ Templates generate successfully - ✅ Go build succeeds with no compilation errors - ✅ All build artifacts verified (dashboard.js, custom-section-builder.js, dashboard_templ.go, custom_section_templ.go) This completes the Custom Section Builder feature, allowing users to create personalized dashboard sections with flexible filter rules or manual book selection.
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
meta:
|
||||
name: Create Custom Section with Manual Books
|
||||
type: http
|
||||
seq: 2
|
||||
http:
|
||||
method: POST
|
||||
url: '{{base_url}}/api/collections'
|
||||
auth: inherit
|
||||
body:
|
||||
type: json
|
||||
jsonBody: "{\n \"library_id\": \"{{library_id}}\",\n \"name\": \"My Favorites\"\
|
||||
,\n \"icon\": \"⭐\",\n \"description\": \"My favorite books\",\n \"show_on_dashboard\"\
|
||||
: true,\n \"auto_assign_rules\": \"\",\n \"manual_book_ids\": [\"{{media_item_id}}\"\
|
||||
],\n \"match_type\": \"all\"\n}"
|
||||
runtime:
|
||||
scripts:
|
||||
- type: tests
|
||||
code: "test(\"status must be 201 with valid request\", function() {\n expect(res.status).to.eql(201);\n\
|
||||
});\n\ntest(\"response contains collection id\", function() {\n expect(res.body.id).to.exist;\n\
|
||||
expect(res.body.id).to.be.a('string');\n });\n\ntest(\"response contains\
|
||||
\ collection name\", function() {\n expect(res.body.name).to.eql(\"My Favorites\"\
|
||||
);\n });"
|
||||
|
||||
docs: |-
|
||||
Create a custom collection with manually selected books.
|
||||
|
||||
**Endpoint**: POST /api/collections
|
||||
**Auth**: Required (Bearer token)
|
||||
|
||||
## Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"library_id": "cc23c3a7-f8fb-451a-a78d-2a16df1b725a",
|
||||
"name": "My Favorites",
|
||||
"icon": "⭐",
|
||||
"description": "My favorite books",
|
||||
"show_on_dashboard": true,
|
||||
"auto_assign_rules": "",
|
||||
"manual_book_ids": ["book-id-1", "book-id-2"],
|
||||
"match_type": "all"
|
||||
}
|
||||
```
|
||||
|
||||
## Response
|
||||
|
||||
Returns the created collection with generated ID.
|
||||
@@ -0,0 +1,23 @@
|
||||
meta:
|
||||
name: Create Custom Section Missing Required Fields
|
||||
type: http
|
||||
seq: 3
|
||||
http:
|
||||
method: POST
|
||||
url: '{{base_url}}/api/collections'
|
||||
auth: inherit
|
||||
body:
|
||||
type: json
|
||||
jsonBody: "{\n \"library_id\": \"{{library_id}}\",\n \"icon\": \"📚\"\
|
||||
\n}"
|
||||
runtime:
|
||||
scripts:
|
||||
- type: tests
|
||||
code: "test(\"status must be 400 with missing name\", function() {\n expect(res.status).to.eql(400);\n\
|
||||
});\n\ntest(\"response contains error message\", function() {\n expect(res.body.error).to.exist;\n\
|
||||
\ });"
|
||||
|
||||
docs: |-
|
||||
Test error handling when creating a collection without required fields.
|
||||
|
||||
**Expected Response**: 400 Bad Request with error message.
|
||||
@@ -0,0 +1,64 @@
|
||||
meta:
|
||||
name: Create Custom Section with Filter Rules
|
||||
type: http
|
||||
seq: 1
|
||||
http:
|
||||
method: POST
|
||||
url: '{{base_url}}/api/collections'
|
||||
auth: inherit
|
||||
body:
|
||||
type: json
|
||||
jsonBody: "{\n \"library_id\": \"{{library_id}}\",\n \"name\": \"Science Fiction\
|
||||
\ Books\",\n \"icon\": \"🚀\",\n \"description\": \"All my sci-fi books\",\n\
|
||||
\ \"show_on_dashboard\": true,\n \"auto_assign_rules\": \"{\\\"rules\\\":[{\\\
|
||||
\"id\\\":\\\"rule1\\\",\\\"field\\\":\\\"genre\\\",\\\"operator\\\":\\\"equals\\\"\
|
||||
,\\\"value\\\":\\\"Sci-Fi\\\",\\\"priority\\\":1}]}\",\n \"manual_book_ids\"\
|
||||
: [],\n \"match_type\": \"all\"\n}"
|
||||
runtime:
|
||||
scripts:
|
||||
- type: tests
|
||||
code: "test(\"status must be 201 with valid request\", function() {\n expect(res.status).to.eql(201);\n\
|
||||
});\n\ntest(\"response contains collection id\", function() {\n expect(res.body.id).to.exist;\n\
|
||||
expect(res.body.id).to.be.a('string');\n });\n\ntest(\"response contains\
|
||||
\ collection name\", function() {\n expect(res.body.name).to.eql(\"Science\
|
||||
\ Fiction Books\");\n });\n\ntest(\"response contains is_system false\", function()\
|
||||
\ {\n expect(res.body.is_system).to.exist;\n expect(res.body.is_system).to.eql(false);\n\
|
||||
\ });"
|
||||
|
||||
docs: |-
|
||||
Create a custom collection with filter rules.
|
||||
|
||||
**Endpoint**: POST /api/collections
|
||||
**Auth**: Required (Bearer token)
|
||||
|
||||
## Request Body
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| library_id | string | Yes | Library UUID |
|
||||
| name | string | Yes | Collection name |
|
||||
| icon | string | No | Emoji icon |
|
||||
| description | string | No | Collection description |
|
||||
| show_on_dashboard | boolean | No | Show on dashboard (default: true) |
|
||||
| auto_assign_rules | string | No | JSON string of filter rules |
|
||||
| manual_book_ids | array | No | Array of book IDs |
|
||||
| match_type | string | No | "all" (AND) or "any" (OR) |
|
||||
|
||||
## Example Request
|
||||
|
||||
```json
|
||||
{
|
||||
"library_id": "cc23c3a7-f8fb-451a-a78d-2a16df1b725a",
|
||||
"name": "Science Fiction Books",
|
||||
"icon": "🚀",
|
||||
"description": "All my sci-fi books",
|
||||
"show_on_dashboard": true,
|
||||
"auto_assign_rules": "{\"rules\":[{\"id\":\"rule1\",\"field\":\"genre\",\"operator\":\"equals\",\"value\":\"Sci-Fi\",\"priority\":1}]}",
|
||||
"manual_book_ids": [],
|
||||
"match_type": "all"
|
||||
}
|
||||
```
|
||||
|
||||
## Response
|
||||
|
||||
Returns the created collection with generated ID.
|
||||
Reference in New Issue
Block a user