Redesign doc service UX for scale + add group-based document sharing
- Three-column layout: Sidebar + SourcePanel (views + searchable category tree) + main - DocumentSlideOver (480px right panel): inline editing, type picker, AI suggestion confirm/reject, categories combobox, tags editor, sharing section, raw text, re-analyse/delete actions - ManageCategoriesDialog: inline rename, delete with confirm, search filter - DocumentsPage rewrite: filter chip system, multi-file upload queue, drag-and-drop overlay, bulk actions bar (share/delete), smart TanStack Query polling, URL-driven view state - Sidebar simplified: per-category NavLinks removed; Documents = single NavLink under Apps - Backend: document_shares table (migration 0004), share CRUD endpoints, shared-with-me view, N+1-safe share_count via GROUP BY, recipient download access, X-User-Groups header enforcement - Gateway proxy: injects X-User-Groups header into all document + category proxy requests - Backend users: GET /api/users/me/groups endpoint for share picker combobox - CLAUDE.md, STATUS.md files, and changelog updated Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -84,7 +84,7 @@ docker compose up --build -d
|
||||
│ ├── app/
|
||||
│ │ ├── main.py ← App factory, router registration, lifespan (health loop)
|
||||
│ │ ├── database.py ← AsyncEngine, AsyncSessionLocal, Base
|
||||
│ │ ├── deps.py ← get_current_user, get_current_admin, get_service_admin(id), check_plugin_access
|
||||
│ │ ├── deps.py ← get_current_user, get_current_admin, get_service_admin(id), check_plugin_access (also get_user_groups in doc-service)
|
||||
│ │ ├── core/
|
||||
│ │ │ ├── config.py ← All settings via pydantic-settings (reads .env)
|
||||
│ │ │ ├── security.py ← JWT sign/verify (RS256), bcrypt hash/verify
|
||||
@@ -101,7 +101,7 @@ docker compose up --build -d
|
||||
│ │ │ └── group.py ← GroupCreate/Update/Out/DetailOut, GroupMemberOut
|
||||
│ │ ├── routers/
|
||||
│ │ │ ├── auth.py ← POST /register, POST /login
|
||||
│ │ │ ├── users.py ← GET /me, GET+PATCH /me/preferences, PATCH /me/color-mode
|
||||
│ │ │ ├── users.py ← GET /me, GET+PATCH /me/preferences, PATCH /me/color-mode, GET /me/groups
|
||||
│ │ │ ├── profile.py ← GET+PUT /me (profile)
|
||||
│ │ │ ├── admin.py ← User admin CRUD (admin-only)
|
||||
│ │ │ ├── groups.py ← Group CRUD + member management (admin-only)
|
||||
@@ -139,16 +139,18 @@ docker compose up --build -d
|
||||
│ ├── app/
|
||||
│ │ ├── main.py ← FastAPI, lifespan (file watcher start/stop)
|
||||
│ │ ├── database.py ← Same PostgreSQL instance as backend
|
||||
│ │ ├── deps.py ← get_user_id (reads x-user-id header)
|
||||
│ │ ├── deps.py ← get_user_id (x-user-id), get_user_groups (x-user-groups)
|
||||
│ │ ├── models/
|
||||
│ │ │ ├── document.py ← Document model (see Database Models)
|
||||
│ │ │ ├── category.py ← DocumentCategory model
|
||||
│ │ │ └── category_assignment.py ← CategoryAssignment (composite PK)
|
||||
│ │ │ ├── category_assignment.py ← CategoryAssignment (composite PK)
|
||||
│ │ │ └── document_share.py ← DocumentShare model (group-based sharing)
|
||||
│ │ ├── schemas/
|
||||
│ │ │ ├── document.py ← DocumentOut, DocumentPage, DocumentStatusOut, etc.
|
||||
│ │ │ └── category.py ← CategoryOut, CategoryCreate, CategoryUpdate
|
||||
│ │ │ ├── category.py ← CategoryOut, CategoryCreate, CategoryUpdate
|
||||
│ │ │ └── share.py ← DocumentShareOut, DocumentShareCreate, SharedDocumentOut
|
||||
│ │ ├── routers/
|
||||
│ │ │ ├── documents.py ← Full document CRUD + file serving + reprocess + suggestion endpoints
|
||||
│ │ │ ├── documents.py ← Full CRUD + file serving + reprocess + suggestions + sharing
|
||||
│ │ │ ├── categories.py ← Category CRUD (includes watch-owned categories)
|
||||
│ │ │ └── plugin.py ← GET /plugin/manifest, GET+PATCH /plugin/settings
|
||||
│ │ └── services/
|
||||
@@ -157,7 +159,8 @@ docker compose up --build -d
|
||||
│ │ ├── config_reader.py ← Config load/save including storage/watch settings
|
||||
│ │ └── file_watcher.py ← watchdog-based PDF watcher + startup scan + ingestion
|
||||
│ ├── alembic/versions/ ← Doc-service migration chain
|
||||
│ │ └── 0003_add_watch_columns.py ← source, watch_path, suggested_folder, suggested_filename
|
||||
│ │ ├── 0003_add_watch_columns.py ← source, watch_path, suggested_folder, suggested_filename
|
||||
│ │ └── 0004_add_document_shares.py ← document_shares table (group-based sharing)
|
||||
│ ├── Dockerfile
|
||||
│ └── STATUS.md
|
||||
│
|
||||
@@ -170,8 +173,11 @@ docker compose up --build -d
|
||||
│ │ ├── useAuth.ts ← Token state (localStorage), login/logout
|
||||
│ │ └── useTheme.ts ← Theme toggle
|
||||
│ ├── components/
|
||||
│ │ ├── AppShell.tsx ← Layout: Sidebar + scrollable main
|
||||
│ │ ├── AppShell.tsx ← Layout: Sidebar + SourcePanel (on /apps/documents) + main
|
||||
│ │ ├── Sidebar.tsx ← Collapsible nav (icons ↔ icons+labels)
|
||||
│ │ ├── SourcePanel.tsx ← Views + searchable category tree (docs route only)
|
||||
│ │ ├── ManageCategoriesDialog.tsx ← Category CRUD modal (rename, delete)
|
||||
│ │ ├── DocumentSlideOver.tsx ← Right slide-over: detail, edit, share, AI suggestions
|
||||
│ │ ├── ThemeToggle.tsx ← Light/dark mode toggle
|
||||
│ │ ├── PluginSchemaForm.tsx ← JSON Schema → React form (boolean/string/number/readOnly)
|
||||
│ │ └── ui/ ← shadcn/ui components (Button, Input, …)
|
||||
@@ -205,8 +211,8 @@ Browser (:5173 dev / :80 prod)
|
||||
/users /groups /documents/categories/*
|
||||
/profile /settings
|
||||
/services │ │
|
||||
JSON volume proxy (injects x-user-id)
|
||||
(/config) │
|
||||
JSON volume proxy (injects x-user-id,
|
||||
(/config) x-user-groups) │
|
||||
doc-service:8001
|
||||
│
|
||||
ai-service:8010
|
||||
@@ -314,6 +320,18 @@ Unique constraint: `(group_id, user_id)`
|
||||
| `document_id` | String | PK + FK→documents.id CASCADE |
|
||||
| `category_id` | String | PK + FK→document_categories.id CASCADE |
|
||||
|
||||
**`document_shares`**
|
||||
|
||||
| Column | Type | Constraints | Notes |
|
||||
|--------|------|-------------|-------|
|
||||
| `id` | String | PK, UUID | |
|
||||
| `document_id` | String | indexed, NOT NULL | not FK — trusts proxy |
|
||||
| `group_id` | String | indexed, NOT NULL | group from backend |
|
||||
| `shared_by_user_id` | String | NOT NULL | owner who shared |
|
||||
| `created_at` | DateTime(tz) | server_default=now() | |
|
||||
|
||||
Unique constraint: `(document_id, group_id)`
|
||||
|
||||
### Migration chains
|
||||
|
||||
**Backend** (must be applied in order):
|
||||
@@ -333,6 +351,7 @@ Unique constraint: `(group_id, user_id)`
|
||||
| `0001` | `create_doc_tables` |
|
||||
| `0002` | `add_document_title` |
|
||||
| `0003` | `add_watch_columns` |
|
||||
| `0004` | `add_document_shares` |
|
||||
|
||||
---
|
||||
|
||||
@@ -353,6 +372,7 @@ Unique constraint: `(group_id, user_id)`
|
||||
| GET | `/api/users/me/preferences` | user | Dashboard pinned app IDs → `{app_ids}` |
|
||||
| PATCH | `/api/users/me/preferences` | user | Save pinned app IDs (max 50, slug-safe) |
|
||||
| PATCH | `/api/users/me/color-mode` | user | Save colour mode preference ("light"/"dark"/"system") |
|
||||
| GET | `/api/users/me/groups` | user | Groups current user belongs to → `list[UserGroupOut]` |
|
||||
|
||||
### Profile (`/api/profile`) — authenticated
|
||||
|
||||
@@ -426,6 +446,10 @@ Unique constraint: `(group_id, user_id)`
|
||||
| POST | `/api/documents/{id}/suggestions/folder/reject` | Reject AI folder suggestion |
|
||||
| POST | `/api/documents/{id}/suggestions/filename/confirm` | Confirm AI filename suggestion |
|
||||
| POST | `/api/documents/{id}/suggestions/filename/reject` | Reject AI filename suggestion |
|
||||
| GET | `/api/documents/shared-with-me` | Documents shared with current user via their groups |
|
||||
| GET | `/api/documents/{id}/shares` | List groups the document is shared with (owner only) |
|
||||
| POST | `/api/documents/{id}/shares` | Share with a group (owner only; group must be in user's groups) |
|
||||
| DELETE | `/api/documents/{id}/shares/{group_id}` | Stop sharing with a group (owner only) |
|
||||
|
||||
### Categories (`/api/documents/categories/*`) — authenticated, proxied to doc-service
|
||||
|
||||
@@ -596,7 +620,10 @@ Adding a new API call:
|
||||
["dashboard-prefs"] // user dashboard preferences
|
||||
["categories"] // document categories
|
||||
["documents", params] // document list (params object for cache isolation)
|
||||
["documents-shared", params] // shared-with-me list
|
||||
["document", id] // single document
|
||||
["document-shares", id] // share list for a specific document
|
||||
["my-groups"] // current user's group memberships (for share picker)
|
||||
["plugins"] // accessible plugin list (filtered by user access)
|
||||
["plugin-manifest", id] // plugin manifest (cached)
|
||||
["plugin-settings", id] // plugin current settings
|
||||
|
||||
Reference in New Issue
Block a user