Files
arcadia-admin/app/lib/arcadia/storage-configs.ts
jules ab116f8465 refactor: rename @crema/arcadia-client → @crema/arcadia-core-client
Disambiguates the Phoenix/auth client lib from lib-arcadia-agents-client.
Dir lib-arcadia-client → lib-arcadia-core-client; alias updated in
tsconfig paths, vite config, app.css @source, imports, CI and docs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 13:31:56 +10:00

170 lines
5.5 KiB
TypeScript

// Arcadia storage configs API helpers.
//
// `GET /api/v1/storage_configs` and `POST /api/v1/storage_configs` are the
// only operations with full OpenAPI coverage today. Update/delete and the
// state-transition actions (activate, deactivate, mark-degraded,
// mark-maintenance, set-default, validate) are listed in the spec but their
// operations are still stubbed as `never`, so we hand-roll types and use the
// generic `arcadia.GET<T>` / `arcadia.POST<T>` / etc. — same pattern as
// `tenants.ts`. Switch to `arcadia.typed.*` when the spec gains coverage.
import type { ArcadiaClient } from "@crema/arcadia-core-client"
export type StorageBackend = "s3" | "local" | "gcs"
export type StorageStatus = "active" | "inactive" | "degraded" | "maintenance"
export interface StorageConfig {
id: string
tenant_id: string
name: string
backend_type: StorageBackend
status: StorageStatus
is_default: boolean
max_file_size_bytes: number | null
allowed_content_types: string[] | null
// Backend-specific fields. Secret fields are returned as "***" by the API.
config: Record<string, unknown>
inserted_at: string
updated_at: string
}
export interface StorageConfigInput {
name: string
backend_type: StorageBackend
config: Record<string, unknown>
is_default?: boolean
max_file_size_bytes?: number | null
allowed_content_types?: string[]
}
export interface StorageStats {
total_objects: number
total_size_bytes: number
by_backend: Record<string, unknown>
by_user: Record<string, unknown>
}
export interface StorageProvidersResponse {
data: Record<StorageBackend, { required_fields: string[]; optional_fields?: string[] }>
}
export async function listStorageConfigs(arcadia: ArcadiaClient): Promise<StorageConfig[]> {
const res = await arcadia.GET<{ data: StorageConfig[] }>("/api/v1/storage_configs")
return res.data
}
export async function getStorageConfig(arcadia: ArcadiaClient, id: string): Promise<StorageConfig> {
const res = await arcadia.GET<{ data: StorageConfig }>(`/api/v1/storage_configs/${id}`)
return res.data
}
export async function createStorageConfig(
arcadia: ArcadiaClient,
input: StorageConfigInput,
): Promise<StorageConfig> {
const res = await arcadia.POST<{ data: StorageConfig }>("/api/v1/storage_configs", {
body: { storage_config: input },
})
return res.data
}
export async function updateStorageConfig(
arcadia: ArcadiaClient,
id: string,
input: Partial<StorageConfigInput>,
): Promise<StorageConfig> {
const res = await arcadia.PATCH<{ data: StorageConfig }>(`/api/v1/storage_configs/${id}`, {
body: { storage_config: input },
})
return res.data
}
export async function deleteStorageConfig(arcadia: ArcadiaClient, id: string): Promise<void> {
await arcadia.DELETE(`/api/v1/storage_configs/${id}`)
}
export async function activateStorageConfig(arcadia: ArcadiaClient, id: string): Promise<StorageConfig> {
const res = await arcadia.POST<{ data: StorageConfig }>(`/api/v1/storage_configs/${id}/activate`)
return res.data
}
export async function deactivateStorageConfig(arcadia: ArcadiaClient, id: string): Promise<StorageConfig> {
const res = await arcadia.POST<{ data: StorageConfig }>(`/api/v1/storage_configs/${id}/deactivate`)
return res.data
}
export async function markStorageConfigDegraded(
arcadia: ArcadiaClient,
id: string,
): Promise<StorageConfig> {
const res = await arcadia.POST<{ data: StorageConfig }>(`/api/v1/storage_configs/${id}/mark-degraded`)
return res.data
}
export async function markStorageConfigMaintenance(
arcadia: ArcadiaClient,
id: string,
): Promise<StorageConfig> {
const res = await arcadia.POST<{ data: StorageConfig }>(
`/api/v1/storage_configs/${id}/mark-maintenance`,
)
return res.data
}
export async function setDefaultStorageConfig(
arcadia: ArcadiaClient,
id: string,
): Promise<StorageConfig> {
const res = await arcadia.POST<{ data: StorageConfig }>(`/api/v1/storage_configs/${id}/set-default`)
return res.data
}
export interface ValidateResult {
ok: boolean
message?: string
details?: unknown
}
export async function validateStorageConfig(
arcadia: ArcadiaClient,
id: string,
): Promise<ValidateResult> {
return arcadia.POST<ValidateResult>(`/api/v1/storage_configs/${id}/validate`)
}
export async function getStorageStats(arcadia: ArcadiaClient): Promise<StorageStats> {
const res = await arcadia.GET<{ data: StorageStats }>("/api/v1/storage_configs/stats")
return res.data
}
// Backend-specific config field schemas. Secret fields appear as "***" on
// reads — the form treats them as write-only and only sends a value when the
// user has typed a fresh one.
export const SECRET_FIELDS: Record<StorageBackend, readonly string[]> = {
s3: ["secret_access_key"],
gcs: ["service_account_json"],
local: [],
}
export const REQUIRED_FIELDS: Record<StorageBackend, readonly string[]> = {
s3: ["bucket", "region", "access_key_id", "secret_access_key"],
gcs: ["bucket", "service_account_json"],
// Local backend's filesystem root. Backend changeset rejects "path" — must
// be `base_path`. Keep this in sync with `Arcadia.Storage.Adapters.Local`.
local: ["base_path"],
}
export const OPTIONAL_FIELDS: Record<StorageBackend, readonly string[]> = {
s3: ["endpoint", "prefix"],
gcs: ["prefix"],
local: [],
}
export function isSecretField(backend: StorageBackend, field: string): boolean {
return SECRET_FIELDS[backend].includes(field)
}
export function isMaskedSecret(value: unknown): boolean {
return typeof value === "string" && value === "***"
}