# Workspaces & Git The **Workspace** is a virtual filesystem for the editor surface. Each workspace has a root directory, file operations, optional Git integration, and full-text indexing for code search. **All endpoints require:** `Auth()` middleware (JWT bearer token). Workspace-scoped endpoints additionally verify ownership via `loadAndAuthorize` (user owns workspace, or is member of the owning team/project/channel). --- ### Workspace CRUD ``` GET /workspaces → { "data": [...] } POST /workspaces ← { "name", "owner_type", "owner_id" } GET /workspaces/:id → workspace object PATCH /workspaces/:id ← partial update DELETE /workspaces/:id ``` **Auth:** Authenticated user. `GET /workspaces` returns user-owned workspaces plus those owned by the user's teams. All `:id` endpoints verify the caller can access the workspace's owner entity. Workspace object: ```json { "id": "uuid", "name": "my-project", "owner_type": "user", "owner_id": "uuid", "status": "active", "max_bytes": 52428800, "indexing_enabled": false, "git_remote_url": "https://github.com/...", "git_branch": "main", "git_credential_id": "uuid", "git_last_sync": "2025-06-15T14:30:00Z", "file_count": 42, "total_bytes": 1048576, "created_at": "2025-06-15T14:30:00Z", "updated_at": "2025-06-15T14:30:00Z" } ``` | Field | Type | Notes | |-------|------|-------| | `id` | string | UUIDv4 | | `name` | string | Max 200 chars | | `owner_type` | `"user"` \| `"project"` \| `"channel"` \| `"team"` | Owner entity type | | `owner_id` | string | UUID of owner entity | | `status` | `"active"` \| `"archived"` \| `"deleting"` | Lifecycle state | | `max_bytes` | int \| null | Quota ceiling. Omitted if unset. | | `indexing_enabled` | bool | Full-text search indexing | | `git_remote_url` | string \| null | Git remote. Omitted if unset. | | `git_branch` | string \| null | Tracked branch. Omitted if unset. | | `git_credential_id` | string \| null | FK to git credential. Omitted if unset. | | `git_last_sync` | string \| null | ISO 8601. Omitted if never synced. | | `file_count` | int | Computed. Enriched on `GET /:id` only. | | `total_bytes` | int | Computed. Enriched on `GET /:id` only. | | `created_at` | string | ISO 8601 | | `updated_at` | string | ISO 8601 | > **Note:** `root_path` is a server-internal filesystem path and is > never exposed in API responses (json tag `"-"`). **Create request:** ```json { "name": "my-project", "owner_type": "user", "owner_id": "uuid", "max_bytes": 52428800 } ``` Returns `201` with the workspace object. **PATCH request** (all fields optional): ```json { "name": "new-name", "status": "archived", "max_bytes": 104857600, "indexing_enabled": true, "git_remote_url": "https://...", "git_branch": "develop", "git_credential_id": "uuid" } ``` Returns updated workspace object. **DELETE** returns `{"ok": true}`. Deletion is async (status set to `"deleting"`, filesystem cleanup runs in background). --- ### File Operations All paths are relative to the workspace root. File path is passed as `?path=` query parameter. ``` GET /workspaces/:id/files?path=/ → { "data": [entries] } GET /workspaces/:id/files/read?path=/a.md → raw file content (binary stream) PUT /workspaces/:id/files/write?path=/a.md ← raw body (Content-Length required) DELETE /workspaces/:id/files/delete?path=/a.md POST /workspaces/:id/files/mkdir?path=/src ``` **Auth:** Authenticated user with workspace access. **`GET .../files`** accepts `?recursive=true` for deep listing. Returns `{"data": [...]}` (never bare array). Nil slice guarded. File entry (from `WorkspaceFile` model): ```json { "id": "uuid", "workspace_id": "uuid", "path": "/src/main.go", "is_directory": false, "content_type": "text/x-go", "size_bytes": 4096, "sha256": "abc123...", "index_status": "ready", "chunk_count": 3, "created_at": "2025-06-15T14:30:00Z", "updated_at": "2025-06-15T14:30:00Z" } ``` **`GET .../files/read`** returns raw file content with appropriate `Content-Type` header and `Content-Length`. This is a binary stream, not JSON. **`PUT .../files/write`** reads the request body as raw content. Returns `{"ok": true, "path": "/a.md"}`. Returns `413` if the write would exceed the workspace quota. **`DELETE .../files/delete`** accepts `?recursive=true` for directory removal. Returns `{"ok": true}`. **`POST .../files/mkdir`** returns `201 {"ok": true, "path": "/src"}`. --- ### Archive **Download** (full workspace as archive): ``` GET /workspaces/:id/archive/download?format=zip ``` **Auth:** Authenticated user with workspace access. `format` query param: `zip` (default), `tar.gz`, `tgz`. Returns binary stream with `Content-Disposition: attachment`. **Upload** (restore from archive): ``` POST /workspaces/:id/archive/upload Content-Type: multipart/form-data (field: "file") ``` **Auth:** Authenticated user with workspace access. Accepts `.zip`, `.tar.gz`, `.tgz` archives. Format detected from filename extension. Returns: ```json { "ok": true, "files_extracted": 42 } ``` --- ### Reconcile, Stats, Index Status **Reconcile** (sync filesystem state with DB metadata): ``` POST /workspaces/:id/reconcile ``` **Auth:** Authenticated user with workspace access. Returns: ```json { "added": 5, "removed": 2, "updated": 3 } ``` **Stats:** ``` GET /workspaces/:id/stats ``` **Auth:** Authenticated user with workspace access. Returns a `WorkspaceStats` object: ```json { "workspace_id": "uuid", "file_count": 42, "dir_count": 8, "total_bytes": 1048576, "max_bytes": 52428800 } ``` **Index status:** ``` GET /workspaces/:id/index-status ``` **Auth:** Authenticated user with workspace access. Returns indexing progress for full-text search: ```json { "workspace_id": "uuid", "indexing_enabled": true, "status_counts": { "pending": 3, "indexing": 1, "ready": 38, "error": 0, "skipped": 2 }, "total_chunks": 156 } ``` --- ### Git Integration All Git operations are workspace-scoped. Require the workspace to have a Git remote configured. ``` POST /workspaces/:id/git/clone ← { "url", "branch", "credential_id" } POST /workspaces/:id/git/pull POST /workspaces/:id/git/push GET /workspaces/:id/git/status → GitStatus object GET /workspaces/:id/git/diff → { "diff": "..." } POST /workspaces/:id/git/commit ← { "message", "paths": [...] } GET /workspaces/:id/git/log → { "data": [GitLogEntry] } GET /workspaces/:id/git/branches → { "branches": [...], "current": "main" } POST /workspaces/:id/git/checkout ← { "branch": "feature-x" } ``` **Auth:** Authenticated user with workspace access. **`POST .../git/clone`** request: ```json { "url": "https://github.com/user/repo.git", "branch": "main", "credential_id": "uuid" } ``` `branch` and `credential_id` are optional. Returns `{"status": "cloned"}`. **`POST .../git/pull`** returns `{"status": "pulled"}`. **`POST .../git/push`** returns `{"status": "pushed"}`. **`GET .../git/status`** returns the full `GitStatus` object: ```json { "branch": "main", "clean": false, "ahead": 2, "behind": 0, "staged": [{ "path": "file.go", "status": "M" }], "modified": [{ "path": "other.go", "status": "M" }], "untracked": ["new-file.txt"] } ``` **`GET .../git/diff`** accepts optional `?path=file.go` for single-file diff. Returns `{"diff": "..."}` (unified diff string). **`POST .../git/commit`** request: ```json { "message": "fix: resolve null pointer", "paths": ["/src/main.go", "/src/util.go"] } ``` `paths` is optional (commits all staged if omitted). Returns `{"status": "committed"}`. **`GET .../git/log`** accepts optional `?n=20` (default 20, max 100). Returns `{"data": [GitLogEntry]}`: ```json { "data": [ { "hash": "abc123...", "short_hash": "abc123", "author": "Jane Doe", "date": "2025-06-15T14:30:00Z", "message": "Initial commit" } ] } ``` **`GET .../git/branches`** returns: ```json { "current": "main", "branches": ["main", "develop", "feature-x"] } ``` **`POST .../git/checkout`** returns `{"status": "checked out", "branch": "feature-x"}`. --- ### Git Credentials User-owned, encrypted credential storage for Git operations. Encrypted with the user's UEK via the platform vault (same pattern as BYOK keys — admins cannot recover). Independent of workspace scope — credentials are user-level. ``` POST /git-credentials ← { "name", "auth_type", ... } GET /git-credentials → { "data": [GitCredentialSummary] } DELETE /git-credentials/:id ``` **Auth:** Authenticated user. Credentials are scoped to the requesting user. **Create request:** `auth_type` determines which fields are required: | `auth_type` | Required fields | |-------------|----------------| | `https_pat` | `token` | | `https_basic` | `username`, `password` | | `ssh_key` | `private_key` (+ optional `passphrase`) | Returns `201` with the credential summary (never exposes encrypted data). **List response:** ```json { "data": [ { "id": "uuid", "name": "GitHub PAT", "auth_type": "https_pat", "created_at": "2025-06-15T14:30:00Z" } ] } ``` **DELETE** returns `{"deleted": true}`. Returns `404` if credential not found or not owned by the requesting user. ---