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>
170 lines
5.5 KiB
TypeScript
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 === "***"
|
|
}
|