package store import ( "context" "fmt" ) // ClaimLegacyOwner is a one-time upgrade step, not a normal runtime // operation: it binds every pre-multi-tenancy row (user_id IS NULL, left // that way by migrations that can't take runtime parameters -- see // docs/superpowers/specs/2026-07-25-per-user-profile-design.md) to a single // new user identified by oidcSub. Safe to call on every startup: once the // users table is non-empty, it's a no-op, so leaving // GENIUSRUN_LEGACY_OWNER_OIDC_SUB set after the first successful run causes // no harm. func (db *DB) ClaimLegacyOwner(ctx context.Context, oidcSub string) error { var userCount int if err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM users`).Scan(&userCount); err != nil { return fmt.Errorf("count users: %w", err) } if userCount > 0 { return nil // already bootstrapped (either claimed already, or real signups exist) } var displayName string err := db.QueryRowContext(ctx, `SELECT name FROM profile WHERE user_id IS NULL LIMIT 1`).Scan(&displayName) if err != nil { return fmt.Errorf("find legacy profile: %w", err) } tx, err := db.BeginTx(ctx, nil) if err != nil { return fmt.Errorf("begin claim legacy owner tx: %w", err) } defer tx.Rollback() res, err := tx.ExecContext(ctx, `INSERT INTO users (oidc_sub, display_name) VALUES (?, ?)`, oidcSub, displayName) if err != nil { return fmt.Errorf("create legacy owner user: %w", err) } userID, err := res.LastInsertId() if err != nil { return err } // table is always one of the fixed literals below, never user input. for _, table := range []string{"profile", "workout_kinds", "activities", "sync_state", "sync_runs"} { if _, err := tx.ExecContext(ctx, `UPDATE `+table+` SET user_id = ? WHERE user_id IS NULL`, userID); err != nil { return fmt.Errorf("claim legacy %s rows: %w", table, err) } } return tx.Commit() }