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:
curo1305
2026-04-18 12:46:43 +02:00
parent 08e7caac4c
commit 94901fc30f
23 changed files with 2603 additions and 900 deletions
+37 -10
View File
@@ -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