Files
at-container-registry/pkg/appview/db/migrations
Evan Jarrett ab4a4ebf9d admin panel long running imrovements, billing fixes, ui cleanup
1. Multiple registry domains + per-user domain preference

The biggest feature. The appview can serve several registry domains (e.g. buoy.cr, atcr.io),
and users can now pick which one shows up in their pull/push commands.

- Lexicon/record: adds registryDomain (and documents ociClient)
to the sailor profile (lexicons/.../profile.json, pkg/atproto/lexicon.go).
- DB: new registry_domain column on users (schema.sql + migration 0027),
with GetUserByDID/Handle reads, UpdateUserRegistryDomain writer,
and Jetstream caching it on profile updates (writes unconditionally so clearing propagates).
- UI/handlers: new UpdateRegistryDomainHandler + /api/profile/registry-domain route,
a <select> in the user settings panel (only shown when >1 domain configured), and resolveRegistryURL()
which falls back to the primary domain if the user's pref is stale/removed. Tests added for all of it.

2. default_hold_did removed → first managed_holds entry is the default

Consolidates two overlapping config fields into one. ServerConfig.DefaultHoldDID is gone;
PrimaryHoldDID() now returns managed_holds[0]. managed_holds is now REQUIRED.
Updated in config, validation, server wiring, test harness, example YAML, and the deploy template.

3. Admin long-running operations → generic background-job framework

New pkg/hold/admin/jobs.go introduces a reusable startJob/jobRegistry pattern
 (a detached context.Background() job + a /admin/api/jobs/{key}/status polling endpoint).
This replaces the bespoke scan-backfill goroutine state machine, and now also wraps crew tier remap and crew import
all three previously looped synchronously on the request context and got 504'd/cancelled mid-run by the reverse proxy.
 Forms switched from POST-redirect to htmx fragments (job_progress.html, job_result.html, crew_import_results.html)
 the old crew_import_results.html page and scan_backfill_progress.html partial were deleted.
This is also captured as a new rule in CLAUDE.md.

4. Cascade-delete manifest on last-tag deletion

DeleteTagHandler now, after removing the last tag pointing to a digest, cascade-deletes the manifest itself
 (PDS + DB + hold blob purge) — but only if it's not a child of a manifest list (multi-arch parent).
 New GetTagDigest and ShouldCascadeDeleteManifest queries back it, plus cascade_delete_test.go.
 Also switches tag rkey computation to the atproto.RepositoryTagToRKey helper.

5. Billing simplification

Drops the OwnerBadge config option (hold-owner supporter badge).
The user-profile template no longer special-cases an "owner" badge value (only "Captain").
Example tiers renamed to the nautical scheme (deckhand/bosun/quartermaster).

6. Build/deploy: go generate always runs via Make

make generate is now a phony target that always runs go generate ./... (regenerating cbor_gen, icon sprites, etc.),
 and build-trixie depends on it. The deploy tooling (provision.go/update.go)
drops its own runGenerate calls since the Makefile handles it.

7. New cmd/firehose-tap tool (untracked)

A standalone CLI that subscribes to a com.atproto.sync.subscribeRepos endpoint and pretty-prints events,
with emphasis on Sync 1.1 compliance fields (per-op prev CIDs, commit prevData) and a --validate CI mode.
Fits with the recent "more sync1.1 compliant" commit.
2026-06-05 20:57:25 -05:00
..
2025-10-28 17:40:11 -05:00

Database Migrations

This directory contains database migrations for the ATCR AppView database.

Schema vs Migrations

schema.sql (in parent directory) contains the complete base schema for fresh database installations. It includes all tables, indexes, and constraints.

Migrations (this directory) handle changes to existing databases. They are only for:

  • ALTER TABLE statements (add/modify/drop columns)
  • UPDATE statements (data transformations)
  • DELETE statements (data cleanup)
  • Creating/modifying indexes on existing tables

NEW TABLES go in schema.sql, NOT in migrations.

Migration Format

Each migration is a YAML file with the following structure:

description: Optional human-readable description of what this migration does
query: |
  SQL commands to apply the migration

Version and name are parsed from the filename, so you don't need to specify them in the YAML.

Naming Convention

Migration files must be named: {version:04d}_{migration_name}.yaml

The filename determines:

  • Version: Numeric prefix (e.g., 0001 → version 1)
  • Name: Everything after first underscore (e.g., add_repository_labels → "add repository labels")

Examples:

  • 0001_remove_star_count_from_repository_stats.yaml → version 1, name "remove star count from repository stats"
  • 0002_add_repository_labels.yaml → version 2, name "add repository labels"
  • 0003_create_webhooks_table.yaml → version 3, name "create webhooks table"

Creating a New Migration

  1. Choose the next version number - Look at existing migrations and increment by 1
  2. Create a new YAML file with format 000N_descriptive_name.yaml
  3. Add description (optional) - Explain what the migration does
  4. Write your SQL in query - Use the | block scalar for clean multi-line SQL
  5. Use IF EXISTS / IF NOT EXISTS where possible for idempotency

Examples

Adding a column to existing table:

Filename: 0007_add_readme_url_to_manifests.yaml

description: Add readme_url column to manifests table for storing io.atcr.readme annotation
query: |
  ALTER TABLE manifests ADD COLUMN readme_url TEXT;

IMPORTANT: After creating this migration, also add the column to schema.sql so fresh installations include it!

Data transformation migration:

Filename: 0005_normalize_hold_endpoint_to_did.yaml

description: Normalize hold_endpoint column to store DIDs instead of URLs
query: |
  -- Convert HTTPS URLs to did:web: format
  UPDATE manifests
  SET hold_endpoint = 'did:web:' || substr(hold_endpoint, 9)
  WHERE hold_endpoint LIKE 'https://%';

  -- Convert HTTP URLs to did:web: format
  UPDATE manifests
  SET hold_endpoint = 'did:web:' || substr(hold_endpoint, 8)
  WHERE hold_endpoint LIKE 'http://%';

Adding an index to existing table:

Filename: 0008_add_repository_description_index.yaml

description: Add index on manifests description field for faster searches
query: |
  CREATE INDEX IF NOT EXISTS idx_manifests_description ON manifests(description);

How Migrations Run

  1. Migrations are loaded from this directory on startup
  2. Sorted by version number (ascending)
  3. Each migration is checked against the schema_migrations table
  4. Only unapplied migrations are executed
  5. After successful execution, the version is recorded in schema_migrations

Important Notes

  • Never modify existing migrations - Once applied, they're immutable
  • Test migrations before committing - Ensure they work on existing databases
  • Version numbers must be unique - The migration system will fail if duplicates exist
  • Migrations run automatically on InitDB() - Schema first, then migrations
  • CRITICAL: Update schema.sql for structural changes - When you ALTER a table or add columns, update both the migration AND schema.sql so fresh installations have the same structure
  • New tables go in schema.sql only - Don't create migration files for new tables