7.7 KiB
7.7 KiB
Phase 12: Cloud Resource Foundation - Pattern Map
Mapped: 2026-06-18 Status: Complete
Purpose
Map Phase 12 files and data flow to existing DocuVault patterns so implementation extends established architecture without duplicating helpers, grids, or provider-specific UI logic.
Backend Pattern Map
| New/changed area | Closest analog | Pattern to preserve |
|---|---|---|
backend/migrations/versions/0006_cloud_resource_foundation.py |
0005_system_settings.py |
Typed Alembic table creation, named constraints/indexes, timezone timestamps, exact down_revision="0005", reversible downgrade |
CloudItem and connection fields in backend/db/models.py |
CloudConnection, Document, Folder |
UUID PKs, explicit owner FKs with cascade, compound owner/navigation indexes, human names in DB only |
backend/storage/cloud_base.py |
backend/storage/base.py, backend/ai/base.py |
Small abstract interface; normalized domain values; provider adapters own translation |
| Provider capability/metadata normalization | Existing Google/OneDrive/Nextcloud adapters | Keep SDK/HTTP details inside provider files, wrap synchronous SDK calls with asyncio.to_thread(), preserve canonical CloudConnectionError import |
backend/services/cloud_items.py |
Owner-scoped query patterns in api/folders.py, service exception policy in AGENTS.md |
Filter by owner in every statement; service raises ValueError/domain exceptions, never HTTPException; use shared deps.utils.parse_uuid at router boundary |
backend/api/cloud/ package |
backend/api/documents/ and backend/api/admin/ |
One aggregator owns /api/cloud; sub-routers have no prefix; root empty-path routes register on parent if FastAPI requires it |
| Cloud response schemas | CloudConnectionOut in backend/api/schemas.py |
Explicit field whitelist with from_attributes; never serialize ORM objects or credentials_enc directly |
| Metadata refresh/backoff | Celery retry conventions in document tasks | Bounded exponential retry, idempotent upsert/reconciliation, no retry around ownership/validation failures |
Required Backend Corrections
- Current
_upsert_cloud_connection()and_get_active_connection()use(user_id, provider)and must not be copied. Phase 12 uses connection UUID as identity to support multiple same-provider accounts. - Current listing cache key
{user_id}:{provider}:{folder_id}is insufficient. Any transitional key includes connection UUID; durable metadata belongs in PostgreSQL. - Current provider listing dictionaries contain only
id,name,is_dir,size. New adapters normalize parent, MIME/type, mtime, etag/version, per-item capabilities, and completeness/pagination. api/folders.pycontains a private_parse_uuid; new cloud routers must importparse_uuidfrombackend/deps/utils.pyunder the project’s shared-helper rule.
Frontend Pattern Map
| New/changed area | Closest analog | Pattern to preserve |
|---|---|---|
| Capability-aware rows | frontend/src/components/storage/StorageBrowser.vue |
This remains the only grid; props drive layout/state and generic events travel upward |
| Sync indicator | frontend/src/components/ui/BreadcrumbBar.vue |
Extend shared breadcrumb rather than adding cloud-only breadcrumb markup |
| Connection roots | Current CloudStorageView.vue plus StorageBrowser.vue |
View owns store/router only; feed normalized root rows into shared browser |
| Cloud folder route | CloudFolderView.vue, FileManagerView.vue |
Thin data provider; no duplicated toolbar, row, action, or tooltip layout |
| API functions | frontend/src/api/cloud.js + client.js barrel |
Change route arguments behind the domain module; consumers retain barrel imports |
| Connection state | frontend/src/stores/cloudConnections.js |
Pinia owns connection/folder state and errors; components stay store-agnostic |
| Shared formatting | frontend/src/utils/formatters.js |
Reuse formatDate, formatSize, and provider helpers; do not redefine locally |
| Icons | frontend/src/components/ui/AppIcon.vue |
Add registry entries only when an existing icon name cannot express status/action |
UI Contract Patterns
- Unsupported actions use
aria-disabled="true", remain focusable/tappable, and suppress action emission while surfacing explanation. - Permanent unsupported state is gray; temporary unavailable state is amber. Do not hide actions based on
mode === "local". StorageBrowserreceives capabilities, folder freshness, connection root, last refresh time, and byte availability as props.- Local storage supplies a supported capability set so local/cloud share one rendering path.
- Minimum touch target stays 36x36 px and all controls use existing indigo focus-ring utilities.
- Session-only last folder state uses
sessionStoragekeyed by connection UUID; JWT tokens remain Pinia-memory-only.
Testing Pattern Map
| Coverage | Existing analog | Phase 12 use |
|---|---|---|
| API/ownership | backend/tests/test_cloud.py |
Multiple same-provider accounts, foreign connection/item 404, admin rejection, credential exclusion |
| Adapter contracts | backend/tests/test_cloud_backends.py |
Shared normalized fixtures, async methods, pagination/completeness, no destructive probes |
| DB/service | async fixtures in backend/tests/conftest.py |
Fast SQLite unit loop plus marked real-PostgreSQL constraint/index/cascade tests |
| Pinia | frontend/src/stores/__tests__/cloudConnections.test.js |
Connection UUID routes, duplicate providers, freshness/error transitions |
| Storage browser | StorageBrowser.dragmove/showSearch/skeleton.test.js |
Capability rendering, action suppression, focus/tap explanations, local regression |
| Breadcrumb | components/ui/__tests__/BreadcrumbBar.test.js |
Refreshing/fresh/warning labels, timestamps, fade behavior |
| Thin views | views/__tests__/FileManagerView.test.js |
Cloud views pass props/handle events without grid duplication |
Data Flow
- Router authenticates regular user and parses
connection_idwith shared request helpers. - Service resolves
(connection_id, user_id)and reads durable children by connection/parent. - Provider adapter normalizes a complete paginated listing and capability states.
- Service upserts metadata using
(connection_id, provider_item_id)and reconciles only after complete success. - Explicit Pydantic schemas return rows, capability messages, freshness, and connection identity without credentials.
- Pinia/view maps API data into
StorageBrowser; the browser renders shared rows/actions and emits generic commands. - Browse never calls provider byte download, writes MinIO, or changes quota.
Files Likely Modified
Backend
backend/migrations/versions/0006_cloud_resource_foundation.pybackend/db/models.pybackend/storage/cloud_base.pybackend/storage/cloud_backend_factory.py- provider backend modules
backend/services/cloud_items.pybackend/api/cloud/__init__.py,connections.py,browse.py,schemas.pybackend/main.pyonly if aggregator import changes- focused backend tests and PostgreSQL integration coverage
Frontend
frontend/src/api/cloud.jsfrontend/src/stores/cloudConnections.jsfrontend/src/router/index.jsfrontend/src/views/CloudStorageView.vuefrontend/src/views/CloudFolderView.vuefrontend/src/components/storage/StorageBrowser.vuefrontend/src/components/ui/BreadcrumbBar.vue- focused component/store/view tests
Documentation/Versioning
AGENTS.md,README.md,backend/main.py, andfrontend/package.jsonper the project documentation/version protocol after user-facing plan completion.