package environment import ( "context" "regexp" "strings" "unicode/utf8" ) // Legacy database tests still exercise migrations created before gateway-owned // browser selection. These helpers are test-only; production has no such model // or management methods. type BrowserVersion struct { Version string BrowserPath string Note string Enabled bool } var legacyBrowserVersionPattern = regexp.MustCompile(`^[0-9][A-Za-z0-9.+~-]{0,63}$`) func (s *Store) CreateBrowserVersion(ctx context.Context, version BrowserVersion) error { version.Version, version.BrowserPath, version.Note = strings.TrimSpace(version.Version), strings.TrimSpace(version.BrowserPath), strings.TrimSpace(version.Note) if !legacyBrowserVersionPattern.MatchString(version.Version) || !strings.HasPrefix(version.BrowserPath, "/") || utf8.RuneCountInString(version.Note) > 200 { return ErrInvalid } _, err := s.db.ExecContext(ctx, `INSERT INTO browser_version (version, browser_path, note, enabled) VALUES ($1, $2, $3, $4)`, version.Version, version.BrowserPath, version.Note, version.Enabled) return publicDatabaseError(err) } func (s *Store) UpdateBrowserVersion(ctx context.Context, version BrowserVersion) error { version.BrowserPath, version.Note = strings.TrimSpace(version.BrowserPath), strings.TrimSpace(version.Note) if !legacyBrowserVersionPattern.MatchString(version.Version) || !strings.HasPrefix(version.BrowserPath, "/") || utf8.RuneCountInString(version.Note) > 200 { return ErrInvalid } var updated string err := s.db.QueryRowContext(ctx, `UPDATE browser_version SET browser_path = $2, note = $3, enabled = $4, updated_at = now() WHERE version = $1 RETURNING version`, version.Version, version.BrowserPath, version.Note, version.Enabled).Scan(&updated) return rowError(err) } func (s *Store) BrowserPath(ctx context.Context, version string) (string, error) { if !legacyBrowserVersionPattern.MatchString(version) { return "", ErrInvalid } var path string if err := s.db.QueryRowContext(ctx, `SELECT browser_path FROM browser_version WHERE version = $1 AND enabled`, version).Scan(&path); err != nil { return "", rowError(err) } return path, nil } func (s *Store) DeleteBrowserVersion(ctx context.Context, version string) error { if !legacyBrowserVersionPattern.MatchString(version) { return ErrInvalid } var deleted string if err := s.db.QueryRowContext(ctx, `DELETE FROM browser_version WHERE version = $1 RETURNING version`, version).Scan(&deleted); err != nil { return rowError(err) } return nil } func (s *Store) UpgradeEnv(ctx context.Context, alias, version string) error { if !legacyBrowserVersionPattern.MatchString(version) { return ErrInvalid } tx, err := s.db.BeginTx(ctx, nil) if err != nil { return err } defer tx.Rollback() var updated string if err := tx.QueryRowContext(ctx, `UPDATE browser_env SET browser_version = $2, version = version + 1 WHERE alias = $1 RETURNING alias`, alias, version).Scan(&updated); err != nil { return rowError(err) } if _, err := tx.ExecContext(ctx, `UPDATE environment_binding SET version = version + 1, updated_at = now() WHERE browser_env_alias = $1`, alias); err != nil { return err } return commitHub(tx) }