diff --git a/surf/serve.go b/surf/serve.go index ddcf36e..34b164f 100644 --- a/surf/serve.go +++ b/surf/serve.go @@ -14,7 +14,9 @@ import ( "git.golem15.com/golem15/summercms/backpack" "git.golem15.com/golem15/summercms/bonfire" "git.golem15.com/golem15/summercms/lagoon" + "git.golem15.com/golem15/summercms/lagoon/attach" "git.golem15.com/golem15/summercms/party" + "gocloud.dev/blob" ) // ServeCommand starts a signal-aware HTTP server on the assembled router. @@ -40,6 +42,11 @@ func ServeCommand(app *backpack.App, plugins []party.Plugin) bonfire.Command { if err := lagoon.Publish(app, sqlDB, gdb); err != nil { return err } + bucket, err := publishUploads(ctx, app) + if err != nil { + return err + } + defer bucket.Close() h, err := Assemble(app, plugins) if err != nil { return err @@ -73,3 +80,20 @@ func ServeCommand(app *backpack.App, plugins []party.Plugin) bonfire.Command { }, } } + +// publishUploads opens storage.uploads.bucket_url and publishes *blob.Bucket. +// An empty URL fails boot the same way an empty JWT secret does. +func publishUploads(ctx context.Context, app *backpack.App) (*blob.Bucket, error) { + if app == nil { + return nil, fmt.Errorf("surf: app is nil") + } + bucket, err := attach.OpenBucket(ctx, app.Config) + if err != nil { + return nil, err + } + if err := attach.Publish(app, bucket); err != nil { + _ = bucket.Close() + return nil, err + } + return bucket, nil +} diff --git a/surf/serve_test.go b/surf/serve_test.go new file mode 100644 index 0000000..8a2d5cc --- /dev/null +++ b/surf/serve_test.go @@ -0,0 +1,53 @@ +package surf + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "git.golem15.com/golem15/summercms/backpack" + "git.golem15.com/golem15/summercms/compass" + "gocloud.dev/blob" +) + +func TestPublishUploadsRequiresURL(t *testing.T) { + app := serveTestApp(t, "uploads:\n bucket_url: \"\"\n") + _, err := publishUploads(t.Context(), app) + if err == nil || !strings.Contains(err.Error(), "bucket_url") { + t.Fatalf("got %v, want bucket_url error", err) + } +} + +func TestPublishUploadsStoresBucket(t *testing.T) { + app := serveTestApp(t, "uploads:\n bucket_url: \"mem://\"\n public_path_prefix: \"/storage/uploads\"\n") + bucket, err := publishUploads(t.Context(), app) + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { _ = bucket.Close() }) + got, ok := app.Lookup[*blob.Bucket]() + if !ok || got != bucket { + t.Fatal("publishUploads must store the opened *blob.Bucket") + } +} + +func serveTestApp(t *testing.T, storageYAML string) *backpack.App { + t.Helper() + dir := t.TempDir() + if err := os.WriteFile(filepath.Join(dir, "app.yaml"), []byte("name: serve-uploads\n"), 0o644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(dir, "storage.yaml"), []byte(storageYAML), 0o644); err != nil { + t.Fatal(err) + } + cfg, err := compass.Open(compass.Options{ + Dir: dir, + Env: "development", + Environ: []string{"SUMMER_ENV=development"}, + }) + if err != nil { + t.Fatal(err) + } + return backpack.New(cfg) +}