mAi: #7 - cloud-sync to Supabase Storage + imagen.images
Every successful imagen generate now (a) uploads the PNG to the private imagen-generated bucket and (b) inserts a row into imagen.images, the data plane the flexsiebels owner-mode viewer reads from. Schema, RLS, indexes, bucket and PostgREST exposure landed via four applied migrations on msupabase: imagen_schema_init, imagen_schema_grants, imagen_storage_policies, imagen_pgrst_expose (authenticator role-level ALTER + reload). Owner UUID for m: ac6c9501-3757-4a6d-8b97-2cff4288382b — documented in the config sample. Code: new internal/cloud/ package mirroring the internal/usage/ shape. PostgREST POST against the imagen schema (Accept-Profile + Content- Profile headers), Storage upload via PUT with x-upsert, retry on 5xx / transport but not 4xx, owner_user_id required (the column is NOT NULL and the read-side RLS policy needs it). Wiring in cmd/imagen/generate.go: --no-cloud flag, output.cloud_sync config knob (auto|on|off mirroring --preview), $IMAGEN_CLOUD_SYNC env override. The hook reads the just-written PNG + sidecar from disk and calls cloud.Sync; failures emit "imagen: cloud sync: <err>" to stderr without changing exit code, so a Supabase blip never loses the artefact. output.Outputs grew Date/Slug/Seed fields so storage_path mirrors the local filename's prefix exactly (no UTC-vs-local drift). Config: owner_user_id field added; sample comment points at the auth.users lookup. imagen config validate warns on stderr when cloud_sync is on/auto but owner_user_id is empty. Tests: cloud_test.go covers happy path, retry-on-5xx, no-retry-on-4xx, missing-owner-uuid, missing-date-or-slug, signed URL, and the partial- success case where the upload landed but the DB insert failed. generate_test.go covers the precedence chain for cloud-sync mode resolution. Build + tests clean across the tree. Real smoke against mRock: generation through flux-schnell-local writes the local PNG + sidecar AND uploads to imagen-generated/2026-05-11/... AND inserts into imagen.images. Signed URL round-trips the same bytes. --no-cloud verified to skip both Storage and DB.
This commit is contained in:
@@ -15,6 +15,10 @@ import (
|
||||
// Config is the top-level shape of imagen.yaml.
|
||||
type Config struct {
|
||||
DefaultBackend string `yaml:"default_backend"`
|
||||
// OwnerUserID is m's auth.users.id on msupabase. The cloud-sync writer
|
||||
// uses it to populate imagen.images.owner_user_id (NOT NULL, owns RLS).
|
||||
// Empty disables DB inserts even when cloud_sync is on.
|
||||
OwnerUserID string `yaml:"owner_user_id"`
|
||||
Output OutputConfig `yaml:"output"`
|
||||
Backends map[string]BackendSpec `yaml:"backends"`
|
||||
}
|
||||
@@ -29,6 +33,11 @@ type OutputConfig struct {
|
||||
// Empty / unset is treated as "auto". $IMAGEN_PREVIEW and the
|
||||
// --preview/--no-preview flags override this in turn.
|
||||
Preview string `yaml:"preview"`
|
||||
// CloudSync controls whether successful generations also upload to
|
||||
// Supabase Storage and insert into imagen.images. Tri-state mirroring
|
||||
// Preview: "auto" (default — on when SUPABASE_URL + SUPABASE_SERVICE_KEY
|
||||
// are set), "on" (errors if env unset), "off". --no-cloud overrides.
|
||||
CloudSync string `yaml:"cloud_sync"`
|
||||
}
|
||||
|
||||
// BackendSpec is one entry under `backends:`. Type identifies the adapter;
|
||||
@@ -88,6 +97,11 @@ func (c *Config) Validate() error {
|
||||
default:
|
||||
return fmt.Errorf("output.preview = %q (must be auto|on|off)", c.Output.Preview)
|
||||
}
|
||||
switch c.Output.CloudSync {
|
||||
case "", "auto", "on", "off":
|
||||
default:
|
||||
return fmt.Errorf("output.cloud_sync = %q (must be auto|on|off)", c.Output.CloudSync)
|
||||
}
|
||||
for name, spec := range c.Backends {
|
||||
if name == "" {
|
||||
return errors.New("empty backend name")
|
||||
@@ -107,6 +121,11 @@ const Sample = `# imagen.yaml — config for the imagen CLI.
|
||||
|
||||
default_backend: flux-schnell-local
|
||||
|
||||
# Owner UUID for the cloud-sync row in imagen.images. Look up via:
|
||||
# SELECT id FROM auth.users WHERE email = '<your-supabase-email>';
|
||||
# Empty disables imagen.images inserts even when cloud_sync is on.
|
||||
owner_user_id: ""
|
||||
|
||||
output:
|
||||
directory: ~/Pictures/imagen
|
||||
naming: "{date}-{slug}-{seed}.png"
|
||||
@@ -116,6 +135,13 @@ output:
|
||||
# on: always preview (errors outside a tmux session).
|
||||
# off: never preview (use this for batch / CI callers).
|
||||
preview: auto
|
||||
# Sync the PNG to Supabase Storage (bucket: imagen-generated) and insert
|
||||
# a row into imagen.images. Reads SUPABASE_URL + SUPABASE_SERVICE_KEY
|
||||
# from env (same as mai.imagen_usage cost-tracking).
|
||||
# auto (default): on iff env is configured AND owner_user_id is set.
|
||||
# on: always upload (errors if env or owner_user_id is missing).
|
||||
# off: never upload. --no-cloud also forces off per-call.
|
||||
cloud_sync: auto
|
||||
|
||||
backends:
|
||||
flux-schnell-local:
|
||||
|
||||
Reference in New Issue
Block a user