refactor: rename relay to plyr.fm (#83)

* refactor(config): rename relay to plyr.fm

- update app name from 'relay' to 'plyr.fm'
- update canonical host to plyr.fm
- update broadcast channel prefix to 'plyr'
- update CORS default regex to include plyr.fm
- update database default URL to plyr
- update atproto namespace from 'app.relay' to 'app.plyr'
- add old_app_namespace field for migration support
- add old_track_collection computed field
- update resolved_scope to include both old and new namespaces when migrating

* chore(infra): update R2 bucket references to generic names

- production: relay → audio-prod
- staging: relay-stg → audio-staging
- update public bucket URLs to new buckets

* chore(frontend): rename wrangler project from relay to plyr

* docs: update project name from relay to plyr in root docs

authored by zzstoatzz.io and committed by GitHub 34d608d9 da634904

Changed files
+37 -18
frontend
src
backend
+3 -3
CLAUDE.md
··· 1 - # relay 2 3 music streaming platform on ATProto. 4 ··· 14 ## structure 15 16 ``` 17 - relay/ 18 - ├── src/relay/ 19 │ ├── api/ # public endpoints (see api/CLAUDE.md) 20 │ ├── _internal/ # internal services (see _internal/CLAUDE.md) 21 │ ├── models/ # database schemas
··· 1 + # plyr 2 3 music streaming platform on ATProto. 4 ··· 14 ## structure 15 16 ``` 17 + plyr/ 18 + ├── src/backend/ 19 │ ├── api/ # public endpoints (see api/CLAUDE.md) 20 │ ├── _internal/ # internal services (see _internal/CLAUDE.md) 21 │ ├── models/ # database schemas
+2 -2
fly.staging.toml
··· 14 [env] 15 ATPROTO_PDS_URL = 'https://pds.zzstoatzz.io' 16 PORT = '8000' 17 - R2_BUCKET = 'relay-stg' 18 R2_ENDPOINT_URL = 'https://8feb33b5fb57ce2bc093bc6f4141f40a.r2.cloudflarestorage.com' 19 - R2_PUBLIC_BUCKET_URL = 'https://pub-3b2a5274e4f4408cb8b5e0653f300116.r2.dev' 20 STORAGE_BACKEND = 'r2' 21 22 [http_service]
··· 14 [env] 15 ATPROTO_PDS_URL = 'https://pds.zzstoatzz.io' 16 PORT = '8000' 17 + R2_BUCKET = 'audio-staging' 18 R2_ENDPOINT_URL = 'https://8feb33b5fb57ce2bc093bc6f4141f40a.r2.cloudflarestorage.com' 19 + R2_PUBLIC_BUCKET_URL = 'https://pub-0a0a2e70496c461581c9fafb442b269d.r2.dev' 20 STORAGE_BACKEND = 'r2' 21 22 [http_service]
+2 -2
fly.toml
··· 27 [env] 28 PORT = '8000' 29 STORAGE_BACKEND = 'r2' 30 - R2_BUCKET = 'relay' 31 R2_ENDPOINT_URL = 'https://8feb33b5fb57ce2bc093bc6f4141f40a.r2.cloudflarestorage.com' 32 - R2_PUBLIC_BUCKET_URL = 'https://pub-841ec0f5a7854eaab01292d44aca4820.r2.dev' 33 ATPROTO_PDS_URL = 'https://pds.zzstoatzz.io' 34 35 # secrets to set via: fly secrets set KEY=value
··· 27 [env] 28 PORT = '8000' 29 STORAGE_BACKEND = 'r2' 30 + R2_BUCKET = 'audio-prod' 31 R2_ENDPOINT_URL = 'https://8feb33b5fb57ce2bc093bc6f4141f40a.r2.cloudflarestorage.com' 32 + R2_PUBLIC_BUCKET_URL = 'https://pub-d4ed8a1e39d44dac85263d86ad5676fd.r2.dev' 33 ATPROTO_PDS_URL = 'https://pds.zzstoatzz.io' 34 35 # secrets to set via: fly secrets set KEY=value
+1 -1
frontend/wrangler.toml
··· 1 - name = "relay" 2 compatibility_date = "2024-01-01" 3 compatibility_flags = ["nodejs_compat"] 4 pages_build_output_dir = ".svelte-kit/cloudflare"
··· 1 + name = "plyr" 2 compatibility_date = "2024-01-01" 3 compatibility_flags = ["nodejs_compat"] 4 pages_build_output_dir = ".svelte-kit/cloudflare"
+29 -10
src/backend/config.py
··· 59 """Core application configuration.""" 60 61 name: str = Field( 62 - default="relay", 63 description="Public-facing application name", 64 ) 65 tagline: str = Field( ··· 80 description="Interval for background tasks in seconds", 81 ) 82 canonical_host: str = Field( 83 - default="relay.zzstoatzz.io", 84 description="Canonical host used for metadata and share links", 85 ) 86 canonical_url_override: str | None = Field( ··· 88 description="Override canonical URL if it differs from https://{canonical_host}", 89 ) 90 broadcast_channel_prefix: str = Field( 91 - default="relay", 92 description="Prefix used for browser BroadcastChannel identifiers", 93 ) 94 ··· 119 cors_origin_regex: str | None = Field( 120 default=None, 121 validation_alias="FRONTEND_CORS_ORIGIN_REGEX", 122 - description="CORS origin regex pattern (if not set, uses default for relay-4i6.pages.dev)", 123 ) 124 125 @computed_field ··· 129 if self.cors_origin_regex is not None: 130 return self.cors_origin_regex 131 132 - # default: allow localhost for dev + cloudflare pages (including preview deployments) 133 - return r"^(https://([a-z0-9]+\.)?relay-4i6\.pages\.dev|http://localhost:5173)$" 134 135 136 class DatabaseSettings(RelaySettingsSection): 137 """Database configuration.""" 138 139 url: str = Field( 140 - default="postgresql+asyncpg://localhost/relay", 141 validation_alias="DATABASE_URL", 142 description="PostgreSQL connection string", 143 ) ··· 202 description="OAuth redirect URI", 203 ) 204 app_namespace: str = Field( 205 - default="app.relay", 206 validation_alias="ATPROTO_APP_NAMESPACE", 207 description="ATProto app namespace used for record collections", 208 ) 209 scope_override: str | None = Field( 210 default=None, 211 validation_alias="ATPROTO_SCOPE_OVERRIDE", ··· 220 @computed_field 221 @property 222 def track_collection(self) -> str: 223 - """Collection name for relay audio records.""" 224 225 return f"{self.app_namespace}.track" 226 227 @computed_field 228 @property 229 def resolved_scope(self) -> str: 230 - """OAuth scope, falling back to the repo scope for the configured namespace.""" 231 232 if self.scope_override: 233 return self.scope_override 234 return f"atproto repo:{self.track_collection}" 235 236
··· 59 """Core application configuration.""" 60 61 name: str = Field( 62 + default="plyr.fm", 63 description="Public-facing application name", 64 ) 65 tagline: str = Field( ··· 80 description="Interval for background tasks in seconds", 81 ) 82 canonical_host: str = Field( 83 + default="plyr.fm", 84 description="Canonical host used for metadata and share links", 85 ) 86 canonical_url_override: str | None = Field( ··· 88 description="Override canonical URL if it differs from https://{canonical_host}", 89 ) 90 broadcast_channel_prefix: str = Field( 91 + default="plyr", 92 description="Prefix used for browser BroadcastChannel identifiers", 93 ) 94 ··· 119 cors_origin_regex: str | None = Field( 120 default=None, 121 validation_alias="FRONTEND_CORS_ORIGIN_REGEX", 122 + description="CORS origin regex pattern (if not set, uses default for plyr.fm and relay-4i6.pages.dev)", 123 ) 124 125 @computed_field ··· 129 if self.cors_origin_regex is not None: 130 return self.cors_origin_regex 131 132 + # default: allow localhost for dev + plyr.fm + cloudflare pages (including preview deployments) 133 + return r"^(https://(www\.)?plyr\.fm|https://([a-z0-9]+\.)?relay-4i6\.pages\.dev|http://localhost:5173)$" 134 135 136 class DatabaseSettings(RelaySettingsSection): 137 """Database configuration.""" 138 139 url: str = Field( 140 + default="postgresql+asyncpg://localhost/plyr", 141 validation_alias="DATABASE_URL", 142 description="PostgreSQL connection string", 143 ) ··· 202 description="OAuth redirect URI", 203 ) 204 app_namespace: str = Field( 205 + default="app.plyr", 206 validation_alias="ATPROTO_APP_NAMESPACE", 207 description="ATProto app namespace used for record collections", 208 ) 209 + old_app_namespace: str | None = Field( 210 + default=None, 211 + validation_alias="ATPROTO_OLD_APP_NAMESPACE", 212 + description="Optional previous ATProto namespace for migration (e.g., 'app.relay'). When set, OAuth scopes will include both old and new namespaces.", 213 + ) 214 scope_override: str | None = Field( 215 default=None, 216 validation_alias="ATPROTO_SCOPE_OVERRIDE", ··· 225 @computed_field 226 @property 227 def track_collection(self) -> str: 228 + """Collection name for plyr audio records.""" 229 230 return f"{self.app_namespace}.track" 231 232 @computed_field 233 @property 234 + def old_track_collection(self) -> str | None: 235 + """Collection name for old namespace, if migration is active.""" 236 + 237 + if self.old_app_namespace: 238 + return f"{self.old_app_namespace}.track" 239 + return None 240 + 241 + @computed_field 242 + @property 243 def resolved_scope(self) -> str: 244 + """OAuth scope, falling back to the repo scope for the configured namespace(s).""" 245 246 if self.scope_override: 247 return self.scope_override 248 + 249 + # if we have an old namespace, request access to both collections 250 + if self.old_app_namespace: 251 + return f"atproto transition:generic repo:{self.track_collection} repo:{self.old_track_collection}" 252 + 253 return f"atproto repo:{self.track_collection}" 254 255