docs(03): create Phase 3 execution plan — document migration & multi-user isolation
5 plans across 5 sequential waves covering: Alembic migration 0003 (null-user cleanup, NOT NULL constraint, quota reconciliation), presigned MinIO PUT upload flow with atomic quota enforcement, auth guards on all document/topic endpoints, flat-file settings retirement + per-user AI classification, and frontend quota bar with 3-step XHR upload progress. Verification passed across all 12 dimensions. All 8 phase requirements covered (STORE-03/04/05/06, SEC-04, DOC-03/04/05). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,377 @@
|
||||
---
|
||||
phase: 03-document-migration-multi-user-isolation
|
||||
plan: 04
|
||||
type: execute
|
||||
wave: 4
|
||||
depends_on:
|
||||
- 03-03
|
||||
files_modified:
|
||||
- backend/config.py
|
||||
- backend/services/classifier.py
|
||||
- backend/services/storage.py
|
||||
- backend/tasks/document_tasks.py
|
||||
- backend/api/settings.py
|
||||
- backend/main.py
|
||||
- backend/tests/test_settings.py
|
||||
- frontend/src/views/SettingsView.vue
|
||||
- frontend/src/stores/settings.js
|
||||
- frontend/src/api/client.js
|
||||
autonomous: true
|
||||
requirements:
|
||||
- DOC-03
|
||||
- DOC-05
|
||||
|
||||
must_haves:
|
||||
truths:
|
||||
- "AI provider and model are resolved per document from users.ai_provider / users.ai_model via the Celery task (no flat-file read)"
|
||||
- "Falls back to settings.default_ai_provider / settings.default_ai_model env-var defaults when user.ai_provider is None"
|
||||
- "/api/settings endpoint no longer exists; backend/main.py does not register api.settings router"
|
||||
- "services.storage no longer exposes load_settings/save_settings/mask_api_key/settings_masked; settings.json is no longer read or written"
|
||||
- "classifier.classify_document accepts ai_provider and ai_model parameters; no longer reads global config from disk"
|
||||
- "SettingsView.vue displays an admin-managed message; the form is removed; stores/settings.js and api/client.js settings calls are removed"
|
||||
artifacts:
|
||||
- path: "backend/config.py"
|
||||
provides: "SYSTEM_PROMPT + DEFAULT_AI_PROVIDER + DEFAULT_AI_MODEL settings; SETTINGS_FILE / DEFAULT_SETTINGS / DEFAULT_SYSTEM_PROMPT removed"
|
||||
contains: "default_ai_provider"
|
||||
- path: "backend/services/classifier.py"
|
||||
provides: "classify_document accepts ai_provider, ai_model kwargs; _DEFAULT_SYSTEM_PROMPT module constant"
|
||||
contains: "ai_provider"
|
||||
- path: "backend/services/storage.py"
|
||||
provides: "load_settings/save_settings/mask_api_key/settings_masked removed"
|
||||
contains: "load_topics_for_user"
|
||||
- path: "backend/tasks/document_tasks.py"
|
||||
provides: "_run resolves user.ai_provider via session.get(User, doc.user_id) and passes to classifier"
|
||||
contains: "user.ai_provider"
|
||||
- path: "backend/main.py"
|
||||
provides: "api.settings router import + include_router removed"
|
||||
contains: "settings_router"
|
||||
- path: "backend/api/settings.py"
|
||||
provides: "File deleted"
|
||||
- path: "frontend/src/views/SettingsView.vue"
|
||||
provides: "Admin-managed placeholder card; form/store/api removed"
|
||||
contains: "managed by"
|
||||
- path: "frontend/src/stores/settings.js"
|
||||
provides: "File deleted or gutted to empty placeholder"
|
||||
- path: "frontend/src/api/client.js"
|
||||
provides: "getSettings/patchSettings/testProvider/getDefaultPrompt removed"
|
||||
contains: "getMyQuota"
|
||||
key_links:
|
||||
- from: "backend/tasks/document_tasks.py"
|
||||
to: "backend/db/models.py User"
|
||||
via: "user = await session.get(User, doc.user_id); ai_provider = user.ai_provider or settings.default_ai_provider"
|
||||
pattern: "user\\.ai_provider"
|
||||
- from: "backend/services/classifier.py"
|
||||
to: "backend/ai/__init__.py get_provider"
|
||||
via: "_settings = {'active_provider': ai_provider, 'providers': {ai_provider: {'model': ai_model}}}; provider = get_provider(_settings)"
|
||||
pattern: "active_provider.*ai_provider"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Retire the flat-file settings system end-to-end (D-12, D-13) and wire per-user AI classification via DB lookup (D-14, D-15, DOC-03, DOC-05). Move system prompt + defaults to env vars in config.py. Delete /api/settings; refactor classifier to receive ai_provider/ai_model as parameters; update Celery task to look up doc.user_id → user.ai_provider/ai_model and pass through to classifier. Replace the frontend Settings view with an admin-managed placeholder; remove the settings store and client calls.
|
||||
|
||||
Purpose: Phase 3 SC5 (each user's AI classification uses the provider/model assigned to that user by the admin) cannot pass while load_settings() exists. This is the final backend plan for Phase 3.
|
||||
Output: 10 file modifications (3 backend code, 1 backend route removal, 1 backend test update, 1 backend main.py, 3 frontend code, 1 backend config). After this plan, the entire Phase 3 backend is multi-user-isolated and admin-controlled.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@/Users/nik/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@/Users/nik/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/03-document-migration-multi-user-isolation/03-CONTEXT.md
|
||||
@.planning/phases/03-document-migration-multi-user-isolation/03-RESEARCH.md
|
||||
@.planning/phases/03-document-migration-multi-user-isolation/03-PATTERNS.md
|
||||
@.planning/phases/03-document-migration-multi-user-isolation/03-VALIDATION.md
|
||||
@.planning/phases/03-document-migration-multi-user-isolation/03-03-SUMMARY.md
|
||||
@CLAUDE.md
|
||||
|
||||
@backend/config.py
|
||||
@backend/services/classifier.py
|
||||
@backend/services/storage.py
|
||||
@backend/tasks/document_tasks.py
|
||||
@backend/api/settings.py
|
||||
@backend/main.py
|
||||
@backend/ai/__init__.py
|
||||
@backend/tests/test_settings.py
|
||||
@frontend/src/views/SettingsView.vue
|
||||
@frontend/src/stores/settings.js
|
||||
@frontend/src/api/client.js
|
||||
|
||||
<interfaces>
|
||||
<!-- Contracts the executor needs without re-reading the codebase. -->
|
||||
|
||||
From backend/db/models.py:
|
||||
User.ai_provider: Mapped[Optional[str]] # nullable text — set by admin via PATCH /api/admin/users/{id}/ai-config
|
||||
User.ai_model: Mapped[Optional[str]] # nullable text
|
||||
|
||||
From backend/ai/__init__.py (current — RESEARCH.md Finding 12 / A4):
|
||||
def get_provider(settings: dict) -> AIProvider
|
||||
# Accepts dict with keys: "active_provider": str, "providers": {<name>: {"model": str, "api_key"?: str, "base_url"?: str}}
|
||||
# Branches on active_provider for anthropic / openai / ollama / lmstudio
|
||||
|
||||
From backend/tasks/document_tasks.py (current):
|
||||
async def _run(document_id: str) -> dict
|
||||
— fetches Document by id
|
||||
— calls `topics = await classifier.classify_document(session, document_id)` # no ai_provider/ai_model yet
|
||||
|
||||
From backend/services/classifier.py (post Plan 03-03):
|
||||
async def classify_document(session, doc_id, topic_names=None) -> list[str]
|
||||
— currently calls `settings = storage.load_settings(); provider = get_provider(settings)`
|
||||
— must change to `provider = get_provider({"active_provider": ai_provider, "providers": {ai_provider: {"model": ai_model}}})`
|
||||
|
||||
From backend/config.py (current):
|
||||
SETTINGS_FILE = Path(settings.data_dir) / "settings.json" # to be removed
|
||||
DEFAULT_SYSTEM_PROMPT = "..." # to be removed (kept as module const in classifier.py)
|
||||
DEFAULT_SETTINGS = {...} # to be removed
|
||||
class Settings(BaseSettings): ... # extend with new fields
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<threat_model>
|
||||
## Trust Boundaries
|
||||
|
||||
| Boundary | Description |
|
||||
|----------|-------------|
|
||||
| user → /api/settings | Endpoint removed; previous attack surface eliminated |
|
||||
| Celery task → users table | Task resolves AI config via doc.user_id → users row; no user-controlled provider/model input |
|
||||
| admin → PATCH /api/admin/users/{id}/ai-config | Existing Phase 2 admin endpoint is the only write path to user.ai_provider / user.ai_model |
|
||||
|
||||
## STRIDE Threat Register
|
||||
|
||||
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||
|-----------|----------|-----------|-------------|-----------------|
|
||||
| T-03-17 | Elevation of Privilege | User changing their own AI provider | mitigate | /api/settings is removed (D-12); only PATCH /api/admin/users/{id}/ai-config (admin-only) writes user.ai_provider/ai_model |
|
||||
| T-03-18 | Information Disclosure | settings.json flat file persisted to disk with API keys | mitigate | services.storage.load_settings/save_settings deleted; settings.json no longer read or written; API keys live in env vars only |
|
||||
| T-03-19 | Tampering | Celery task accepting ai_provider in task signature | mitigate | Task signature unchanged (just document_id); ai_provider/ai_model resolved INSIDE the task via DB lookup so a malicious broker message cannot inject provider |
|
||||
| T-03-20 | Information Disclosure | system_prompt env var in container logs | accept | SYSTEM_PROMPT is a static instruction string with no PII; documented as low-sensitivity |
|
||||
| T-03-21 | Repudiation | Frontend SettingsView still attempts old API calls | mitigate | Remove getSettings/patchSettings/testProvider/getDefaultPrompt from api/client.js; gut stores/settings.js; SettingsView shows static message — no API calls |
|
||||
| T-03-SC | Tampering | pip installs | mitigate | No new package installs |
|
||||
</threat_model>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Backend — retire settings flat-file; route classifier through per-user AI config</name>
|
||||
<files>backend/config.py, backend/services/classifier.py, backend/services/storage.py, backend/tasks/document_tasks.py, backend/api/settings.py, backend/main.py, backend/tests/test_settings.py</files>
|
||||
<read_first>
|
||||
- backend/config.py — Settings class fields, SETTINGS_FILE definition, DEFAULT_SYSTEM_PROMPT string, DEFAULT_SETTINGS dict
|
||||
- backend/services/storage.py — load_settings / save_settings / mask_api_key / settings_masked function bodies and __all__ entries (lines ~411-473)
|
||||
- backend/services/classifier.py — current settings load + get_provider call sites (lines 33-35 and 67-69)
|
||||
- backend/tasks/document_tasks.py — _run async body, particularly the classifier.classify_document call (line ~80)
|
||||
- backend/api/settings.py — the entire file (to be deleted)
|
||||
- backend/main.py — settings_router import + include_router call site
|
||||
- backend/ai/__init__.py — get_provider factory signature (RESEARCH.md A4 — verify factory accepts {"active_provider", "providers"} dict)
|
||||
- backend/tests/test_settings.py — existing test file with active tests (to be reduced to test_settings_endpoint_removed)
|
||||
- .planning/phases/03-document-migration-multi-user-isolation/03-RESEARCH.md — Finding 8 (Celery refactor), Finding 11 (retirement scope), Finding 12 (get_provider factory)
|
||||
- .planning/phases/03-document-migration-multi-user-isolation/03-PATTERNS.md — classifier signature (lines ~452-510), config.py additions (lines ~743-752)
|
||||
</read_first>
|
||||
<behavior>
|
||||
- config.py removes `SETTINGS_FILE`, `DEFAULT_SYSTEM_PROMPT`, `DEFAULT_SETTINGS` module-level constants
|
||||
- config.py Settings class gains `system_prompt: str = ""`, `default_ai_provider: str = "ollama"`, `default_ai_model: str = "llama3.2"`
|
||||
- services/classifier.py defines a module-level `_DEFAULT_SYSTEM_PROMPT` constant (the hardcoded string from old config.DEFAULT_SYSTEM_PROMPT). classify_document signature gains `ai_provider: str | None = None, ai_model: str | None = None` keyword params; substitutes config defaults when None; constructs `_settings = {"active_provider": ai_provider, "providers": {ai_provider: {"model": ai_model}}}` and calls `get_provider(_settings)`. NEVER calls `storage.load_settings()`
|
||||
- suggest_topics_for_document gets the same treatment (also gains ai_provider/ai_model kwargs and same fallback)
|
||||
- services/storage.py: removes `load_settings`, `save_settings`, `mask_api_key`, `settings_masked`; removes these from `__all__`; removes `from config import DEFAULT_SETTINGS, SETTINGS_FILE` and any `import json`, `import copy` lines that are no longer used; adds `from config import settings as app_settings` if not already present (Plan 03-04 may not need it, but classifier does)
|
||||
- tasks/document_tasks.py: in `_run` after fetching Document, fetch `user = await session.get(User, doc.user_id)`; compute `ai_provider = (user.ai_provider if user else None) or settings.default_ai_provider`, same for model; pass into `classifier.classify_document(session, document_id, ai_provider=ai_provider, ai_model=ai_model)`. Add `from config import settings` and `from db.models import User` deferred imports inside `_run`
|
||||
- backend/api/settings.py: file deleted
|
||||
- backend/main.py: removes `from api.settings import router as settings_router` and `app.include_router(settings_router)`
|
||||
- backend/tests/test_settings.py: replaces all existing tests with the single `test_settings_endpoint_removed` (the xfail stub from Plan 03-01) — keep file path. Mark previous tests as xfail or simply delete them; the only required test is the 404 assertion
|
||||
- GET /api/settings returns 404 (route no longer exists)
|
||||
- The Plan 03-01 stub tests test_per_user_provider, test_celery_task_uses_user_provider, test_default_provider_fallback, test_settings_endpoint_removed transition from xfail → pass
|
||||
</behavior>
|
||||
<action>
|
||||
Modify `backend/config.py`:
|
||||
1. Remove `SETTINGS_FILE = Path(settings.data_dir) / "settings.json"` (line 60).
|
||||
2. Remove the `DEFAULT_SYSTEM_PROMPT = """..."""` block (lines 62-67).
|
||||
3. Remove the `DEFAULT_SETTINGS = {...}` dict (lines 69-91).
|
||||
4. Inside the `Settings` class (alongside existing fields), add:
|
||||
```
|
||||
# AI classification defaults (Phase 3 — D-13, D-15)
|
||||
system_prompt: str = "" # SYSTEM_PROMPT env var; hardcoded fallback lives in classifier.py
|
||||
default_ai_provider: str = "ollama" # DEFAULT_AI_PROVIDER env var
|
||||
default_ai_model: str = "llama3.2" # DEFAULT_AI_MODEL env var
|
||||
```
|
||||
5. Verify no module-level imports of `Path` or anything else remain unused after the deletions (clean up `from pathlib import Path` if no other consumers).
|
||||
|
||||
Modify `backend/services/classifier.py`:
|
||||
1. At module top, add `_DEFAULT_SYSTEM_PROMPT = """You are a document classification assistant..."""` with the verbatim string from the old `config.DEFAULT_SYSTEM_PROMPT`.
|
||||
2. Add imports `import uuid` and `from db.models import Document` and `from config import settings as app_settings`.
|
||||
3. Modify `classify_document` signature:
|
||||
```
|
||||
async def classify_document(
|
||||
session: AsyncSession,
|
||||
doc_id: str,
|
||||
topic_names: list[str] | None = None,
|
||||
ai_provider: str | None = None,
|
||||
ai_model: str | None = None,
|
||||
) -> list[str]:
|
||||
```
|
||||
Replace `settings = storage.load_settings(); system_prompt = settings.get("system_prompt", ""); provider = get_provider(settings)` with:
|
||||
```
|
||||
_ai_provider = ai_provider or app_settings.default_ai_provider
|
||||
_ai_model = ai_model or app_settings.default_ai_model
|
||||
system_prompt = app_settings.system_prompt or _DEFAULT_SYSTEM_PROMPT
|
||||
_settings = {
|
||||
"active_provider": _ai_provider,
|
||||
"providers": {_ai_provider: {"model": _ai_model}},
|
||||
}
|
||||
provider = get_provider(_settings)
|
||||
```
|
||||
4. Modify `suggest_topics_for_document` with the same signature change (`ai_provider`, `ai_model` kwargs) and same `_settings` construction in place of `storage.load_settings()`.
|
||||
5. Ensure no remaining `storage.load_settings()` call exists anywhere in this file.
|
||||
|
||||
Modify `backend/services/storage.py`:
|
||||
1. Remove the import `from config import DEFAULT_SETTINGS, SETTINGS_FILE` (line 36) — replace with nothing (config import is no longer needed in this module).
|
||||
2. Remove `import copy` and `import json` from top imports (no longer needed).
|
||||
3. Remove `load_settings()`, `save_settings()`, `mask_api_key()`, `settings_masked()` function definitions (lines ~416-449).
|
||||
4. Remove these from `__all__`: `"load_settings", "save_settings", "mask_api_key", "settings_masked"`.
|
||||
|
||||
Modify `backend/tasks/document_tasks.py` `_run` function:
|
||||
1. After `doc = await session.get(Document, doc_uuid)` and the None check, insert:
|
||||
```
|
||||
from db.models import User
|
||||
from config import settings as app_settings
|
||||
user = await session.get(User, doc.user_id) if doc.user_id else None
|
||||
ai_provider = (user.ai_provider if user else None) or app_settings.default_ai_provider
|
||||
ai_model = (user.ai_model if user else None) or app_settings.default_ai_model
|
||||
```
|
||||
2. Replace `topics = await classifier.classify_document(session, document_id)` with `topics = await classifier.classify_document(session, document_id, ai_provider=ai_provider, ai_model=ai_model)`.
|
||||
|
||||
Delete `backend/api/settings.py` entirely. Use the `rm` shell equivalent or write an empty stub — preferable: delete the file. (Executor: use `git rm backend/api/settings.py` or `os.remove` then commit.)
|
||||
|
||||
Modify `backend/main.py`:
|
||||
1. Remove `from api.settings import router as settings_router` (line 18).
|
||||
2. Remove `app.include_router(settings_router)` (line 174).
|
||||
|
||||
Modify `backend/tests/test_settings.py`:
|
||||
1. Delete all existing tests (they reference the removed endpoint and storage functions).
|
||||
2. Keep only the single test from Plan 03-01:
|
||||
```
|
||||
async def test_settings_endpoint_removed(async_client):
|
||||
"""D-12: /api/settings endpoint is removed in Phase 3."""
|
||||
resp = await async_client.get("/api/settings")
|
||||
assert resp.status_code == 404
|
||||
```
|
||||
Remove the `@pytest.mark.xfail` marker — this test should now pass green.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd backend && pytest tests/test_settings.py tests/test_classifier.py::test_per_user_provider tests/test_classifier.py::test_celery_task_uses_user_provider tests/test_classifier.py::test_default_provider_fallback -x -q 2>&1 | tail -30 && test ! -f backend/api/settings.py && echo "settings.py deleted" && ! grep -q "load_settings" backend/services/storage.py && echo "load_settings removed" && grep -c "default_ai_provider" backend/config.py && grep -c "_DEFAULT_SYSTEM_PROMPT" backend/services/classifier.py && grep -c "user.ai_provider" backend/tasks/document_tasks.py</automated>
|
||||
</verify>
|
||||
<done>
|
||||
`backend/api/settings.py` file does not exist. `backend/services/storage.py` contains no `load_settings`, `save_settings`, `mask_api_key`, `settings_masked` (grep returns 0). `backend/config.py` contains `default_ai_provider` and `default_ai_model`. `backend/services/classifier.py` contains `_DEFAULT_SYSTEM_PROMPT` and accepts `ai_provider` kwarg. `backend/tasks/document_tasks.py` contains `user.ai_provider`. test_settings_endpoint_removed and the 3 classifier tests pass.
|
||||
</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: Frontend — strip settings store/API and replace SettingsView with admin-managed placeholder</name>
|
||||
<files>frontend/src/views/SettingsView.vue, frontend/src/stores/settings.js, frontend/src/api/client.js</files>
|
||||
<read_first>
|
||||
- frontend/src/views/SettingsView.vue — current form structure; replace entirely with placeholder card
|
||||
- frontend/src/stores/settings.js — actions to remove (fetchSettings, save, testConnection, resetPrompt)
|
||||
- frontend/src/api/client.js — getSettings, patchSettings, testProvider, getDefaultPrompt functions (lines ~110-132)
|
||||
- frontend/src/router/index.js — verify /settings route still exists (do not remove — UI-SPEC Risk 6 says keep route with placeholder)
|
||||
- .planning/phases/03-document-migration-multi-user-isolation/03-UI-SPEC.md — Phase 3 spec inherits Phase 2 design system; no specific SettingsView spec — use minimal card matching existing card style
|
||||
- .planning/phases/03-document-migration-multi-user-isolation/03-RESEARCH.md — Finding 11 (frontend retirement scope), Risk 6 (UX regression mitigation)
|
||||
</read_first>
|
||||
<behavior>
|
||||
- frontend/src/views/SettingsView.vue is replaced with a static placeholder: a heading "AI Configuration" and a card explaining "AI configuration is managed by your admin." Add a secondary link or text directing users to contact their admin. No form, no API calls, no useSettingsStore import
|
||||
- frontend/src/stores/settings.js is reduced to an empty defineStore that exports no actions, OR deleted entirely. Plan: delete the file and update any imports (SettingsView no longer imports it; check if anything else imports it — grep). If other files import it, gut the file to a minimal noop store
|
||||
- frontend/src/api/client.js removes the four functions: getSettings, patchSettings, testProvider, getDefaultPrompt (lines ~110-132 in current file). Adds a new export getMyQuota() that hits GET /api/auth/me/quota (used by QuotaBar in Plan 03-05 — pre-emptively add here so Plan 03-05 is a pure UI plan)
|
||||
- Visiting /settings in the browser shows the admin-managed placeholder without any console errors
|
||||
- SettingsView.vue uses only the Phase 2 design system (existing classes; text-sm / text-xl / bg-white / border-gray-200 / rounded-xl) — UI-SPEC inheritance
|
||||
</behavior>
|
||||
<action>
|
||||
Rewrite `frontend/src/views/SettingsView.vue` with the placeholder content. Template structure:
|
||||
```vue
|
||||
<template>
|
||||
<div class="p-8 max-w-3xl mx-auto">
|
||||
<h2 class="text-2xl font-semibold text-gray-900 mb-1">Settings</h2>
|
||||
<p class="text-sm text-gray-500 mb-8">Account-level options for your DocuVault workspace.</p>
|
||||
|
||||
<section class="bg-white border border-gray-200 rounded-xl p-6">
|
||||
<h3 class="text-xl font-semibold text-gray-800 mb-2">AI configuration</h3>
|
||||
<p class="text-sm text-gray-600">
|
||||
AI provider and model are managed by your administrator. Contact your admin
|
||||
to request changes to which AI provider is used for your documents.
|
||||
</p>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// SettingsView is a static placeholder after Phase 3 D-12 settings retirement.
|
||||
// No store usage, no API calls — AI config is admin-only via /api/admin/users/{id}/ai-config.
|
||||
</script>
|
||||
```
|
||||
Remove any `<style>` blocks if present in the original file.
|
||||
|
||||
Delete `frontend/src/stores/settings.js`. Before deleting, grep the frontend tree for imports of `'../stores/settings.js'` or `'@/stores/settings.js'`. If any consumer exists outside SettingsView.vue, instead gut the file to:
|
||||
```js
|
||||
// stores/settings.js was retired in Phase 3 D-12 — kept as a minimal no-op to avoid breaking imports.
|
||||
// Remove this file in a future cleanup once all consumers are updated.
|
||||
import { defineStore } from 'pinia'
|
||||
export const useSettingsStore = defineStore('settings', () => {
|
||||
return {}
|
||||
})
|
||||
```
|
||||
Otherwise delete the file.
|
||||
|
||||
Modify `frontend/src/api/client.js`:
|
||||
1. Remove the four functions in the `// ── Settings ──` section: `getSettings`, `patchSettings`, `testProvider`, `getDefaultPrompt` (lines ~110-132). Remove the section header comment too.
|
||||
2. Add a new function for the quota endpoint (used by Plan 03-05's QuotaBar). Place it under a new `// ── Quota ──` section (or under `// ── Auth ──`):
|
||||
```js
|
||||
export function getMyQuota() {
|
||||
return request('/api/auth/me/quota')
|
||||
}
|
||||
```
|
||||
3. Add the upload-flow API helpers used by Plan 03-05:
|
||||
```js
|
||||
export function getUploadUrl(filename, contentType) {
|
||||
return request('/api/documents/upload-url', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ filename, content_type: contentType }),
|
||||
})
|
||||
}
|
||||
|
||||
export function confirmUpload(documentId) {
|
||||
return request(`/api/documents/${documentId}/confirm`, { method: 'POST' })
|
||||
}
|
||||
```
|
||||
Place these under the `// ── Documents ──` section near the existing `uploadDocument` function. Leave `uploadDocument` in place for now (Plan 03-05 will remove it when the documents store no longer calls it).
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd frontend && grep -c "managed by your administrator" src/views/SettingsView.vue && ! grep -q "useSettingsStore" src/views/SettingsView.vue && ! grep -q "getSettings\|patchSettings\|testProvider\|getDefaultPrompt" src/api/client.js && grep -c "getMyQuota\|getUploadUrl\|confirmUpload" src/api/client.js</automated>
|
||||
</verify>
|
||||
<done>
|
||||
SettingsView.vue is the placeholder card (no form, no store). api/client.js has getMyQuota + getUploadUrl + confirmUpload exports and no getSettings/patchSettings/testProvider/getDefaultPrompt. stores/settings.js is deleted OR gutted to a no-op store. Visiting /settings in the dev server renders the card with no console errors (manual check during Plan 03-05 verification — not required here).
|
||||
</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
- Settings endpoint removed: `cd backend && pytest tests/test_settings.py::test_settings_endpoint_removed -x -q`
|
||||
- Per-user AI classification: `cd backend && pytest tests/test_classifier.py::test_per_user_provider tests/test_classifier.py::test_celery_task_uses_user_provider tests/test_classifier.py::test_default_provider_fallback -x -q`
|
||||
- No load_settings reference remains: `cd backend && grep -rn 'load_settings\|save_settings' backend --include='*.py' | grep -v tests/` returns no hits
|
||||
- Frontend settings imports clean: `cd frontend && grep -rn 'getSettings\|patchSettings\|testProvider\|getDefaultPrompt' src/` returns no hits
|
||||
- All Phase 3 tests still green: `cd backend && pytest -x -q`
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- backend/api/settings.py deleted
|
||||
- backend/main.py no longer registers settings_router
|
||||
- backend/services/storage.py no longer contains load_settings/save_settings/mask_api_key/settings_masked
|
||||
- backend/services/classifier.py classify_document accepts ai_provider and ai_model kwargs; reads no flat file
|
||||
- backend/tasks/document_tasks.py resolves user.ai_provider via DB lookup and passes to classifier
|
||||
- backend/config.py exposes system_prompt, default_ai_provider, default_ai_model fields and no longer defines SETTINGS_FILE/DEFAULT_SETTINGS/DEFAULT_SYSTEM_PROMPT
|
||||
- frontend/src/views/SettingsView.vue shows the admin-managed placeholder
|
||||
- frontend/src/api/client.js exports getMyQuota, getUploadUrl, confirmUpload and no longer exports settings functions
|
||||
- All Plan 03-01 stub tests for DOC-03 / DOC-05 / D-12 pass
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
Create `.planning/phases/03-document-migration-multi-user-isolation/03-04-SUMMARY.md` when done — document the final classifier signature and the env var defaults; note that Phase 4 will keep /settings as a placeholder until a dedicated storage settings page lands.
|
||||
</output>
|
||||
Reference in New Issue
Block a user