# 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.py` contains a private `_parse_uuid`; new cloud routers must import `parse_uuid` from `backend/deps/utils.py` under 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"`. - `StorageBrowser` receives 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 `sessionStorage` keyed 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 1. Router authenticates regular user and parses `connection_id` with shared request helpers. 2. Service resolves `(connection_id, user_id)` and reads durable children by connection/parent. 3. Provider adapter normalizes a complete paginated listing and capability states. 4. Service upserts metadata using `(connection_id, provider_item_id)` and reconciles only after complete success. 5. Explicit Pydantic schemas return rows, capability messages, freshness, and connection identity without credentials. 6. Pinia/view maps API data into `StorageBrowser`; the browser renders shared rows/actions and emits generic commands. 7. Browse never calls provider byte download, writes MinIO, or changes quota. ## Files Likely Modified ### Backend - `backend/migrations/versions/0006_cloud_resource_foundation.py` - `backend/db/models.py` - `backend/storage/cloud_base.py` - `backend/storage/cloud_backend_factory.py` - provider backend modules - `backend/services/cloud_items.py` - `backend/api/cloud/__init__.py`, `connections.py`, `browse.py`, `schemas.py` - `backend/main.py` only if aggregator import changes - focused backend tests and PostgreSQL integration coverage ### Frontend - `frontend/src/api/cloud.js` - `frontend/src/stores/cloudConnections.js` - `frontend/src/router/index.js` - `frontend/src/views/CloudStorageView.vue` - `frontend/src/views/CloudFolderView.vue` - `frontend/src/components/storage/StorageBrowser.vue` - `frontend/src/components/ui/BreadcrumbBar.vue` - focused component/store/view tests ### Documentation/Versioning - `AGENTS.md`, `README.md`, `backend/main.py`, and `frontend/package.json` per the project documentation/version protocol after user-facing plan completion. ## PATTERN MAPPING COMPLETE