Highly ambitious ATProtocol AppView service and sdks

update cli init command, one shot app view

+777 -197
+378 -28
packages/cli/src/commands/init.ts
··· 1 1 import { parseArgs } from "@std/cli/parse-args"; 2 2 import { join, dirname } from "@std/path"; 3 3 import { ensureDir } from "@std/fs"; 4 + import { cyan, green, bold, dim } from "@std/fmt/colors"; 4 5 import { logger } from "../utils/logger.ts"; 5 6 import { getAllTemplates } from "../templates/embedded.ts"; 7 + import { generateSliceName, generateDomain } from "../utils/name_generator.ts"; 8 + import { createAuthenticatedClient } from "../utils/client.ts"; 9 + import { ConfigManager } from "../auth/config.ts"; 10 + import { pullCommand } from "./lexicon/pull.ts"; 11 + import { pushCommand } from "./lexicon/push.ts"; 12 + import { codegenCommand } from "./codegen.ts"; 13 + import { dasherize } from "../utils/strings.ts"; 14 + import { 15 + SYSTEM_SLICE_URI, 16 + REFERENCE_SLICE_URI, 17 + DEFAULT_API_URL, 18 + DEFAULT_AIP_BASE_URL, 19 + } from "../utils/constants.ts"; 6 20 7 21 export async function initCommand(args: string[], _globalArgs: unknown) { 8 22 const parsed = parseArgs(args, { ··· 19 33 return; 20 34 } 21 35 22 - const projectName = parsed.name || parsed._[0] as string; 36 + // Check if we're inside an existing project 37 + const currentDir = Deno.cwd(); 38 + const projectIndicators = ["deno.json", "slices.json", ".git"]; 39 + 40 + for (const indicator of projectIndicators) { 41 + try { 42 + await Deno.stat(join(currentDir, indicator)); 43 + logger.error( 44 + `Cannot initialize a new project here - found ${indicator} in current directory.` 45 + ); 46 + logger.info( 47 + "Please run this command from outside your project directory." 48 + ); 49 + Deno.exit(1); 50 + } catch { 51 + // File doesn't exist, which is what we want 52 + } 53 + } 54 + 55 + let projectName = parsed.name || (parsed._[0] as string); 56 + let wasNameGenerated = false; 23 57 58 + // If no project name provided, generate a random one 24 59 if (!projectName) { 25 - logger.error("Project name is required"); 26 - console.log("Usage: slices init <project-name> or slices init --name <project-name>"); 27 - Deno.exit(1); 60 + projectName = generateSliceName(); 61 + wasNameGenerated = true; 62 + 63 + console.log( 64 + `\n${cyan("📦 No project name provided, generated one for you:")}` 65 + ); 66 + console.log(` Project name: ${bold(projectName)}`); 67 + 68 + // Ask for confirmation 69 + const proceed = confirm("\nDo you want to proceed with this name?"); 70 + if (!proceed) { 71 + logger.info( 72 + "Initialization cancelled. Run 'slices init <project-name>' to specify your own name." 73 + ); 74 + return; 75 + } 76 + } else { 77 + // Dasherize user-provided name 78 + projectName = dasherize(projectName); 28 79 } 29 80 30 81 // Validate project name 31 82 if (!/^[a-zA-Z0-9_-]+$/.test(projectName)) { 32 - logger.error("Project name can only contain letters, numbers, hyphens, and underscores"); 83 + logger.error( 84 + "Project name can only contain letters, numbers, hyphens, and underscores" 85 + ); 33 86 Deno.exit(1); 34 87 } 35 88 ··· 46 99 // Directory doesn't exist, which is what we want 47 100 } 48 101 49 - logger.info(`Creating new Deno SSR project: ${projectName}`); 102 + logger.info(`Creating project: ${projectName}`); 103 + 104 + // Try to create a slice with the same name 105 + let sliceUri: string | undefined; 106 + let sliceDomain: string | undefined; 107 + let oauthClientId: string | undefined; 108 + let oauthClientSecret: string | undefined; 109 + let oauthRedirectUri: string | undefined; 110 + const config = new ConfigManager(); 111 + await config.load(); 112 + 113 + if (config.isAuthenticated()) { 114 + try { 115 + // If we generated the name, use it for the domain too 116 + // Otherwise, prompt for domain 117 + if (wasNameGenerated) { 118 + sliceDomain = `network.slices.${projectName}`; 119 + } else { 120 + // User provided a name, ask for domain 121 + console.log(`\n${cyan("🌐 Setting up your slice domain:")}`); 122 + console.log( 123 + ` ${dim("Format: com.example (reverse domain notation)")}` 124 + ); 125 + console.log(` ${dim("Leave blank to generate one automatically")}`); 126 + 127 + const userDomain = prompt( 128 + "\nEnter your domain (or press Enter to generate):" 129 + ); 130 + 131 + if (userDomain && userDomain.trim()) { 132 + // Validate domain format (basic check for at least one dot) 133 + if (!userDomain.includes(".")) { 134 + logger.error( 135 + "Domain must contain at least one dot (e.g., com.example)" 136 + ); 137 + Deno.exit(1); 138 + } 139 + sliceDomain = userDomain.trim(); 140 + } else { 141 + // Generate a domain 142 + sliceDomain = generateDomain(); 143 + console.log(` Generated domain: ${bold(sliceDomain)}`); 144 + } 145 + } 146 + 147 + console.log(`\n${cyan("🌐 Creating a slice on the Slices network:")}`); 148 + console.log(` Slice name: ${bold(projectName)}`); 149 + console.log(` Domain: ${bold(sliceDomain)}`); 150 + console.log( 151 + ` ${dim("This will be your unique namespace for collections")}` 152 + ); 153 + 154 + const createSlice = confirm("\nCreate this slice?"); 155 + if (!createSlice) { 156 + logger.info( 157 + "Skipping slice creation. You can create one later at https://slices.network" 158 + ); 159 + } else { 160 + logger.info("Creating slice..."); 161 + const client = await createAuthenticatedClient( 162 + SYSTEM_SLICE_URI, 163 + DEFAULT_API_URL 164 + ); 165 + 166 + // Check if domain already exists in the system slice 167 + const existingSlices = await client.network.slices.slice.getRecords({ 168 + where: { domain: { eq: sliceDomain } }, 169 + limit: 1, 170 + }); 171 + 172 + if (existingSlices.records.length > 0) { 173 + logger.error(`A slice with domain '${sliceDomain}' already exists`); 174 + logger.info("Please try again with a different name or domain"); 175 + Deno.exit(1); 176 + } 177 + 178 + const result = await client.network.slices.slice.createRecord({ 179 + name: projectName, 180 + domain: sliceDomain, 181 + createdAt: new Date().toISOString(), 182 + }); 183 + 184 + sliceUri = result.uri; 185 + logger.success(`Created slice: ${sliceUri}`); 186 + 187 + // Ask if they want to create OAuth client 188 + const createOAuth = confirm( 189 + "\nWould you like to create OAuth credentials for this slice?" 190 + ); 191 + if (createOAuth) { 192 + logger.info("Creating OAuth client..."); 193 + 194 + // Default redirect URI for local development 195 + const redirectUri = "http://localhost:8080/oauth/callback"; 196 + 197 + try { 198 + const oauthResult = 199 + await client.network.slices.slice.createOAuthClient({ 200 + sliceUri: sliceUri, 201 + clientName: `${projectName} Development`, 202 + redirectUris: [redirectUri], 203 + grantTypes: ["authorization_code", "refresh_token"], 204 + responseTypes: ["code"], 205 + scope: "profile openid atproto transition:generic", 206 + }); 207 + 208 + // Store OAuth credentials to add to .env later 209 + oauthClientId = oauthResult.clientId; 210 + oauthClientSecret = oauthResult.clientSecret; 211 + oauthRedirectUri = redirectUri; 212 + 213 + logger.success("Created OAuth client!"); 214 + console.log(` Client ID: ${cyan(oauthResult.clientId)}`); 215 + console.log( 216 + ` ${dim("Client secret will be added to your .env file")}` 217 + ); 218 + } catch (oauthError) { 219 + const err = oauthError as Error; 220 + logger.warn(`Could not create OAuth client: ${err.message}`); 221 + logger.info( 222 + "You can create OAuth credentials later at https://slices.network" 223 + ); 224 + } 225 + } 226 + } 227 + } catch (error) { 228 + const err = error as Error; 229 + logger.warn(`Could not create slice: ${err.message}`); 230 + logger.info("You can create a slice later at https://slices.network"); 231 + } 232 + } else { 233 + logger.info("Not authenticated - skipping slice creation"); 234 + logger.info( 235 + "Run 'slices login' to authenticate, then create a slice at https://slices.network" 236 + ); 237 + } 50 238 51 239 try { 52 240 // Create target directory ··· 55 243 // Extract embedded templates 56 244 await extractEmbeddedTemplates(targetDir, projectName); 57 245 246 + // Update .env.example with slice URI if we created one 247 + if (sliceUri) { 248 + const envExamplePath = join(targetDir, ".env.example"); 249 + try { 250 + let envContent = await Deno.readTextFile(envExamplePath); 251 + envContent = envContent.replace( 252 + /SLICE_URI=.*/, 253 + `SLICE_URI="${sliceUri}"` 254 + ); 255 + await Deno.writeTextFile(envExamplePath, envContent); 256 + } catch { 257 + // Ignore if can't update .env.example 258 + } 259 + } 260 + 261 + // Create .env file with OAuth credentials if we have them 262 + if (oauthClientId && oauthClientSecret) { 263 + const envPath = join(targetDir, ".env"); 264 + const envExamplePath = join(targetDir, ".env.example"); 265 + 266 + try { 267 + // Read the example file as a template 268 + let envContent = await Deno.readTextFile(envExamplePath); 269 + 270 + // Replace with actual values 271 + if (sliceUri) { 272 + envContent = envContent.replace( 273 + /SLICE_URI=.*/, 274 + `SLICE_URI="${sliceUri}"` 275 + ); 276 + } 277 + envContent = envContent.replace( 278 + /OAUTH_CLIENT_ID=.*/, 279 + `OAUTH_CLIENT_ID="${oauthClientId}"` 280 + ); 281 + envContent = envContent.replace( 282 + /OAUTH_CLIENT_SECRET=.*/, 283 + `OAUTH_CLIENT_SECRET="${oauthClientSecret}"` 284 + ); 285 + envContent = envContent.replace( 286 + /OAUTH_REDIRECT_URI=.*/, 287 + `OAUTH_REDIRECT_URI="${oauthRedirectUri}"` 288 + ); 289 + envContent = envContent.replace( 290 + /OAUTH_AIP_BASE_URL=.*/, 291 + `OAUTH_AIP_BASE_URL="${DEFAULT_AIP_BASE_URL}"` 292 + ); 293 + envContent = envContent.replace( 294 + /API_URL=.*/, 295 + `API_URL="${DEFAULT_API_URL}"` 296 + ); 297 + 298 + // Write the actual .env file 299 + await Deno.writeTextFile(envPath, envContent); 300 + logger.success("Created .env file with your OAuth credentials"); 301 + } catch (error) { 302 + const err = error as Error; 303 + logger.warn(`Could not create .env file: ${err.message}`); 304 + } 305 + } 306 + 307 + // Create slices.json file with actual slice URI 308 + if (sliceUri) { 309 + const slicesJsonPath = join(targetDir, "slices.json"); 310 + const slicesConfig = { 311 + slice: sliceUri, 312 + lexiconPath: "./lexicons", 313 + clientOutputPath: "./src/generated_client.ts", 314 + }; 315 + 316 + try { 317 + await Deno.writeTextFile( 318 + slicesJsonPath, 319 + JSON.stringify(slicesConfig, null, 2) + "\n" 320 + ); 321 + logger.success("Created slices.json with your slice configuration"); 322 + } catch (error) { 323 + const err = error as Error; 324 + logger.warn(`Could not create slices.json file: ${err.message}`); 325 + } 326 + } 327 + 328 + // Pull lexicons from the base slice w/ Bsky Profile Lexicons 329 + try { 330 + logger.info("Pulling base lexicons..."); 331 + 332 + // Change to the project directory and run pull 333 + const originalCwd = Deno.cwd(); 334 + Deno.chdir(targetDir); 335 + 336 + // Pull from the base Slices platform slice which has all the proper lexicons 337 + await pullCommand(["--slice", REFERENCE_SLICE_URI], {}); 338 + 339 + logger.success("Pulled base lexicons"); 340 + 341 + // If we created a slice, push the lexicons to it 342 + if (sliceUri) { 343 + logger.info("Pushing lexicons to your slice..."); 344 + await pushCommand([], {}); 345 + logger.success("Pushed lexicons to your slice"); 346 + } 347 + 348 + // Generate TypeScript client from lexicons 349 + logger.info("Generating TypeScript client..."); 350 + await codegenCommand([], {}); 351 + logger.success("Generated TypeScript client"); 352 + 353 + // Change back to original directory 354 + Deno.chdir(originalCwd); 355 + } catch (error) { 356 + const err = error as Error; 357 + logger.warn(`Could not pull lexicons: ${err.message}`); 358 + 359 + // Create empty lexicons folder as fallback 360 + const lexiconsPath = join(targetDir, "lexicons"); 361 + try { 362 + await ensureDir(lexiconsPath); 363 + await Deno.writeTextFile(join(lexiconsPath, ".gitkeep"), ""); 364 + } catch { 365 + // Ignore if we can't create the folder 366 + } 367 + } 368 + 369 + // Initialize git repository 370 + try { 371 + const gitInit = new Deno.Command("git", { 372 + args: ["init"], 373 + cwd: targetDir, 374 + stdout: "piped", 375 + stderr: "piped", 376 + }); 377 + 378 + const result = await gitInit.output(); 379 + if (result.success) { 380 + logger.success("Initialized git repository"); 381 + } else { 382 + const stderr = new TextDecoder().decode(result.stderr); 383 + logger.warn(`Could not initialize git repository: ${stderr}`); 384 + } 385 + } catch (error) { 386 + const err = error as Error; 387 + logger.warn(`Could not initialize git repository: ${err.message}`); 388 + } 389 + 58 390 logger.success(`Created project: ${projectName}`); 59 - console.log(` 60 - Get started: 61 - cd ${projectName} 62 - cp .env.example .env 63 - deno task dev 391 + 392 + console.log(`\n${green("✨ Your project is ready!")}\n`); 393 + console.log(`${bold("Next steps:")}`); 394 + console.log(` 1. cd ${cyan(projectName)}`); 395 + if (oauthClientId && oauthClientSecret) { 396 + console.log(` 2. deno task dev`); 397 + console.log(` ${dim(" Your .env file is already configured!")}`); 398 + } else { 399 + console.log(` 2. cp .env.example .env`); 400 + console.log(` 3. ${dim("# Add your OAuth credentials to .env")}`); 401 + console.log(` 4. deno task dev`); 402 + } 403 + 404 + if (sliceUri && sliceDomain) { 405 + console.log(`\n${bold("Your slice details:")}`); 406 + console.log(` Name: ${cyan(projectName)}`); 407 + console.log(` Domain: ${cyan(sliceDomain)}`); 408 + console.log(` URI: ${dim(sliceUri)}`); 409 + } 64 410 65 - Available commands: 66 - deno task dev Start development server 67 - deno task start Start production server 68 - deno fmt Format code 69 - `); 411 + console.log(`\n${bold("Available commands:")}`); 412 + console.log(` ${cyan("deno task dev")} Start development server`); 413 + console.log(` ${cyan("deno task start")} Start production server`); 414 + console.log(` ${cyan("deno fmt")} Format code`); 70 415 } catch (error) { 71 416 const err = error as Error; 72 417 logger.error("Failed to create project:", err.message); ··· 74 419 } 75 420 } 76 421 77 - async function extractEmbeddedTemplates(targetDir: string, projectName: string) { 422 + async function extractEmbeddedTemplates( 423 + targetDir: string, 424 + projectName: string 425 + ) { 78 426 try { 79 427 const templates = getAllTemplates(); 80 428 ··· 106 454 107 455 async function processTemplateFiles(targetDir: string, projectName: string) { 108 456 // Process files that need variable replacement 109 - const filesToProcess = [ 110 - "README.md", 111 - ]; 457 + const filesToProcess = ["README.md", "src/config.ts"]; 112 458 113 459 for (const file of filesToProcess) { 114 460 const filePath = join(targetDir, file); ··· 128 474 Initialize a new Deno SSR project with OAuth authentication 129 475 130 476 USAGE: 131 - slices init <project-name> 132 - slices init --name <project-name> 477 + slices init <project-name> Create project with specified name 478 + slices init Create project with random generated name 479 + slices init --name <name> Create project with specified name 133 480 134 481 ARGUMENTS: 135 - <project-name> Name of the project to create 482 + <project-name> Name of the project to create (optional) 136 483 137 484 OPTIONS: 138 485 -n, --name <name> Project name 139 486 -h, --help Show this help message 140 487 141 488 EXAMPLES: 142 - slices init my-app 143 - slices init --name my-awesome-project 489 + slices init my-app Creates "my-app" project and slice 490 + slices init Creates project with random name like "stellar-wave" 491 + slices init --name my-project 144 492 145 - The generated project includes: 493 + FEATURES: 494 + • Automatically creates a matching slice (if authenticated) 495 + • Generates domain in format: network.slices.<project-name> 496 + • Updates .env.example with slice URI 146 497 • Deno SSR with Preact and JSX 147 498 • OAuth authentication with PKCE flow 148 499 • Session management with SQLite 149 500 • HTMX for interactive components 150 501 • Tailwind CSS for styling 151 502 • Feature-based architecture 152 - • Comprehensive documentation 153 503 `); 154 - } 504 + }
+1 -1
packages/cli/src/templates/deno-ssr/.env.example
··· 6 6 7 7 # API Configuration (required) 8 8 API_URL=https://api.slices.network 9 - SLICE_URI=at://did:plc:example/network.slices.slice/example 9 + SLICE_URI=at://did:plc:bcgltzqazw5tb6k2g3ttenbj/network.slices.slice/3lzbzumcmvo2z 10 10 11 11 # Database (optional, defaults to slices.db) 12 12 DATABASE_URL=slices.db
+3 -28
packages/cli/src/templates/deno-ssr/.gitignore
··· 1 - # Environment variables 2 - .env 3 - 4 - # Database 5 - *.db 6 - *.db-journal 7 - *.db-wal 8 - *.db-shm 9 - 10 - # Deno 11 - deno.lock 12 - 13 - # OS generated files 14 - .DS_Store 15 - Thumbs.db 16 - 17 - # IDE 18 - .vscode/ 19 - .idea/ 20 - *.swp 21 - *.swo 22 - 23 - # Logs 24 - *.log 25 - logs/ 26 - 27 - # Coverage 28 - coverage/ 1 + .env* 2 + node_modules 3 + *.db*
+22 -16
packages/cli/src/templates/deno-ssr/README.md
··· 5 5 6 6 ## Quick Start 7 7 8 - 1. **Copy environment file:** 9 - ```bash 10 - cp .env.example .env 11 - ``` 8 + ```bash 9 + # Start the development server 10 + deno task dev 11 + ``` 12 12 13 - 2. **Configure OAuth:** Edit `.env` with your OAuth client credentials from 14 - Slices 13 + Visit your app at http://localhost:8080 15 14 16 - 3. **Start development server:** 17 - ```bash 18 - deno task dev 19 - ``` 20 - 21 - 4. **Visit your app:** Open http://localhost:8080 15 + > **Note:** Your slice and OAuth credentials were automatically configured 16 + > during project creation. The `.env` file is already set up with your 17 + > credentials. 22 18 23 19 ## Features 24 20 ··· 49 45 ## Project Structure 50 46 51 47 ``` 48 + slices.json # Slices configuration file 49 + lexicons/ # AT Protocol lexicon definitions 52 50 src/ 53 51 ├── main.ts # Server entry point 54 52 ├── config.ts # OAuth & session configuration 53 + ├── generated_client.ts # Generated TypeScript client from lexicons 55 54 ├── routes/ # Route definitions 56 55 ├── features/ # Feature modules 57 56 │ └── auth/ # Authentication ··· 61 60 62 61 ## OAuth Setup 63 62 64 - 1. Register your application at 65 - [Slices Developer Portal](https://slices.network) 66 - 2. Add your OAuth credentials to `.env` 67 - 3. Configure redirect URI: `http://localhost:8080/oauth/callback` 63 + Your OAuth application was automatically created during project initialization 64 + with: 65 + 66 + - **Client ID & Secret**: Already configured in `.env` 67 + - **Redirect URI**: `http://localhost:8080/oauth/callback` 68 + - **Slice**: Automatically created and linked 69 + 70 + To manage your OAuth clients or create additional ones: 71 + 72 + 1. Visit [Slices Network](https://slices.network) 73 + 2. Use the `slices login` CLI command 68 74 69 75 ## Documentation 70 76
+5 -4
packages/cli/src/templates/deno-ssr/deno.json
··· 8 8 "jsxImportSource": "preact" 9 9 }, 10 10 "imports": { 11 - "@slices/client": "jsr:@slices/client@^0.1.0-alpha.3", 12 - "@slices/oauth": "jsr:@slices/oauth@^0.4.1", 13 - "@slices/session": "jsr:@slices/session@^0.2.1", 11 + "@slices/client": "jsr:@slices/client@^0.1.0-alpha.4", 12 + "@slices/oauth": "jsr:@slices/oauth@^0.6.0", 13 + "@slices/session": "jsr:@slices/session@^0.3.0", 14 14 "@std/assert": "jsr:@std/assert@^1.0.14", 15 + "@std/fmt": "jsr:@std/fmt@^1.0.8", 15 16 "preact": "npm:preact@^10.27.1", 16 17 "preact-render-to-string": "npm:preact-render-to-string@^6.5.13", 17 18 "typed-htmx": "npm:typed-htmx@^0.3.1", ··· 21 22 "lucide-preact": "npm:lucide-preact@^0.544.0" 22 23 }, 23 24 "nodeModulesDir": "auto" 24 - } 25 + }
+33 -20
packages/cli/src/templates/deno-ssr/src/config.ts
··· 1 1 import { OAuthClient, SQLiteOAuthStorage } from "@slices/oauth"; 2 2 import { SessionStore, SQLiteAdapter, withOAuthSession } from "@slices/session"; 3 + import { AtProtoClient } from "./generated_client.ts"; 3 4 4 5 const OAUTH_CLIENT_ID = Deno.env.get("OAUTH_CLIENT_ID"); 5 6 const OAUTH_CLIENT_SECRET = Deno.env.get("OAUTH_CLIENT_SECRET"); ··· 26 27 27 28 // OAuth setup 28 29 const oauthStorage = new SQLiteOAuthStorage(DATABASE_URL); 29 - const oauthClient = new OAuthClient( 30 - { 31 - clientId: OAUTH_CLIENT_ID, 32 - clientSecret: OAUTH_CLIENT_SECRET, 33 - authBaseUrl: OAUTH_AIP_BASE_URL, 34 - redirectUri: OAUTH_REDIRECT_URI, 35 - scopes: [ 36 - "openid", 37 - "email", 38 - "profile", 39 - "atproto", 40 - "transition:generic", 41 - ], 42 - }, 43 - oauthStorage 44 - ); 30 + const oauthConfig = { 31 + clientId: OAUTH_CLIENT_ID, 32 + clientSecret: OAUTH_CLIENT_SECRET, 33 + authBaseUrl: OAUTH_AIP_BASE_URL, 34 + redirectUri: OAUTH_REDIRECT_URI, 35 + scopes: ["atproto", "openid", "profile"], 36 + }; 37 + 38 + // Export config and storage for creating user-scoped clients 39 + export { oauthConfig, oauthStorage }; 45 40 46 41 // Session setup (shared database) 47 42 export const sessionStore = new SessionStore({ 48 43 adapter: new SQLiteAdapter(DATABASE_URL), 44 + cookieName: "{{PROJECT_NAME}}-session", 49 45 cookieOptions: { 50 46 httpOnly: true, 51 47 secure: Deno.env.get("DENO_ENV") === "production", ··· 55 51 }); 56 52 57 53 // OAuth + Session integration 58 - export const oauthSessions = withOAuthSession(sessionStore, oauthClient, { 59 - autoRefresh: true, 60 - }); 54 + export const oauthSessions = withOAuthSession( 55 + sessionStore, 56 + oauthConfig, 57 + oauthStorage, 58 + { 59 + autoRefresh: true, 60 + } 61 + ); 61 62 62 - export { oauthClient }; 63 + // Helper function to create user-scoped OAuth client 64 + export function createOAuthClient(userId: string): OAuthClient { 65 + return new OAuthClient(oauthConfig, oauthStorage, userId); 66 + } 67 + 68 + // Helper function to create authenticated AtProto client for a user 69 + export function createSessionClient(userId: string): AtProtoClient { 70 + const userOAuthClient = createOAuthClient(userId); 71 + return new AtProtoClient(API_URL!, SLICE_URI!, userOAuthClient); 72 + } 73 + 74 + // Public client for unauthenticated requests 75 + export const publicClient = new AtProtoClient(API_URL, SLICE_URI);
+41 -32
packages/cli/src/templates/deno-ssr/src/features/auth/handlers.tsx
··· 1 1 import type { Route } from "@std/http/unstable-route"; 2 2 import { withAuth } from "../../routes/middleware.ts"; 3 - import { oauthClient, oauthSessions, sessionStore } from "../../config.ts"; 3 + import { OAuthClient } from "@slices/oauth"; 4 + import { 5 + createOAuthClient, 6 + createSessionClient, 7 + oauthConfig, 8 + oauthStorage, 9 + oauthSessions, 10 + sessionStore, 11 + } from "../../config.ts"; 4 12 import { renderHTML } from "../../utils/render.tsx"; 5 13 import { LoginPage } from "./templates/LoginPage.tsx"; 6 14 ··· 14 22 } 15 23 16 24 const error = url.searchParams.get("error"); 17 - return renderHTML( 18 - <LoginPage error={error || undefined} /> 19 - ); 25 + return renderHTML(<LoginPage error={error || undefined} />); 20 26 } 21 27 22 28 async function handleOAuthAuthorize(req: Request): Promise<Response> { ··· 28 34 return new Response("Missing login hint", { status: 400 }); 29 35 } 30 36 31 - const authResult = await oauthClient.authorize({ 37 + const tempOAuthClient = new OAuthClient( 38 + oauthConfig, 39 + oauthStorage, 40 + loginHint 41 + ); 42 + const authResult = await tempOAuthClient.authorize({ 32 43 loginHint, 33 44 }); 34 45 35 46 return Response.redirect(authResult.authorizationUrl, 302); 36 47 } catch (error) { 37 48 console.error("OAuth authorize error:", error); 49 + 38 50 return Response.redirect( 39 51 new URL( 40 - "/login?error=" + encodeURIComponent("OAuth initialization failed"), 52 + "/login?error=" + 53 + encodeURIComponent("Please check your handle and try again."), 41 54 req.url 42 55 ), 43 56 302 ··· 61 74 ); 62 75 } 63 76 64 - await oauthClient.handleCallback({ code, state }); 65 - const sessionId = await oauthSessions.createOAuthSession(); 77 + const tempOAuthClient = new OAuthClient(oauthConfig, oauthStorage, "temp"); 78 + const tokens = await tempOAuthClient.handleCallback({ code, state }); 79 + const sessionId = await oauthSessions.createOAuthSession(tokens); 66 80 67 81 if (!sessionId) { 68 82 return Response.redirect( ··· 76 90 77 91 const sessionCookie = sessionStore.createSessionCookie(sessionId); 78 92 93 + let userInfo; 94 + try { 95 + const sessionOAuthClient = createOAuthClient(sessionId); 96 + userInfo = await sessionOAuthClient.getUserInfo(); 97 + } catch (error) { 98 + console.error("Failed to get user info:", error); 99 + } 100 + 101 + if (userInfo?.sub) { 102 + try { 103 + const userClient = createSessionClient(sessionId); 104 + await userClient.syncUserCollections(); 105 + console.log("Synced Bluesky profile for", userInfo.sub); 106 + } catch (error) { 107 + console.error("Error syncing Bluesky profile:", error); 108 + } 109 + } 110 + 79 111 return new Response(null, { 80 112 status: 302, 81 113 headers: { ··· 113 145 }); 114 146 } 115 147 116 - async function handleDashboard(req: Request): Promise<Response> { 117 - const context = await withAuth(req); 118 - 119 - if (!context.currentUser) { 120 - return Response.redirect(new URL("/login", req.url), 302); 121 - } 122 - 123 - return renderHTML( 124 - <div> 125 - <h1>Dashboard</h1> 126 - <p>Welcome, {context.currentUser.name || context.currentUser.sub}!</p> 127 - <form method="post" action="/logout"> 128 - <button type="submit">Logout</button> 129 - </form> 130 - </div> 131 - ); 132 - } 133 - 134 148 export const authRoutes: Route[] = [ 135 149 { 136 150 method: "GET", ··· 152 166 pattern: new URLPattern({ pathname: "/logout" }), 153 167 handler: handleLogout, 154 168 }, 155 - { 156 - method: "GET", 157 - pattern: new URLPattern({ pathname: "/dashboard" }), 158 - handler: handleDashboard, 159 - }, 160 - ]; 169 + ];
+49
packages/cli/src/templates/deno-ssr/src/features/dashboard/handlers.tsx
··· 1 + import type { Route } from "@std/http/unstable-route"; 2 + import { withAuth } from "../../routes/middleware.ts"; 3 + import { renderHTML } from "../../utils/render.tsx"; 4 + import { DashboardPage } from "./templates/DashboardPage.tsx"; 5 + import { publicClient } from "../../config.ts"; 6 + import { recordBlobToCdnUrl } from "@slices/client"; 7 + import { AppBskyActorProfile } from "../../generated_client.ts"; 8 + 9 + async function handleDashboard(req: Request): Promise<Response> { 10 + const context = await withAuth(req); 11 + 12 + if (!context.currentUser) { 13 + return Response.redirect(new URL("/login", req.url), 302); 14 + } 15 + 16 + let profile: AppBskyActorProfile | undefined; 17 + let avatarUrl: string | undefined; 18 + try { 19 + const profileResult = await publicClient.app.bsky.actor.profile.getRecord({ 20 + uri: `at://${context.currentUser.sub}/app.bsky.actor.profile/self`, 21 + }); 22 + 23 + if (profileResult) { 24 + profile = profileResult.value; 25 + 26 + if (profile.avatar) { 27 + avatarUrl = recordBlobToCdnUrl(profileResult, profile.avatar, "avatar"); 28 + } 29 + } 30 + } catch (error) { 31 + console.error("Error fetching profile:", error); 32 + } 33 + 34 + return renderHTML( 35 + <DashboardPage 36 + currentUser={context.currentUser} 37 + profile={profile} 38 + avatarUrl={avatarUrl} 39 + /> 40 + ); 41 + } 42 + 43 + export const dashboardRoutes: Route[] = [ 44 + { 45 + method: "GET", 46 + pattern: new URLPattern({ pathname: "/dashboard" }), 47 + handler: handleDashboard, 48 + }, 49 + ];
+56
packages/cli/src/templates/deno-ssr/src/features/dashboard/templates/DashboardPage.tsx
··· 1 + import { Layout } from "../../../shared/fragments/Layout.tsx"; 2 + import { Button } from "../../../shared/fragments/Button.tsx"; 3 + import type { AppBskyActorProfile } from "../../../generated_client.ts"; 4 + 5 + interface DashboardPageProps { 6 + currentUser: { 7 + name?: string; 8 + sub: string; 9 + }; 10 + profile?: AppBskyActorProfile; 11 + avatarUrl?: string; 12 + } 13 + 14 + export function DashboardPage({ 15 + currentUser, 16 + profile, 17 + avatarUrl, 18 + }: DashboardPageProps) { 19 + return ( 20 + <Layout title="Dashboard"> 21 + <div className="min-h-screen bg-gray-50 p-8"> 22 + <div className="max-w-2xl mx-auto"> 23 + <div className="bg-white rounded-lg shadow p-6"> 24 + <div className="flex justify-between items-center mb-6"> 25 + <h1 className="text-2xl font-bold">Dashboard</h1> 26 + <form method="post" action="/logout"> 27 + <Button type="submit" variant="secondary"> 28 + Logout 29 + </Button> 30 + </form> 31 + </div> 32 + 33 + <div className="mb-6"> 34 + {avatarUrl && ( 35 + <img 36 + src={avatarUrl} 37 + alt="Profile" 38 + className="w-20 h-20 rounded-full mb-4" 39 + /> 40 + )} 41 + <h2 className="text-xl font-semibold mb-2"> 42 + {profile?.displayName || currentUser.name || currentUser.sub} 43 + </h2> 44 + {currentUser.name && ( 45 + <p className="text-gray-600 mb-2">@{currentUser.name}</p> 46 + )} 47 + {profile?.description && ( 48 + <p className="text-gray-700 mt-2">{profile.description}</p> 49 + )} 50 + </div> 51 + </div> 52 + </div> 53 + </div> 54 + </Layout> 55 + ); 56 + }
+7 -5
packages/cli/src/templates/deno-ssr/src/routes/middleware.ts
··· 1 - import { sessionStore, oauthSessions, oauthClient } from "../config.ts"; 1 + import { sessionStore, createOAuthClient } from "../config.ts"; 2 2 3 3 export interface AuthContext { 4 4 currentUser: { ··· 17 17 } 18 18 19 19 try { 20 - // Get user info from OAuth client 21 - const userInfo = await oauthClient.getUserInfo(); 20 + const sessionOAuthClient = createOAuthClient(session.sessionId); 21 + const userInfo = await sessionOAuthClient.getUserInfo(); 22 22 return { 23 23 currentUser: userInfo || null, 24 24 sessionId: session.sessionId, ··· 28 28 } 29 29 } 30 30 31 - export function requireAuth(handler: (req: Request, context: AuthContext) => Promise<Response>) { 31 + export function requireAuth( 32 + handler: (req: Request, context: AuthContext) => Promise<Response> 33 + ) { 32 34 return async (req: Request): Promise<Response> => { 33 35 const context = await withAuth(req); 34 36 ··· 38 40 39 41 return handler(req, context); 40 42 }; 41 - } 43 + }
+4
packages/cli/src/templates/deno-ssr/src/routes/mod.ts
··· 1 1 import type { Route } from "@std/http/unstable-route"; 2 2 import { authRoutes } from "../features/auth/handlers.tsx"; 3 + import { dashboardRoutes } from "../features/dashboard/handlers.tsx"; 3 4 4 5 export const allRoutes: Route[] = [ 5 6 // Root redirect to login for now ··· 11 12 12 13 // Auth routes 13 14 ...authRoutes, 15 + 16 + // Dashboard routes 17 + ...dashboardRoutes, 14 18 ];
+10 -11
packages/cli/src/templates/deno-ssr/src/shared/fragments/Button.tsx
··· 1 - import { clsx } from "clsx"; 1 + import { ComponentChildren, JSX } from "preact"; 2 + import { cn } from "../../utils/cn.ts"; 2 3 3 - interface ButtonProps { 4 - children: any; 5 - type?: "button" | "submit" | "reset"; 4 + interface ButtonProps extends Omit<JSX.IntrinsicElements['button'], "size"> { 5 + children: ComponentChildren; 6 6 variant?: "primary" | "secondary" | "danger"; 7 7 size?: "sm" | "md" | "lg"; 8 - className?: string; 9 - disabled?: boolean; 10 - [key: string]: any; 11 8 } 12 9 13 10 export function Button({ ··· 19 16 disabled, 20 17 ...props 21 18 }: ButtonProps) { 22 - const baseClasses = "inline-flex items-center justify-center font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed"; 19 + const baseClasses = 20 + "inline-flex items-center justify-center font-medium rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed"; 23 21 24 22 const variantClasses = { 25 23 primary: "bg-blue-600 hover:bg-blue-700 text-white focus:ring-blue-500", 26 - secondary: "bg-gray-200 hover:bg-gray-300 text-gray-900 focus:ring-gray-500", 24 + secondary: 25 + "bg-gray-200 hover:bg-gray-300 text-gray-900 focus:ring-gray-500", 27 26 danger: "bg-red-600 hover:bg-red-700 text-white focus:ring-red-500", 28 27 }; 29 28 ··· 37 36 <button 38 37 type={type} 39 38 disabled={disabled} 40 - className={clsx( 39 + className={cn( 41 40 baseClasses, 42 41 variantClasses[variant], 43 42 sizeClasses[size], ··· 48 47 {children} 49 48 </button> 50 49 ); 51 - } 50 + }
+4 -13
packages/cli/src/templates/deno-ssr/src/shared/fragments/Input.tsx
··· 1 - import { clsx } from "clsx"; 1 + import { JSX } from "preact"; 2 + import { cn } from "../../utils/cn.ts"; 2 3 3 - interface InputProps { 4 - type?: string; 5 - name?: string; 6 - id?: string; 7 - placeholder?: string; 8 - required?: boolean; 9 - disabled?: boolean; 10 - className?: string; 11 - value?: string; 12 - [key: string]: any; 13 - } 4 + type InputProps = JSX.IntrinsicElements['input']; 14 5 15 6 export function Input({ 16 7 type = "text", ··· 20 11 return ( 21 12 <input 22 13 type={type} 23 - className={clsx( 14 + className={cn( 24 15 "block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm", 25 16 "focus:outline-none focus:ring-blue-500 focus:border-blue-500", 26 17 "disabled:bg-gray-50 disabled:text-gray-500",
+3 -1
packages/cli/src/templates/deno-ssr/src/shared/fragments/Layout.tsx
··· 1 + import { ComponentChildren } from "preact"; 2 + 1 3 interface LayoutProps { 2 4 title?: string; 3 - children: any; 5 + children: ComponentChildren; 4 6 } 5 7 6 8 export function Layout({ title = "App", children }: LayoutProps) {
+6
packages/cli/src/templates/deno-ssr/src/utils/cn.ts
··· 1 + import { type ClassValue, clsx } from "clsx"; 2 + import { twMerge } from "tailwind-merge"; 3 + 4 + export function cn(...inputs: ClassValue[]): string { 5 + return twMerge(clsx(inputs)); 6 + }
+29 -4
packages/cli/src/templates/deno-ssr/src/utils/logging.ts
··· 1 - export function createLoggingHandler(handler: (req: Request) => Response | Promise<Response>) { 1 + import { cyan, green, red, yellow, bold, dim } from "@std/fmt/colors"; 2 + 3 + export function createLoggingHandler( 4 + handler: (req: Request) => Response | Promise<Response> 5 + ) { 2 6 return async (req: Request): Promise<Response> => { 3 7 const start = Date.now(); 4 8 const method = req.method; ··· 7 11 try { 8 12 const response = await Promise.resolve(handler(req)); 9 13 const duration = Date.now() - start; 10 - console.log(`${method} ${url.pathname} - ${response.status} (${duration}ms)`); 14 + 15 + const methodColor = cyan(bold(method)); 16 + const statusColor = 17 + response.status >= 200 && response.status < 300 18 + ? green(String(response.status)) 19 + : response.status >= 300 && response.status < 400 20 + ? yellow(String(response.status)) 21 + : response.status >= 400 22 + ? red(String(response.status)) 23 + : String(response.status); 24 + const durationText = dim(`(${duration}ms)`); 25 + 26 + console.log( 27 + `${methodColor} ${url.pathname} - ${statusColor} ${durationText}` 28 + ); 11 29 return response; 12 30 } catch (error) { 13 31 const duration = Date.now() - start; 14 - console.error(`${method} ${url.pathname} - ERROR (${duration}ms):`, error); 32 + const methodColor = cyan(bold(method)); 33 + const errorText = red(bold("ERROR")); 34 + const durationText = dim(`(${duration}ms)`); 35 + 36 + console.error( 37 + `${methodColor} ${url.pathname} - ${errorText} ${durationText}:`, 38 + error 39 + ); 15 40 throw error; 16 41 } 17 42 }; 18 - } 43 + }
+2 -1
packages/cli/src/templates/deno-ssr/src/utils/render.tsx
··· 1 1 import { renderToString } from "preact-render-to-string"; 2 + import { VNode } from "preact"; 2 3 3 - export function renderHTML(element: any): Response { 4 + export function renderHTML(element: VNode): Response { 4 5 const html = renderToString(element); 5 6 6 7 return new Response(html, {
+25 -13
packages/cli/src/templates/embedded.ts
··· 9 9 export const EMBEDDED_TEMPLATES: EmbeddedTemplate[] = [ 10 10 { 11 11 "path": "deno.json", 12 - "content": "ewogICJ0YXNrcyI6IHsKICAgICJzdGFydCI6ICJkZW5vIHJ1biAtQSAtLWVudi1maWxlPS5lbnYgc3JjL21haW4udHMiLAogICAgImRldiI6ICJkZW5vIHJ1biAtQSAtLWVudi1maWxlPS5lbnYgLS13YXRjaCBzcmMvbWFpbi50cyIKICB9LAogICJjb21waWxlck9wdGlvbnMiOiB7CiAgICAianN4IjogInByZWNvbXBpbGUiLAogICAgImpzeEltcG9ydFNvdXJjZSI6ICJwcmVhY3QiCiAgfSwKICAiaW1wb3J0cyI6IHsKICAgICJAc2xpY2VzL2NsaWVudCI6ICJqc3I6QHNsaWNlcy9jbGllbnRAXjAuMS4wLWFscGhhLjMiLAogICAgIkBzbGljZXMvb2F1dGgiOiAianNyOkBzbGljZXMvb2F1dGhAXjAuNC4xIiwKICAgICJAc2xpY2VzL3Nlc3Npb24iOiAianNyOkBzbGljZXMvc2Vzc2lvbkBeMC4yLjEiLAogICAgIkBzdGQvYXNzZXJ0IjogImpzcjpAc3RkL2Fzc2VydEBeMS4wLjE0IiwKICAgICJwcmVhY3QiOiAibnBtOnByZWFjdEBeMTAuMjcuMSIsCiAgICAicHJlYWN0LXJlbmRlci10by1zdHJpbmciOiAibnBtOnByZWFjdC1yZW5kZXItdG8tc3RyaW5nQF42LjUuMTMiLAogICAgInR5cGVkLWh0bXgiOiAibnBtOnR5cGVkLWh0bXhAXjAuMy4xIiwKICAgICJAc3RkL2h0dHAiOiAianNyOkBzdGQvaHR0cEBeMS4wLjIwIiwKICAgICJjbHN4IjogIm5wbTpjbHN4QF4yLjEuMSIsCiAgICAidGFpbHdpbmQtbWVyZ2UiOiAibnBtOnRhaWx3aW5kLW1lcmdlQF4yLjUuNSIsCiAgICAibHVjaWRlLXByZWFjdCI6ICJucG06bHVjaWRlLXByZWFjdEBeMC41NDQuMCIKICB9LAogICJub2RlTW9kdWxlc0RpciI6ICJhdXRvIgp9" 12 + "content": "ewogICJ0YXNrcyI6IHsKICAgICJzdGFydCI6ICJkZW5vIHJ1biAtQSAtLWVudi1maWxlPS5lbnYgc3JjL21haW4udHMiLAogICAgImRldiI6ICJkZW5vIHJ1biAtQSAtLWVudi1maWxlPS5lbnYgLS13YXRjaCBzcmMvbWFpbi50cyIKICB9LAogICJjb21waWxlck9wdGlvbnMiOiB7CiAgICAianN4IjogInByZWNvbXBpbGUiLAogICAgImpzeEltcG9ydFNvdXJjZSI6ICJwcmVhY3QiCiAgfSwKICAiaW1wb3J0cyI6IHsKICAgICJAc2xpY2VzL2NsaWVudCI6ICJqc3I6QHNsaWNlcy9jbGllbnRAXjAuMS4wLWFscGhhLjQiLAogICAgIkBzbGljZXMvb2F1dGgiOiAianNyOkBzbGljZXMvb2F1dGhAXjAuNi4wIiwKICAgICJAc2xpY2VzL3Nlc3Npb24iOiAianNyOkBzbGljZXMvc2Vzc2lvbkBeMC4zLjAiLAogICAgIkBzdGQvYXNzZXJ0IjogImpzcjpAc3RkL2Fzc2VydEBeMS4wLjE0IiwKICAgICJAc3RkL2ZtdCI6ICJqc3I6QHN0ZC9mbXRAXjEuMC44IiwKICAgICJwcmVhY3QiOiAibnBtOnByZWFjdEBeMTAuMjcuMSIsCiAgICAicHJlYWN0LXJlbmRlci10by1zdHJpbmciOiAibnBtOnByZWFjdC1yZW5kZXItdG8tc3RyaW5nQF42LjUuMTMiLAogICAgInR5cGVkLWh0bXgiOiAibnBtOnR5cGVkLWh0bXhAXjAuMy4xIiwKICAgICJAc3RkL2h0dHAiOiAianNyOkBzdGQvaHR0cEBeMS4wLjIwIiwKICAgICJjbHN4IjogIm5wbTpjbHN4QF4yLjEuMSIsCiAgICAidGFpbHdpbmQtbWVyZ2UiOiAibnBtOnRhaWx3aW5kLW1lcmdlQF4yLjUuNSIsCiAgICAibHVjaWRlLXByZWFjdCI6ICJucG06bHVjaWRlLXByZWFjdEBeMC41NDQuMCIKICB9LAogICJub2RlTW9kdWxlc0RpciI6ICJhdXRvIgp9Cg==" 13 13 }, 14 14 { 15 15 "path": "README.md", 16 - "content": "IyB7e1BST0pFQ1RfTkFNRX19CgpBIERlbm8gU1NSIHdlYiBhcHBsaWNhdGlvbiB3aXRoIEFUIFByb3RvY29sIGludGVncmF0aW9uLCBidWlsdCB3aXRoIFByZWFjdCwKSFRNWCwgYW5kIE9BdXRoIGF1dGhlbnRpY2F0aW9uLgoKIyMgUXVpY2sgU3RhcnQKCjEuICoqQ29weSBlbnZpcm9ubWVudCBmaWxlOioqCiAgIGBgYGJhc2gKICAgY3AgLmVudi5leGFtcGxlIC5lbnYKICAgYGBgCgoyLiAqKkNvbmZpZ3VyZSBPQXV0aDoqKiBFZGl0IGAuZW52YCB3aXRoIHlvdXIgT0F1dGggY2xpZW50IGNyZWRlbnRpYWxzIGZyb20KICAgU2xpY2VzCgozLiAqKlN0YXJ0IGRldmVsb3BtZW50IHNlcnZlcjoqKgogICBgYGBiYXNoCiAgIGRlbm8gdGFzayBkZXYKICAgYGBgCgo0LiAqKlZpc2l0IHlvdXIgYXBwOioqIE9wZW4gaHR0cDovL2xvY2FsaG9zdDo4MDgwCgojIyBGZWF0dXJlcwoKLSDwn5SQICoqT0F1dGggQXV0aGVudGljYXRpb24qKiB3aXRoIFBLQ0UgZmxvdwotIOKaoSAqKlNlcnZlci1TaWRlIFJlbmRlcmluZyoqIHdpdGggUHJlYWN0Ci0g8J+OryAqKkludGVyYWN0aXZlIFVJKiogd2l0aCBIVE1YCi0g8J+OqCAqKlN0eWxpbmcqKiB3aXRoIFRhaWx3aW5kIENTUwotIPCfl4TvuI8gKipTZXNzaW9uIE1hbmFnZW1lbnQqKiB3aXRoIFNRTGl0ZQotIPCflIQgKipBdXRvIFRva2VuIFJlZnJlc2gqKgotIPCfj5fvuI8gKipGZWF0dXJlLUJhc2VkIEFyY2hpdGVjdHVyZSoqCgojIyBEZXZlbG9wbWVudAoKYGBgYmFzaAojIFN0YXJ0IGRldmVsb3BtZW50IHNlcnZlciB3aXRoIGhvdCByZWxvYWQKZGVubyB0YXNrIGRldgoKIyBTdGFydCBwcm9kdWN0aW9uIHNlcnZlcgpkZW5vIHRhc2sgc3RhcnQKCiMgRm9ybWF0IGNvZGUKZGVubyBmbXQKCiMgQ2hlY2sgdHlwZXMKZGVubyBjaGVjayBzcmMvKiovKi50cyBzcmMvKiovKi50c3gKYGBgCgojIyBQcm9qZWN0IFN0cnVjdHVyZQoKYGBgCnNyYy8K4pSc4pSA4pSAIG1haW4udHMgICAgICAgICAgICAgICMgU2VydmVyIGVudHJ5IHBvaW50CuKUnOKUgOKUgCBjb25maWcudHMgICAgICAgICAgICAjIE9BdXRoICYgc2Vzc2lvbiBjb25maWd1cmF0aW9uCuKUnOKUgOKUgCByb3V0ZXMvICAgICAgICAgICAgICAjIFJvdXRlIGRlZmluaXRpb25zCuKUnOKUgOKUgCBmZWF0dXJlcy8gICAgICAgICAgICAjIEZlYXR1cmUgbW9kdWxlcwrilIIgICDilJTilIDilIAgYXV0aC8gICAgICAgICAgICMgQXV0aGVudGljYXRpb24K4pSc4pSA4pSAIHNoYXJlZC9mcmFnbWVudHMvICAgICMgUmV1c2FibGUgVUkgY29tcG9uZW50cwrilJTilIDilIAgdXRpbHMvICAgICAgICAgICAgICAjIFV0aWxpdHkgZnVuY3Rpb25zCmBgYAoKIyMgT0F1dGggU2V0dXAKCjEuIFJlZ2lzdGVyIHlvdXIgYXBwbGljYXRpb24gYXQKICAgW1NsaWNlcyBEZXZlbG9wZXIgUG9ydGFsXShodHRwczovL3NsaWNlcy5uZXR3b3JrKQoyLiBBZGQgeW91ciBPQXV0aCBjcmVkZW50aWFscyB0byBgLmVudmAKMy4gQ29uZmlndXJlIHJlZGlyZWN0IFVSSTogYGh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9vYXV0aC9jYWxsYmFja2AKCiMjIERvY3VtZW50YXRpb24KCi0gYENMQVVERS5tZGAgLSBBcmNoaXRlY3R1cmUgZ3VpZGUgZm9yIEFJIGFzc2lzdGFuY2UKLSBGZWF0dXJlIGRpcmVjdG9yaWVzIGNvbnRhaW4gaGFuZGxlcnMgYW5kIHRlbXBsYXRlcwotIENvbXBvbmVudHMgdXNlIFByZWFjdCB3aXRoIHNlcnZlci1zaWRlIHJlbmRlcmluZwotIEhUTVggcHJvdmlkZXMgaW50ZXJhY3RpdmUgYmVoYXZpb3Igd2l0aG91dCBwYWdlIHJlbG9hZHMKCiMjIExpY2Vuc2UKCk1JVAo=" 16 + "content": "IyB7e1BST0pFQ1RfTkFNRX19CgpBIERlbm8gU1NSIHdlYiBhcHBsaWNhdGlvbiB3aXRoIEFUIFByb3RvY29sIGludGVncmF0aW9uLCBidWlsdCB3aXRoIFByZWFjdCwKSFRNWCwgYW5kIE9BdXRoIGF1dGhlbnRpY2F0aW9uLgoKIyMgUXVpY2sgU3RhcnQKCmBgYGJhc2gKIyBTdGFydCB0aGUgZGV2ZWxvcG1lbnQgc2VydmVyCmRlbm8gdGFzayBkZXYKYGBgCgpWaXNpdCB5b3VyIGFwcCBhdCBodHRwOi8vbG9jYWxob3N0OjgwODAKCj4gKipOb3RlOioqIFlvdXIgc2xpY2UgYW5kIE9BdXRoIGNyZWRlbnRpYWxzIHdlcmUgYXV0b21hdGljYWxseSBjb25maWd1cmVkCj4gZHVyaW5nIHByb2plY3QgY3JlYXRpb24uIFRoZSBgLmVudmAgZmlsZSBpcyBhbHJlYWR5IHNldCB1cCB3aXRoIHlvdXIKPiBjcmVkZW50aWFscy4KCiMjIEZlYXR1cmVzCgotIPCflJAgKipPQXV0aCBBdXRoZW50aWNhdGlvbioqIHdpdGggUEtDRSBmbG93Ci0g4pqhICoqU2VydmVyLVNpZGUgUmVuZGVyaW5nKiogd2l0aCBQcmVhY3QKLSDwn46vICoqSW50ZXJhY3RpdmUgVUkqKiB3aXRoIEhUTVgKLSDwn46oICoqU3R5bGluZyoqIHdpdGggVGFpbHdpbmQgQ1NTCi0g8J+XhO+4jyAqKlNlc3Npb24gTWFuYWdlbWVudCoqIHdpdGggU1FMaXRlCi0g8J+UhCAqKkF1dG8gVG9rZW4gUmVmcmVzaCoqCi0g8J+Pl++4jyAqKkZlYXR1cmUtQmFzZWQgQXJjaGl0ZWN0dXJlKioKCiMjIERldmVsb3BtZW50CgpgYGBiYXNoCiMgU3RhcnQgZGV2ZWxvcG1lbnQgc2VydmVyIHdpdGggaG90IHJlbG9hZApkZW5vIHRhc2sgZGV2CgojIFN0YXJ0IHByb2R1Y3Rpb24gc2VydmVyCmRlbm8gdGFzayBzdGFydAoKIyBGb3JtYXQgY29kZQpkZW5vIGZtdAoKIyBDaGVjayB0eXBlcwpkZW5vIGNoZWNrIHNyYy8qKi8qLnRzIHNyYy8qKi8qLnRzeApgYGAKCiMjIFByb2plY3QgU3RydWN0dXJlCgpgYGAKc2xpY2VzLmpzb24gICAgICAgICAgICAgICMgU2xpY2VzIGNvbmZpZ3VyYXRpb24gZmlsZQpsZXhpY29ucy8gICAgICAgICAgICAgICAgIyBBVCBQcm90b2NvbCBsZXhpY29uIGRlZmluaXRpb25zCnNyYy8K4pSc4pSA4pSAIG1haW4udHMgICAgICAgICAgICAgICMgU2VydmVyIGVudHJ5IHBvaW50CuKUnOKUgOKUgCBjb25maWcudHMgICAgICAgICAgICAjIE9BdXRoICYgc2Vzc2lvbiBjb25maWd1cmF0aW9uCuKUnOKUgOKUgCBnZW5lcmF0ZWRfY2xpZW50LnRzICAjIEdlbmVyYXRlZCBUeXBlU2NyaXB0IGNsaWVudCBmcm9tIGxleGljb25zCuKUnOKUgOKUgCByb3V0ZXMvICAgICAgICAgICAgICAjIFJvdXRlIGRlZmluaXRpb25zCuKUnOKUgOKUgCBmZWF0dXJlcy8gICAgICAgICAgICAjIEZlYXR1cmUgbW9kdWxlcwrilIIgICDilJTilIDilIAgYXV0aC8gICAgICAgICAgICMgQXV0aGVudGljYXRpb24K4pSc4pSA4pSAIHNoYXJlZC9mcmFnbWVudHMvICAgICMgUmV1c2FibGUgVUkgY29tcG9uZW50cwrilJTilIDilIAgdXRpbHMvICAgICAgICAgICAgICAjIFV0aWxpdHkgZnVuY3Rpb25zCmBgYAoKIyMgT0F1dGggU2V0dXAKCllvdXIgT0F1dGggYXBwbGljYXRpb24gd2FzIGF1dG9tYXRpY2FsbHkgY3JlYXRlZCBkdXJpbmcgcHJvamVjdCBpbml0aWFsaXphdGlvbgp3aXRoOgoKLSAqKkNsaWVudCBJRCAmIFNlY3JldCoqOiBBbHJlYWR5IGNvbmZpZ3VyZWQgaW4gYC5lbnZgCi0gKipSZWRpcmVjdCBVUkkqKjogYGh0dHA6Ly9sb2NhbGhvc3Q6ODA4MC9vYXV0aC9jYWxsYmFja2AKLSAqKlNsaWNlKio6IEF1dG9tYXRpY2FsbHkgY3JlYXRlZCBhbmQgbGlua2VkCgpUbyBtYW5hZ2UgeW91ciBPQXV0aCBjbGllbnRzIG9yIGNyZWF0ZSBhZGRpdGlvbmFsIG9uZXM6CgoxLiBWaXNpdCBbU2xpY2VzIE5ldHdvcmtdKGh0dHBzOi8vc2xpY2VzLm5ldHdvcmspCjIuIFVzZSB0aGUgYHNsaWNlcyBsb2dpbmAgQ0xJIGNvbW1hbmQKCiMjIERvY3VtZW50YXRpb24KCi0gYENMQVVERS5tZGAgLSBBcmNoaXRlY3R1cmUgZ3VpZGUgZm9yIEFJIGFzc2lzdGFuY2UKLSBGZWF0dXJlIGRpcmVjdG9yaWVzIGNvbnRhaW4gaGFuZGxlcnMgYW5kIHRlbXBsYXRlcwotIENvbXBvbmVudHMgdXNlIFByZWFjdCB3aXRoIHNlcnZlci1zaWRlIHJlbmRlcmluZwotIEhUTVggcHJvdmlkZXMgaW50ZXJhY3RpdmUgYmVoYXZpb3Igd2l0aG91dCBwYWdlIHJlbG9hZHMKCiMjIExpY2Vuc2UKCk1JVAo=" 17 17 }, 18 18 { 19 19 "path": ".gitignore", 20 - "content": "IyBFbnZpcm9ubWVudCB2YXJpYWJsZXMKLmVudgoKIyBEYXRhYmFzZQoqLmRiCiouZGItam91cm5hbAoqLmRiLXdhbAoqLmRiLXNobQoKIyBEZW5vCmRlbm8ubG9jawoKIyBPUyBnZW5lcmF0ZWQgZmlsZXMKLkRTX1N0b3JlClRodW1icy5kYgoKIyBJREUKLnZzY29kZS8KLmlkZWEvCiouc3dwCiouc3dvCgojIExvZ3MKKi5sb2cKbG9ncy8KCiMgQ292ZXJhZ2UKY292ZXJhZ2Uv" 20 + "content": "LmVudioKbm9kZV9tb2R1bGVzCiouZGIqCg==" 21 21 }, 22 22 { 23 23 "path": ".env.example", 24 - "content": "IyBPQXV0aCBDb25maWd1cmF0aW9uIChyZXF1aXJlZCkKT0FVVEhfQ0xJRU5UX0lEPXlvdXJfb2F1dGhfY2xpZW50X2lkCk9BVVRIX0NMSUVOVF9TRUNSRVQ9eW91cl9vYXV0aF9jbGllbnRfc2VjcmV0Ck9BVVRIX1JFRElSRUNUX1VSST1odHRwOi8vbG9jYWxob3N0OjgwODAvb2F1dGgvY2FsbGJhY2sKT0FVVEhfQUlQX0JBU0VfVVJMPWh0dHBzOi8vYXV0aC5zbGljZXMubmV0d29yawoKIyBBUEkgQ29uZmlndXJhdGlvbiAocmVxdWlyZWQpCkFQSV9VUkw9aHR0cHM6Ly9hcGkuc2xpY2VzLm5ldHdvcmsKU0xJQ0VfVVJJPWF0Oi8vZGlkOnBsYzpleGFtcGxlL25ldHdvcmsuc2xpY2VzLnNsaWNlL2V4YW1wbGUKCiMgRGF0YWJhc2UgKG9wdGlvbmFsLCBkZWZhdWx0cyB0byBzbGljZXMuZGIpCkRBVEFCQVNFX1VSTD1zbGljZXMuZGIKCiMgRW52aXJvbm1lbnQgKG9wdGlvbmFsLCBhZmZlY3RzIGNvb2tpZSBzZWN1cml0eSkKREVOT19FTlY9ZGV2ZWxvcG1lbnQKCiMgU2VydmVyIChvcHRpb25hbCwgZGVmYXVsdHMgdG8gODA4MCkKUE9SVD04MDgw" 24 + "content": "IyBPQXV0aCBDb25maWd1cmF0aW9uIChyZXF1aXJlZCkKT0FVVEhfQ0xJRU5UX0lEPXlvdXJfb2F1dGhfY2xpZW50X2lkCk9BVVRIX0NMSUVOVF9TRUNSRVQ9eW91cl9vYXV0aF9jbGllbnRfc2VjcmV0Ck9BVVRIX1JFRElSRUNUX1VSST1odHRwOi8vbG9jYWxob3N0OjgwODAvb2F1dGgvY2FsbGJhY2sKT0FVVEhfQUlQX0JBU0VfVVJMPWh0dHBzOi8vYXV0aC5zbGljZXMubmV0d29yawoKIyBBUEkgQ29uZmlndXJhdGlvbiAocmVxdWlyZWQpCkFQSV9VUkw9aHR0cHM6Ly9hcGkuc2xpY2VzLm5ldHdvcmsKU0xJQ0VfVVJJPWF0Oi8vZGlkOnBsYzpiY2dsdHpxYXp3NXRiNmsyZzN0dGVuYmovbmV0d29yay5zbGljZXMuc2xpY2UvM2x6Ynp1bWNtdm8yegoKIyBEYXRhYmFzZSAob3B0aW9uYWwsIGRlZmF1bHRzIHRvIHNsaWNlcy5kYikKREFUQUJBU0VfVVJMPXNsaWNlcy5kYgoKIyBFbnZpcm9ubWVudCAob3B0aW9uYWwsIGFmZmVjdHMgY29va2llIHNlY3VyaXR5KQpERU5PX0VOVj1kZXZlbG9wbWVudAoKIyBTZXJ2ZXIgKG9wdGlvbmFsLCBkZWZhdWx0cyB0byA4MDgwKQpQT1JUPTgwODA=" 25 25 }, 26 26 { 27 27 "path": "CLAUDE.md", ··· 37 37 }, 38 38 { 39 39 "path": "src/features/auth/handlers.tsx", 40 - "content": "aW1wb3J0IHR5cGUgeyBSb3V0ZSB9IGZyb20gIkBzdGQvaHR0cC91bnN0YWJsZS1yb3V0ZSI7CmltcG9ydCB7IHdpdGhBdXRoIH0gZnJvbSAiLi4vLi4vcm91dGVzL21pZGRsZXdhcmUudHMiOwppbXBvcnQgeyBvYXV0aENsaWVudCwgb2F1dGhTZXNzaW9ucywgc2Vzc2lvblN0b3JlIH0gZnJvbSAiLi4vLi4vY29uZmlnLnRzIjsKaW1wb3J0IHsgcmVuZGVySFRNTCB9IGZyb20gIi4uLy4uL3V0aWxzL3JlbmRlci50c3giOwppbXBvcnQgeyBMb2dpblBhZ2UgfSBmcm9tICIuL3RlbXBsYXRlcy9Mb2dpblBhZ2UudHN4IjsKCmFzeW5jIGZ1bmN0aW9uIGhhbmRsZUxvZ2luUGFnZShyZXE6IFJlcXVlc3QpOiBQcm9taXNlPFJlc3BvbnNlPiB7CiAgY29uc3QgY29udGV4dCA9IGF3YWl0IHdpdGhBdXRoKHJlcSk7CiAgY29uc3QgdXJsID0gbmV3IFVSTChyZXEudXJsKTsKCiAgLy8gUmVkaXJlY3QgaWYgYWxyZWFkeSBsb2dnZWQgaW4KICBpZiAoY29udGV4dC5jdXJyZW50VXNlcikgewogICAgcmV0dXJuIFJlc3BvbnNlLnJlZGlyZWN0KG5ldyBVUkwoIi9kYXNoYm9hcmQiLCByZXEudXJsKSwgMzAyKTsKICB9CgogIGNvbnN0IGVycm9yID0gdXJsLnNlYXJjaFBhcmFtcy5nZXQoImVycm9yIik7CiAgcmV0dXJuIHJlbmRlckhUTUwoCiAgICA8TG9naW5QYWdlIGVycm9yPXtlcnJvciB8fCB1bmRlZmluZWR9IC8+CiAgKTsKfQoKYXN5bmMgZnVuY3Rpb24gaGFuZGxlT0F1dGhBdXRob3JpemUocmVxOiBSZXF1ZXN0KTogUHJvbWlzZTxSZXNwb25zZT4gewogIHRyeSB7CiAgICBjb25zdCBmb3JtRGF0YSA9IGF3YWl0IHJlcS5mb3JtRGF0YSgpOwogICAgY29uc3QgbG9naW5IaW50ID0gZm9ybURhdGEuZ2V0KCJsb2dpbkhpbnQiKSBhcyBzdHJpbmc7CgogICAgaWYgKCFsb2dpbkhpbnQpIHsKICAgICAgcmV0dXJuIG5ldyBSZXNwb25zZSgiTWlzc2luZyBsb2dpbiBoaW50IiwgeyBzdGF0dXM6IDQwMCB9KTsKICAgIH0KCiAgICBjb25zdCBhdXRoUmVzdWx0ID0gYXdhaXQgb2F1dGhDbGllbnQuYXV0aG9yaXplKHsKICAgICAgbG9naW5IaW50LAogICAgfSk7CgogICAgcmV0dXJuIFJlc3BvbnNlLnJlZGlyZWN0KGF1dGhSZXN1bHQuYXV0aG9yaXphdGlvblVybCwgMzAyKTsKICB9IGNhdGNoIChlcnJvcikgewogICAgY29uc29sZS5lcnJvcigiT0F1dGggYXV0aG9yaXplIGVycm9yOiIsIGVycm9yKTsKICAgIHJldHVybiBSZXNwb25zZS5yZWRpcmVjdCgKICAgICAgbmV3IFVSTCgKICAgICAgICAiL2xvZ2luP2Vycm9yPSIgKyBlbmNvZGVVUklDb21wb25lbnQoIk9BdXRoIGluaXRpYWxpemF0aW9uIGZhaWxlZCIpLAogICAgICAgIHJlcS51cmwKICAgICAgKSwKICAgICAgMzAyCiAgICApOwogIH0KfQoKYXN5bmMgZnVuY3Rpb24gaGFuZGxlT0F1dGhDYWxsYmFjayhyZXE6IFJlcXVlc3QpOiBQcm9taXNlPFJlc3BvbnNlPiB7CiAgdHJ5IHsKICAgIGNvbnN0IHVybCA9IG5ldyBVUkwocmVxLnVybCk7CiAgICBjb25zdCBjb2RlID0gdXJsLnNlYXJjaFBhcmFtcy5nZXQoImNvZGUiKTsKICAgIGNvbnN0IHN0YXRlID0gdXJsLnNlYXJjaFBhcmFtcy5nZXQoInN0YXRlIik7CgogICAgaWYgKCFjb2RlIHx8ICFzdGF0ZSkgewogICAgICByZXR1cm4gUmVzcG9uc2UucmVkaXJlY3QoCiAgICAgICAgbmV3IFVSTCgKICAgICAgICAgICIvbG9naW4/ZXJyb3I9IiArIGVuY29kZVVSSUNvbXBvbmVudCgiSW52YWxpZCBPQXV0aCBjYWxsYmFjayIpLAogICAgICAgICAgcmVxLnVybAogICAgICAgICksCiAgICAgICAgMzAyCiAgICAgICk7CiAgICB9CgogICAgYXdhaXQgb2F1dGhDbGllbnQuaGFuZGxlQ2FsbGJhY2soeyBjb2RlLCBzdGF0ZSB9KTsKICAgIGNvbnN0IHNlc3Npb25JZCA9IGF3YWl0IG9hdXRoU2Vzc2lvbnMuY3JlYXRlT0F1dGhTZXNzaW9uKCk7CgogICAgaWYgKCFzZXNzaW9uSWQpIHsKICAgICAgcmV0dXJuIFJlc3BvbnNlLnJlZGlyZWN0KAogICAgICAgIG5ldyBVUkwoCiAgICAgICAgICAiL2xvZ2luP2Vycm9yPSIgKyBlbmNvZGVVUklDb21wb25lbnQoIkZhaWxlZCB0byBjcmVhdGUgc2Vzc2lvbiIpLAogICAgICAgICAgcmVxLnVybAogICAgICAgICksCiAgICAgICAgMzAyCiAgICAgICk7CiAgICB9CgogICAgY29uc3Qgc2Vzc2lvbkNvb2tpZSA9IHNlc3Npb25TdG9yZS5jcmVhdGVTZXNzaW9uQ29va2llKHNlc3Npb25JZCk7CgogICAgcmV0dXJuIG5ldyBSZXNwb25zZShudWxsLCB7CiAgICAgIHN0YXR1czogMzAyLAogICAgICBoZWFkZXJzOiB7CiAgICAgICAgTG9jYXRpb246IG5ldyBVUkwoIi9kYXNoYm9hcmQiLCByZXEudXJsKS50b1N0cmluZygpLAogICAgICAgICJTZXQtQ29va2llIjogc2Vzc2lvbkNvb2tpZSwKICAgICAgfSwKICAgIH0pOwogIH0gY2F0Y2ggKGVycm9yKSB7CiAgICBjb25zb2xlLmVycm9yKCJPQXV0aCBjYWxsYmFjayBlcnJvcjoiLCBlcnJvcik7CiAgICByZXR1cm4gUmVzcG9uc2UucmVkaXJlY3QoCiAgICAgIG5ldyBVUkwoCiAgICAgICAgIi9sb2dpbj9lcnJvcj0iICsgZW5jb2RlVVJJQ29tcG9uZW50KCJBdXRoZW50aWNhdGlvbiBmYWlsZWQiKSwKICAgICAgICByZXEudXJsCiAgICAgICksCiAgICAgIDMwMgogICAgKTsKICB9Cn0KCmFzeW5jIGZ1bmN0aW9uIGhhbmRsZUxvZ291dChyZXE6IFJlcXVlc3QpOiBQcm9taXNlPFJlc3BvbnNlPiB7CiAgY29uc3Qgc2Vzc2lvbiA9IGF3YWl0IHNlc3Npb25TdG9yZS5nZXRTZXNzaW9uRnJvbVJlcXVlc3QocmVxKTsKCiAgaWYgKHNlc3Npb24pIHsKICAgIGF3YWl0IG9hdXRoU2Vzc2lvbnMubG9nb3V0KHNlc3Npb24uc2Vzc2lvbklkKTsKICB9CgogIGNvbnN0IGNsZWFyQ29va2llID0gc2Vzc2lvblN0b3JlLmNyZWF0ZUxvZ291dENvb2tpZSgpOwoKICByZXR1cm4gbmV3IFJlc3BvbnNlKG51bGwsIHsKICAgIHN0YXR1czogMzAyLAogICAgaGVhZGVyczogewogICAgICBMb2NhdGlvbjogbmV3IFVSTCgiL2xvZ2luIiwgcmVxLnVybCkudG9TdHJpbmcoKSwKICAgICAgIlNldC1Db29raWUiOiBjbGVhckNvb2tpZSwKICAgIH0sCiAgfSk7Cn0KCmFzeW5jIGZ1bmN0aW9uIGhhbmRsZURhc2hib2FyZChyZXE6IFJlcXVlc3QpOiBQcm9taXNlPFJlc3BvbnNlPiB7CiAgY29uc3QgY29udGV4dCA9IGF3YWl0IHdpdGhBdXRoKHJlcSk7CgogIGlmICghY29udGV4dC5jdXJyZW50VXNlcikgewogICAgcmV0dXJuIFJlc3BvbnNlLnJlZGlyZWN0KG5ldyBVUkwoIi9sb2dpbiIsIHJlcS51cmwpLCAzMDIpOwogIH0KCiAgcmV0dXJuIHJlbmRlckhUTUwoCiAgICA8ZGl2PgogICAgICA8aDE+RGFzaGJvYXJkPC9oMT4KICAgICAgPHA+V2VsY29tZSwge2NvbnRleHQuY3VycmVudFVzZXIubmFtZSB8fCBjb250ZXh0LmN1cnJlbnRVc2VyLnN1Yn0hPC9wPgogICAgICA8Zm9ybSBtZXRob2Q9InBvc3QiIGFjdGlvbj0iL2xvZ291dCI+CiAgICAgICAgPGJ1dHRvbiB0eXBlPSJzdWJtaXQiPkxvZ291dDwvYnV0dG9uPgogICAgICA8L2Zvcm0+CiAgICA8L2Rpdj4KICApOwp9CgpleHBvcnQgY29uc3QgYXV0aFJvdXRlczogUm91dGVbXSA9IFsKICB7CiAgICBtZXRob2Q6ICJHRVQiLAogICAgcGF0dGVybjogbmV3IFVSTFBhdHRlcm4oeyBwYXRobmFtZTogIi9sb2dpbiIgfSksCiAgICBoYW5kbGVyOiBoYW5kbGVMb2dpblBhZ2UsCiAgfSwKICB7CiAgICBtZXRob2Q6ICJQT1NUIiwKICAgIHBhdHRlcm46IG5ldyBVUkxQYXR0ZXJuKHsgcGF0aG5hbWU6ICIvb2F1dGgvYXV0aG9yaXplIiB9KSwKICAgIGhhbmRsZXI6IGhhbmRsZU9BdXRoQXV0aG9yaXplLAogIH0sCiAgewogICAgbWV0aG9kOiAiR0VUIiwKICAgIHBhdHRlcm46IG5ldyBVUkxQYXR0ZXJuKHsgcGF0aG5hbWU6ICIvb2F1dGgvY2FsbGJhY2siIH0pLAogICAgaGFuZGxlcjogaGFuZGxlT0F1dGhDYWxsYmFjaywKICB9LAogIHsKICAgIG1ldGhvZDogIlBPU1QiLAogICAgcGF0dGVybjogbmV3IFVSTFBhdHRlcm4oeyBwYXRobmFtZTogIi9sb2dvdXQiIH0pLAogICAgaGFuZGxlcjogaGFuZGxlTG9nb3V0LAogIH0sCiAgewogICAgbWV0aG9kOiAiR0VUIiwKICAgIHBhdHRlcm46IG5ldyBVUkxQYXR0ZXJuKHsgcGF0aG5hbWU6ICIvZGFzaGJvYXJkIiB9KSwKICAgIGhhbmRsZXI6IGhhbmRsZURhc2hib2FyZCwKICB9LApdOw==" 40 + "content": "aW1wb3J0IHR5cGUgeyBSb3V0ZSB9IGZyb20gIkBzdGQvaHR0cC91bnN0YWJsZS1yb3V0ZSI7CmltcG9ydCB7IHdpdGhBdXRoIH0gZnJvbSAiLi4vLi4vcm91dGVzL21pZGRsZXdhcmUudHMiOwppbXBvcnQgeyBPQXV0aENsaWVudCB9IGZyb20gIkBzbGljZXMvb2F1dGgiOwppbXBvcnQgewogIGNyZWF0ZU9BdXRoQ2xpZW50LAogIGNyZWF0ZVNlc3Npb25DbGllbnQsCiAgb2F1dGhDb25maWcsCiAgb2F1dGhTdG9yYWdlLAogIG9hdXRoU2Vzc2lvbnMsCiAgc2Vzc2lvblN0b3JlLAp9IGZyb20gIi4uLy4uL2NvbmZpZy50cyI7CmltcG9ydCB7IHJlbmRlckhUTUwgfSBmcm9tICIuLi8uLi91dGlscy9yZW5kZXIudHN4IjsKaW1wb3J0IHsgTG9naW5QYWdlIH0gZnJvbSAiLi90ZW1wbGF0ZXMvTG9naW5QYWdlLnRzeCI7Cgphc3luYyBmdW5jdGlvbiBoYW5kbGVMb2dpblBhZ2UocmVxOiBSZXF1ZXN0KTogUHJvbWlzZTxSZXNwb25zZT4gewogIGNvbnN0IGNvbnRleHQgPSBhd2FpdCB3aXRoQXV0aChyZXEpOwogIGNvbnN0IHVybCA9IG5ldyBVUkwocmVxLnVybCk7CgogIC8vIFJlZGlyZWN0IGlmIGFscmVhZHkgbG9nZ2VkIGluCiAgaWYgKGNvbnRleHQuY3VycmVudFVzZXIpIHsKICAgIHJldHVybiBSZXNwb25zZS5yZWRpcmVjdChuZXcgVVJMKCIvZGFzaGJvYXJkIiwgcmVxLnVybCksIDMwMik7CiAgfQoKICBjb25zdCBlcnJvciA9IHVybC5zZWFyY2hQYXJhbXMuZ2V0KCJlcnJvciIpOwogIHJldHVybiByZW5kZXJIVE1MKDxMb2dpblBhZ2UgZXJyb3I9e2Vycm9yIHx8IHVuZGVmaW5lZH0gLz4pOwp9Cgphc3luYyBmdW5jdGlvbiBoYW5kbGVPQXV0aEF1dGhvcml6ZShyZXE6IFJlcXVlc3QpOiBQcm9taXNlPFJlc3BvbnNlPiB7CiAgdHJ5IHsKICAgIGNvbnN0IGZvcm1EYXRhID0gYXdhaXQgcmVxLmZvcm1EYXRhKCk7CiAgICBjb25zdCBsb2dpbkhpbnQgPSBmb3JtRGF0YS5nZXQoImxvZ2luSGludCIpIGFzIHN0cmluZzsKCiAgICBpZiAoIWxvZ2luSGludCkgewogICAgICByZXR1cm4gbmV3IFJlc3BvbnNlKCJNaXNzaW5nIGxvZ2luIGhpbnQiLCB7IHN0YXR1czogNDAwIH0pOwogICAgfQoKICAgIGNvbnN0IHRlbXBPQXV0aENsaWVudCA9IG5ldyBPQXV0aENsaWVudCgKICAgICAgb2F1dGhDb25maWcsCiAgICAgIG9hdXRoU3RvcmFnZSwKICAgICAgbG9naW5IaW50CiAgICApOwogICAgY29uc3QgYXV0aFJlc3VsdCA9IGF3YWl0IHRlbXBPQXV0aENsaWVudC5hdXRob3JpemUoewogICAgICBsb2dpbkhpbnQsCiAgICB9KTsKCiAgICByZXR1cm4gUmVzcG9uc2UucmVkaXJlY3QoYXV0aFJlc3VsdC5hdXRob3JpemF0aW9uVXJsLCAzMDIpOwogIH0gY2F0Y2ggKGVycm9yKSB7CiAgICBjb25zb2xlLmVycm9yKCJPQXV0aCBhdXRob3JpemUgZXJyb3I6IiwgZXJyb3IpOwoKICAgIHJldHVybiBSZXNwb25zZS5yZWRpcmVjdCgKICAgICAgbmV3IFVSTCgKICAgICAgICAiL2xvZ2luP2Vycm9yPSIgKwogICAgICAgICAgZW5jb2RlVVJJQ29tcG9uZW50KCJQbGVhc2UgY2hlY2sgeW91ciBoYW5kbGUgYW5kIHRyeSBhZ2Fpbi4iKSwKICAgICAgICByZXEudXJsCiAgICAgICksCiAgICAgIDMwMgogICAgKTsKICB9Cn0KCmFzeW5jIGZ1bmN0aW9uIGhhbmRsZU9BdXRoQ2FsbGJhY2socmVxOiBSZXF1ZXN0KTogUHJvbWlzZTxSZXNwb25zZT4gewogIHRyeSB7CiAgICBjb25zdCB1cmwgPSBuZXcgVVJMKHJlcS51cmwpOwogICAgY29uc3QgY29kZSA9IHVybC5zZWFyY2hQYXJhbXMuZ2V0KCJjb2RlIik7CiAgICBjb25zdCBzdGF0ZSA9IHVybC5zZWFyY2hQYXJhbXMuZ2V0KCJzdGF0ZSIpOwoKICAgIGlmICghY29kZSB8fCAhc3RhdGUpIHsKICAgICAgcmV0dXJuIFJlc3BvbnNlLnJlZGlyZWN0KAogICAgICAgIG5ldyBVUkwoCiAgICAgICAgICAiL2xvZ2luP2Vycm9yPSIgKyBlbmNvZGVVUklDb21wb25lbnQoIkludmFsaWQgT0F1dGggY2FsbGJhY2siKSwKICAgICAgICAgIHJlcS51cmwKICAgICAgICApLAogICAgICAgIDMwMgogICAgICApOwogICAgfQoKICAgIGNvbnN0IHRlbXBPQXV0aENsaWVudCA9IG5ldyBPQXV0aENsaWVudChvYXV0aENvbmZpZywgb2F1dGhTdG9yYWdlLCAidGVtcCIpOwogICAgY29uc3QgdG9rZW5zID0gYXdhaXQgdGVtcE9BdXRoQ2xpZW50LmhhbmRsZUNhbGxiYWNrKHsgY29kZSwgc3RhdGUgfSk7CiAgICBjb25zdCBzZXNzaW9uSWQgPSBhd2FpdCBvYXV0aFNlc3Npb25zLmNyZWF0ZU9BdXRoU2Vzc2lvbih0b2tlbnMpOwoKICAgIGlmICghc2Vzc2lvbklkKSB7CiAgICAgIHJldHVybiBSZXNwb25zZS5yZWRpcmVjdCgKICAgICAgICBuZXcgVVJMKAogICAgICAgICAgIi9sb2dpbj9lcnJvcj0iICsgZW5jb2RlVVJJQ29tcG9uZW50KCJGYWlsZWQgdG8gY3JlYXRlIHNlc3Npb24iKSwKICAgICAgICAgIHJlcS51cmwKICAgICAgICApLAogICAgICAgIDMwMgogICAgICApOwogICAgfQoKICAgIGNvbnN0IHNlc3Npb25Db29raWUgPSBzZXNzaW9uU3RvcmUuY3JlYXRlU2Vzc2lvbkNvb2tpZShzZXNzaW9uSWQpOwoKICAgIGxldCB1c2VySW5mbzsKICAgIHRyeSB7CiAgICAgIGNvbnN0IHNlc3Npb25PQXV0aENsaWVudCA9IGNyZWF0ZU9BdXRoQ2xpZW50KHNlc3Npb25JZCk7CiAgICAgIHVzZXJJbmZvID0gYXdhaXQgc2Vzc2lvbk9BdXRoQ2xpZW50LmdldFVzZXJJbmZvKCk7CiAgICB9IGNhdGNoIChlcnJvcikgewogICAgICBjb25zb2xlLmVycm9yKCJGYWlsZWQgdG8gZ2V0IHVzZXIgaW5mbzoiLCBlcnJvcik7CiAgICB9CgogICAgaWYgKHVzZXJJbmZvPy5zdWIpIHsKICAgICAgdHJ5IHsKICAgICAgICBjb25zdCB1c2VyQ2xpZW50ID0gY3JlYXRlU2Vzc2lvbkNsaWVudChzZXNzaW9uSWQpOwogICAgICAgIGF3YWl0IHVzZXJDbGllbnQuc3luY1VzZXJDb2xsZWN0aW9ucygpOwogICAgICAgIGNvbnNvbGUubG9nKCJTeW5jZWQgQmx1ZXNreSBwcm9maWxlIGZvciIsIHVzZXJJbmZvLnN1Yik7CiAgICAgIH0gY2F0Y2ggKGVycm9yKSB7CiAgICAgICAgY29uc29sZS5lcnJvcigiRXJyb3Igc3luY2luZyBCbHVlc2t5IHByb2ZpbGU6IiwgZXJyb3IpOwogICAgICB9CiAgICB9CgogICAgcmV0dXJuIG5ldyBSZXNwb25zZShudWxsLCB7CiAgICAgIHN0YXR1czogMzAyLAogICAgICBoZWFkZXJzOiB7CiAgICAgICAgTG9jYXRpb246IG5ldyBVUkwoIi9kYXNoYm9hcmQiLCByZXEudXJsKS50b1N0cmluZygpLAogICAgICAgICJTZXQtQ29va2llIjogc2Vzc2lvbkNvb2tpZSwKICAgICAgfSwKICAgIH0pOwogIH0gY2F0Y2ggKGVycm9yKSB7CiAgICBjb25zb2xlLmVycm9yKCJPQXV0aCBjYWxsYmFjayBlcnJvcjoiLCBlcnJvcik7CiAgICByZXR1cm4gUmVzcG9uc2UucmVkaXJlY3QoCiAgICAgIG5ldyBVUkwoCiAgICAgICAgIi9sb2dpbj9lcnJvcj0iICsgZW5jb2RlVVJJQ29tcG9uZW50KCJBdXRoZW50aWNhdGlvbiBmYWlsZWQiKSwKICAgICAgICByZXEudXJsCiAgICAgICksCiAgICAgIDMwMgogICAgKTsKICB9Cn0KCmFzeW5jIGZ1bmN0aW9uIGhhbmRsZUxvZ291dChyZXE6IFJlcXVlc3QpOiBQcm9taXNlPFJlc3BvbnNlPiB7CiAgY29uc3Qgc2Vzc2lvbiA9IGF3YWl0IHNlc3Npb25TdG9yZS5nZXRTZXNzaW9uRnJvbVJlcXVlc3QocmVxKTsKCiAgaWYgKHNlc3Npb24pIHsKICAgIGF3YWl0IG9hdXRoU2Vzc2lvbnMubG9nb3V0KHNlc3Npb24uc2Vzc2lvbklkKTsKICB9CgogIGNvbnN0IGNsZWFyQ29va2llID0gc2Vzc2lvblN0b3JlLmNyZWF0ZUxvZ291dENvb2tpZSgpOwoKICByZXR1cm4gbmV3IFJlc3BvbnNlKG51bGwsIHsKICAgIHN0YXR1czogMzAyLAogICAgaGVhZGVyczogewogICAgICBMb2NhdGlvbjogbmV3IFVSTCgiL2xvZ2luIiwgcmVxLnVybCkudG9TdHJpbmcoKSwKICAgICAgIlNldC1Db29raWUiOiBjbGVhckNvb2tpZSwKICAgIH0sCiAgfSk7Cn0KCmV4cG9ydCBjb25zdCBhdXRoUm91dGVzOiBSb3V0ZVtdID0gWwogIHsKICAgIG1ldGhvZDogIkdFVCIsCiAgICBwYXR0ZXJuOiBuZXcgVVJMUGF0dGVybih7IHBhdGhuYW1lOiAiL2xvZ2luIiB9KSwKICAgIGhhbmRsZXI6IGhhbmRsZUxvZ2luUGFnZSwKICB9LAogIHsKICAgIG1ldGhvZDogIlBPU1QiLAogICAgcGF0dGVybjogbmV3IFVSTFBhdHRlcm4oeyBwYXRobmFtZTogIi9vYXV0aC9hdXRob3JpemUiIH0pLAogICAgaGFuZGxlcjogaGFuZGxlT0F1dGhBdXRob3JpemUsCiAgfSwKICB7CiAgICBtZXRob2Q6ICJHRVQiLAogICAgcGF0dGVybjogbmV3IFVSTFBhdHRlcm4oeyBwYXRobmFtZTogIi9vYXV0aC9jYWxsYmFjayIgfSksCiAgICBoYW5kbGVyOiBoYW5kbGVPQXV0aENhbGxiYWNrLAogIH0sCiAgewogICAgbWV0aG9kOiAiUE9TVCIsCiAgICBwYXR0ZXJuOiBuZXcgVVJMUGF0dGVybih7IHBhdGhuYW1lOiAiL2xvZ291dCIgfSksCiAgICBoYW5kbGVyOiBoYW5kbGVMb2dvdXQsCiAgfSwKXTsK" 41 + }, 42 + { 43 + "path": "src/features/dashboard/templates/DashboardPage.tsx", 44 + "content": "aW1wb3J0IHsgTGF5b3V0IH0gZnJvbSAiLi4vLi4vLi4vc2hhcmVkL2ZyYWdtZW50cy9MYXlvdXQudHN4IjsKaW1wb3J0IHsgQnV0dG9uIH0gZnJvbSAiLi4vLi4vLi4vc2hhcmVkL2ZyYWdtZW50cy9CdXR0b24udHN4IjsKaW1wb3J0IHR5cGUgeyBBcHBCc2t5QWN0b3JQcm9maWxlIH0gZnJvbSAiLi4vLi4vLi4vZ2VuZXJhdGVkX2NsaWVudC50cyI7CgppbnRlcmZhY2UgRGFzaGJvYXJkUGFnZVByb3BzIHsKICBjdXJyZW50VXNlcjogewogICAgbmFtZT86IHN0cmluZzsKICAgIHN1Yjogc3RyaW5nOwogIH07CiAgcHJvZmlsZT86IEFwcEJza3lBY3RvclByb2ZpbGU7CiAgYXZhdGFyVXJsPzogc3RyaW5nOwp9CgpleHBvcnQgZnVuY3Rpb24gRGFzaGJvYXJkUGFnZSh7CiAgY3VycmVudFVzZXIsCiAgcHJvZmlsZSwKICBhdmF0YXJVcmwsCn06IERhc2hib2FyZFBhZ2VQcm9wcykgewogIHJldHVybiAoCiAgICA8TGF5b3V0IHRpdGxlPSJEYXNoYm9hcmQiPgogICAgICA8ZGl2IGNsYXNzTmFtZT0ibWluLWgtc2NyZWVuIGJnLWdyYXktNTAgcC04Ij4KICAgICAgICA8ZGl2IGNsYXNzTmFtZT0ibWF4LXctMnhsIG14LWF1dG8iPgogICAgICAgICAgPGRpdiBjbGFzc05hbWU9ImJnLXdoaXRlIHJvdW5kZWQtbGcgc2hhZG93IHAtNiI+CiAgICAgICAgICAgIDxkaXYgY2xhc3NOYW1lPSJmbGV4IGp1c3RpZnktYmV0d2VlbiBpdGVtcy1jZW50ZXIgbWItNiI+CiAgICAgICAgICAgICAgPGgxIGNsYXNzTmFtZT0idGV4dC0yeGwgZm9udC1ib2xkIj5EYXNoYm9hcmQ8L2gxPgogICAgICAgICAgICAgIDxmb3JtIG1ldGhvZD0icG9zdCIgYWN0aW9uPSIvbG9nb3V0Ij4KICAgICAgICAgICAgICAgIDxCdXR0b24gdHlwZT0ic3VibWl0IiB2YXJpYW50PSJzZWNvbmRhcnkiPgogICAgICAgICAgICAgICAgICBMb2dvdXQKICAgICAgICAgICAgICAgIDwvQnV0dG9uPgogICAgICAgICAgICAgIDwvZm9ybT4KICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICA8ZGl2IGNsYXNzTmFtZT0ibWItNiI+CiAgICAgICAgICAgICAge2F2YXRhclVybCAmJiAoCiAgICAgICAgICAgICAgICA8aW1nCiAgICAgICAgICAgICAgICAgIHNyYz17YXZhdGFyVXJsfQogICAgICAgICAgICAgICAgICBhbHQ9IlByb2ZpbGUiCiAgICAgICAgICAgICAgICAgIGNsYXNzTmFtZT0idy0yMCBoLTIwIHJvdW5kZWQtZnVsbCBtYi00IgogICAgICAgICAgICAgICAgLz4KICAgICAgICAgICAgICApfQogICAgICAgICAgICAgIDxoMiBjbGFzc05hbWU9InRleHQteGwgZm9udC1zZW1pYm9sZCBtYi0yIj4KICAgICAgICAgICAgICAgIHtwcm9maWxlPy5kaXNwbGF5TmFtZSB8fCBjdXJyZW50VXNlci5uYW1lIHx8IGN1cnJlbnRVc2VyLnN1Yn0KICAgICAgICAgICAgICA8L2gyPgogICAgICAgICAgICAgIHtjdXJyZW50VXNlci5uYW1lICYmICgKICAgICAgICAgICAgICAgIDxwIGNsYXNzTmFtZT0idGV4dC1ncmF5LTYwMCBtYi0yIj5Ae2N1cnJlbnRVc2VyLm5hbWV9PC9wPgogICAgICAgICAgICAgICl9CiAgICAgICAgICAgICAge3Byb2ZpbGU/LmRlc2NyaXB0aW9uICYmICgKICAgICAgICAgICAgICAgIDxwIGNsYXNzTmFtZT0idGV4dC1ncmF5LTcwMCBtdC0yIj57cHJvZmlsZS5kZXNjcmlwdGlvbn08L3A+CiAgICAgICAgICAgICAgKX0KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L2Rpdj4KICAgICAgPC9kaXY+CiAgICA8L0xheW91dD4KICApOwp9Cg==" 45 + }, 46 + { 47 + "path": "src/features/dashboard/handlers.tsx", 48 + "content": "aW1wb3J0IHR5cGUgeyBSb3V0ZSB9IGZyb20gIkBzdGQvaHR0cC91bnN0YWJsZS1yb3V0ZSI7CmltcG9ydCB7IHdpdGhBdXRoIH0gZnJvbSAiLi4vLi4vcm91dGVzL21pZGRsZXdhcmUudHMiOwppbXBvcnQgeyByZW5kZXJIVE1MIH0gZnJvbSAiLi4vLi4vdXRpbHMvcmVuZGVyLnRzeCI7CmltcG9ydCB7IERhc2hib2FyZFBhZ2UgfSBmcm9tICIuL3RlbXBsYXRlcy9EYXNoYm9hcmRQYWdlLnRzeCI7CmltcG9ydCB7IHB1YmxpY0NsaWVudCB9IGZyb20gIi4uLy4uL2NvbmZpZy50cyI7CmltcG9ydCB7IHJlY29yZEJsb2JUb0NkblVybCB9IGZyb20gIkBzbGljZXMvY2xpZW50IjsKaW1wb3J0IHsgQXBwQnNreUFjdG9yUHJvZmlsZSB9IGZyb20gIi4uLy4uL2dlbmVyYXRlZF9jbGllbnQudHMiOwoKYXN5bmMgZnVuY3Rpb24gaGFuZGxlRGFzaGJvYXJkKHJlcTogUmVxdWVzdCk6IFByb21pc2U8UmVzcG9uc2U+IHsKICBjb25zdCBjb250ZXh0ID0gYXdhaXQgd2l0aEF1dGgocmVxKTsKCiAgaWYgKCFjb250ZXh0LmN1cnJlbnRVc2VyKSB7CiAgICByZXR1cm4gUmVzcG9uc2UucmVkaXJlY3QobmV3IFVSTCgiL2xvZ2luIiwgcmVxLnVybCksIDMwMik7CiAgfQoKICBsZXQgcHJvZmlsZTogQXBwQnNreUFjdG9yUHJvZmlsZSB8IHVuZGVmaW5lZDsKICBsZXQgYXZhdGFyVXJsOiBzdHJpbmcgfCB1bmRlZmluZWQ7CiAgdHJ5IHsKICAgIGNvbnN0IHByb2ZpbGVSZXN1bHQgPSBhd2FpdCBwdWJsaWNDbGllbnQuYXBwLmJza3kuYWN0b3IucHJvZmlsZS5nZXRSZWNvcmQoewogICAgICB1cmk6IGBhdDovLyR7Y29udGV4dC5jdXJyZW50VXNlci5zdWJ9L2FwcC5ic2t5LmFjdG9yLnByb2ZpbGUvc2VsZmAsCiAgICB9KTsKCiAgICBpZiAocHJvZmlsZVJlc3VsdCkgewogICAgICBwcm9maWxlID0gcHJvZmlsZVJlc3VsdC52YWx1ZTsKCiAgICAgIGlmIChwcm9maWxlLmF2YXRhcikgewogICAgICAgIGF2YXRhclVybCA9IHJlY29yZEJsb2JUb0NkblVybChwcm9maWxlUmVzdWx0LCBwcm9maWxlLmF2YXRhciwgImF2YXRhciIpOwogICAgICB9CiAgICB9CiAgfSBjYXRjaCAoZXJyb3IpIHsKICAgIGNvbnNvbGUuZXJyb3IoIkVycm9yIGZldGNoaW5nIHByb2ZpbGU6IiwgZXJyb3IpOwogIH0KCiAgcmV0dXJuIHJlbmRlckhUTUwoCiAgICA8RGFzaGJvYXJkUGFnZQogICAgICBjdXJyZW50VXNlcj17Y29udGV4dC5jdXJyZW50VXNlcn0KICAgICAgcHJvZmlsZT17cHJvZmlsZX0KICAgICAgYXZhdGFyVXJsPXthdmF0YXJVcmx9CiAgICAvPgogICk7Cn0KCmV4cG9ydCBjb25zdCBkYXNoYm9hcmRSb3V0ZXM6IFJvdXRlW10gPSBbCiAgewogICAgbWV0aG9kOiAiR0VUIiwKICAgIHBhdHRlcm46IG5ldyBVUkxQYXR0ZXJuKHsgcGF0aG5hbWU6ICIvZGFzaGJvYXJkIiB9KSwKICAgIGhhbmRsZXI6IGhhbmRsZURhc2hib2FyZCwKICB9LApdOwo=" 49 + }, 50 + { 51 + "path": "src/utils/cn.ts", 52 + "content": "aW1wb3J0IHsgdHlwZSBDbGFzc1ZhbHVlLCBjbHN4IH0gZnJvbSAiY2xzeCI7CmltcG9ydCB7IHR3TWVyZ2UgfSBmcm9tICJ0YWlsd2luZC1tZXJnZSI7CgpleHBvcnQgZnVuY3Rpb24gY24oLi4uaW5wdXRzOiBDbGFzc1ZhbHVlW10pOiBzdHJpbmcgewogIHJldHVybiB0d01lcmdlKGNsc3goaW5wdXRzKSk7Cn0=" 41 53 }, 42 54 { 43 55 "path": "src/utils/logging.ts", 44 - "content": "ZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZUxvZ2dpbmdIYW5kbGVyKGhhbmRsZXI6IChyZXE6IFJlcXVlc3QpID0+IFJlc3BvbnNlIHwgUHJvbWlzZTxSZXNwb25zZT4pIHsKICByZXR1cm4gYXN5bmMgKHJlcTogUmVxdWVzdCk6IFByb21pc2U8UmVzcG9uc2U+ID0+IHsKICAgIGNvbnN0IHN0YXJ0ID0gRGF0ZS5ub3coKTsKICAgIGNvbnN0IG1ldGhvZCA9IHJlcS5tZXRob2Q7CiAgICBjb25zdCB1cmwgPSBuZXcgVVJMKHJlcS51cmwpOwoKICAgIHRyeSB7CiAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgUHJvbWlzZS5yZXNvbHZlKGhhbmRsZXIocmVxKSk7CiAgICAgIGNvbnN0IGR1cmF0aW9uID0gRGF0ZS5ub3coKSAtIHN0YXJ0OwogICAgICBjb25zb2xlLmxvZyhgJHttZXRob2R9ICR7dXJsLnBhdGhuYW1lfSAtICR7cmVzcG9uc2Uuc3RhdHVzfSAoJHtkdXJhdGlvbn1tcylgKTsKICAgICAgcmV0dXJuIHJlc3BvbnNlOwogICAgfSBjYXRjaCAoZXJyb3IpIHsKICAgICAgY29uc3QgZHVyYXRpb24gPSBEYXRlLm5vdygpIC0gc3RhcnQ7CiAgICAgIGNvbnNvbGUuZXJyb3IoYCR7bWV0aG9kfSAke3VybC5wYXRobmFtZX0gLSBFUlJPUiAoJHtkdXJhdGlvbn1tcyk6YCwgZXJyb3IpOwogICAgICB0aHJvdyBlcnJvcjsKICAgIH0KICB9Owp9" 56 + "content": "aW1wb3J0IHsgY3lhbiwgZ3JlZW4sIHJlZCwgeWVsbG93LCBib2xkLCBkaW0gfSBmcm9tICJAc3RkL2ZtdC9jb2xvcnMiOwoKZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZUxvZ2dpbmdIYW5kbGVyKAogIGhhbmRsZXI6IChyZXE6IFJlcXVlc3QpID0+IFJlc3BvbnNlIHwgUHJvbWlzZTxSZXNwb25zZT4KKSB7CiAgcmV0dXJuIGFzeW5jIChyZXE6IFJlcXVlc3QpOiBQcm9taXNlPFJlc3BvbnNlPiA9PiB7CiAgICBjb25zdCBzdGFydCA9IERhdGUubm93KCk7CiAgICBjb25zdCBtZXRob2QgPSByZXEubWV0aG9kOwogICAgY29uc3QgdXJsID0gbmV3IFVSTChyZXEudXJsKTsKCiAgICB0cnkgewogICAgICBjb25zdCByZXNwb25zZSA9IGF3YWl0IFByb21pc2UucmVzb2x2ZShoYW5kbGVyKHJlcSkpOwogICAgICBjb25zdCBkdXJhdGlvbiA9IERhdGUubm93KCkgLSBzdGFydDsKCiAgICAgIGNvbnN0IG1ldGhvZENvbG9yID0gY3lhbihib2xkKG1ldGhvZCkpOwogICAgICBjb25zdCBzdGF0dXNDb2xvciA9CiAgICAgICAgcmVzcG9uc2Uuc3RhdHVzID49IDIwMCAmJiByZXNwb25zZS5zdGF0dXMgPCAzMDAKICAgICAgICAgID8gZ3JlZW4oU3RyaW5nKHJlc3BvbnNlLnN0YXR1cykpCiAgICAgICAgICA6IHJlc3BvbnNlLnN0YXR1cyA+PSAzMDAgJiYgcmVzcG9uc2Uuc3RhdHVzIDwgNDAwCiAgICAgICAgICA/IHllbGxvdyhTdHJpbmcocmVzcG9uc2Uuc3RhdHVzKSkKICAgICAgICAgIDogcmVzcG9uc2Uuc3RhdHVzID49IDQwMAogICAgICAgICAgPyByZWQoU3RyaW5nKHJlc3BvbnNlLnN0YXR1cykpCiAgICAgICAgICA6IFN0cmluZyhyZXNwb25zZS5zdGF0dXMpOwogICAgICBjb25zdCBkdXJhdGlvblRleHQgPSBkaW0oYCgke2R1cmF0aW9ufW1zKWApOwoKICAgICAgY29uc29sZS5sb2coCiAgICAgICAgYCR7bWV0aG9kQ29sb3J9ICR7dXJsLnBhdGhuYW1lfSAtICR7c3RhdHVzQ29sb3J9ICR7ZHVyYXRpb25UZXh0fWAKICAgICAgKTsKICAgICAgcmV0dXJuIHJlc3BvbnNlOwogICAgfSBjYXRjaCAoZXJyb3IpIHsKICAgICAgY29uc3QgZHVyYXRpb24gPSBEYXRlLm5vdygpIC0gc3RhcnQ7CiAgICAgIGNvbnN0IG1ldGhvZENvbG9yID0gY3lhbihib2xkKG1ldGhvZCkpOwogICAgICBjb25zdCBlcnJvclRleHQgPSByZWQoYm9sZCgiRVJST1IiKSk7CiAgICAgIGNvbnN0IGR1cmF0aW9uVGV4dCA9IGRpbShgKCR7ZHVyYXRpb259bXMpYCk7CgogICAgICBjb25zb2xlLmVycm9yKAogICAgICAgIGAke21ldGhvZENvbG9yfSAke3VybC5wYXRobmFtZX0gLSAke2Vycm9yVGV4dH0gJHtkdXJhdGlvblRleHR9OmAsCiAgICAgICAgZXJyb3IKICAgICAgKTsKICAgICAgdGhyb3cgZXJyb3I7CiAgICB9CiAgfTsKfQo=" 45 57 }, 46 58 { 47 59 "path": "src/utils/render.tsx", 48 - "content": "aW1wb3J0IHsgcmVuZGVyVG9TdHJpbmcgfSBmcm9tICJwcmVhY3QtcmVuZGVyLXRvLXN0cmluZyI7CgpleHBvcnQgZnVuY3Rpb24gcmVuZGVySFRNTChlbGVtZW50OiBhbnkpOiBSZXNwb25zZSB7CiAgY29uc3QgaHRtbCA9IHJlbmRlclRvU3RyaW5nKGVsZW1lbnQpOwoKICByZXR1cm4gbmV3IFJlc3BvbnNlKGh0bWwsIHsKICAgIGhlYWRlcnM6IHsKICAgICAgIkNvbnRlbnQtVHlwZSI6ICJ0ZXh0L2h0bWw7IGNoYXJzZXQ9dXRmLTgiLAogICAgfSwKICB9KTsKfQ==" 60 + "content": "aW1wb3J0IHsgcmVuZGVyVG9TdHJpbmcgfSBmcm9tICJwcmVhY3QtcmVuZGVyLXRvLXN0cmluZyI7CmltcG9ydCB7IFZOb2RlIH0gZnJvbSAicHJlYWN0IjsKCmV4cG9ydCBmdW5jdGlvbiByZW5kZXJIVE1MKGVsZW1lbnQ6IFZOb2RlKTogUmVzcG9uc2UgewogIGNvbnN0IGh0bWwgPSByZW5kZXJUb1N0cmluZyhlbGVtZW50KTsKCiAgcmV0dXJuIG5ldyBSZXNwb25zZShodG1sLCB7CiAgICBoZWFkZXJzOiB7CiAgICAgICJDb250ZW50LVR5cGUiOiAidGV4dC9odG1sOyBjaGFyc2V0PXV0Zi04IiwKICAgIH0sCiAgfSk7Cn0=" 49 61 }, 50 62 { 51 63 "path": "src/shared/fragments/Layout.tsx", 52 - "content": "aW50ZXJmYWNlIExheW91dFByb3BzIHsKICB0aXRsZT86IHN0cmluZzsKICBjaGlsZHJlbjogYW55Owp9CgpleHBvcnQgZnVuY3Rpb24gTGF5b3V0KHsgdGl0bGUgPSAiQXBwIiwgY2hpbGRyZW4gfTogTGF5b3V0UHJvcHMpIHsKICByZXR1cm4gKAogICAgPGh0bWwgbGFuZz0iZW4iPgogICAgICA8aGVhZD4KICAgICAgICA8bWV0YSBjaGFyc2V0PSJVVEYtOCIgLz4KICAgICAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEuMCIgLz4KICAgICAgICA8dGl0bGU+e3RpdGxlfTwvdGl0bGU+CiAgICAgICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vY2RuLnRhaWx3aW5kY3NzLmNvbSI+PC9zY3JpcHQ+CiAgICAgICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vdW5wa2cuY29tL2h0bXgub3JnQDEuOS4xMCI+PC9zY3JpcHQ+CiAgICAgIDwvaGVhZD4KICAgICAgPGJvZHk+CiAgICAgICAge2NoaWxkcmVufQogICAgICA8L2JvZHk+CiAgICA8L2h0bWw+CiAgKTsKfQ==" 64 + "content": "aW1wb3J0IHsgQ29tcG9uZW50Q2hpbGRyZW4gfSBmcm9tICJwcmVhY3QiOwoKaW50ZXJmYWNlIExheW91dFByb3BzIHsKICB0aXRsZT86IHN0cmluZzsKICBjaGlsZHJlbjogQ29tcG9uZW50Q2hpbGRyZW47Cn0KCmV4cG9ydCBmdW5jdGlvbiBMYXlvdXQoeyB0aXRsZSA9ICJBcHAiLCBjaGlsZHJlbiB9OiBMYXlvdXRQcm9wcykgewogIHJldHVybiAoCiAgICA8aHRtbCBsYW5nPSJlbiI+CiAgICAgIDxoZWFkPgogICAgICAgIDxtZXRhIGNoYXJzZXQ9IlVURi04IiAvPgogICAgICAgIDxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIGluaXRpYWwtc2NhbGU9MS4wIiAvPgogICAgICAgIDx0aXRsZT57dGl0bGV9PC90aXRsZT4KICAgICAgICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly9jZG4udGFpbHdpbmRjc3MuY29tIj48L3NjcmlwdD4KICAgICAgICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly91bnBrZy5jb20vaHRteC5vcmdAMS45LjEwIj48L3NjcmlwdD4KICAgICAgPC9oZWFkPgogICAgICA8Ym9keT4KICAgICAgICB7Y2hpbGRyZW59CiAgICAgIDwvYm9keT4KICAgIDwvaHRtbD4KICApOwp9" 53 65 }, 54 66 { 55 67 "path": "src/shared/fragments/Button.tsx", 56 - "content": "aW1wb3J0IHsgY2xzeCB9IGZyb20gImNsc3giOwoKaW50ZXJmYWNlIEJ1dHRvblByb3BzIHsKICBjaGlsZHJlbjogYW55OwogIHR5cGU/OiAiYnV0dG9uIiB8ICJzdWJtaXQiIHwgInJlc2V0IjsKICB2YXJpYW50PzogInByaW1hcnkiIHwgInNlY29uZGFyeSIgfCAiZGFuZ2VyIjsKICBzaXplPzogInNtIiB8ICJtZCIgfCAibGciOwogIGNsYXNzTmFtZT86IHN0cmluZzsKICBkaXNhYmxlZD86IGJvb2xlYW47CiAgW2tleTogc3RyaW5nXTogYW55Owp9CgpleHBvcnQgZnVuY3Rpb24gQnV0dG9uKHsKICBjaGlsZHJlbiwKICB0eXBlID0gImJ1dHRvbiIsCiAgdmFyaWFudCA9ICJwcmltYXJ5IiwKICBzaXplID0gIm1kIiwKICBjbGFzc05hbWUsCiAgZGlzYWJsZWQsCiAgLi4ucHJvcHMKfTogQnV0dG9uUHJvcHMpIHsKICBjb25zdCBiYXNlQ2xhc3NlcyA9ICJpbmxpbmUtZmxleCBpdGVtcy1jZW50ZXIganVzdGlmeS1jZW50ZXIgZm9udC1tZWRpdW0gcm91bmRlZC1tZCBmb2N1czpvdXRsaW5lLW5vbmUgZm9jdXM6cmluZy0yIGZvY3VzOnJpbmctb2Zmc2V0LTIgZGlzYWJsZWQ6b3BhY2l0eS01MCBkaXNhYmxlZDpjdXJzb3Itbm90LWFsbG93ZWQiOwoKICBjb25zdCB2YXJpYW50Q2xhc3NlcyA9IHsKICAgIHByaW1hcnk6ICJiZy1ibHVlLTYwMCBob3ZlcjpiZy1ibHVlLTcwMCB0ZXh0LXdoaXRlIGZvY3VzOnJpbmctYmx1ZS01MDAiLAogICAgc2Vjb25kYXJ5OiAiYmctZ3JheS0yMDAgaG92ZXI6YmctZ3JheS0zMDAgdGV4dC1ncmF5LTkwMCBmb2N1czpyaW5nLWdyYXktNTAwIiwKICAgIGRhbmdlcjogImJnLXJlZC02MDAgaG92ZXI6YmctcmVkLTcwMCB0ZXh0LXdoaXRlIGZvY3VzOnJpbmctcmVkLTUwMCIsCiAgfTsKCiAgY29uc3Qgc2l6ZUNsYXNzZXMgPSB7CiAgICBzbTogInB4LTMgcHktMS41IHRleHQtc20iLAogICAgbWQ6ICJweC00IHB5LTIgdGV4dC1zbSIsCiAgICBsZzogInB4LTYgcHktMyB0ZXh0LWJhc2UiLAogIH07CgogIHJldHVybiAoCiAgICA8YnV0dG9uCiAgICAgIHR5cGU9e3R5cGV9CiAgICAgIGRpc2FibGVkPXtkaXNhYmxlZH0KICAgICAgY2xhc3NOYW1lPXtjbHN4KAogICAgICAgIGJhc2VDbGFzc2VzLAogICAgICAgIHZhcmlhbnRDbGFzc2VzW3ZhcmlhbnRdLAogICAgICAgIHNpemVDbGFzc2VzW3NpemVdLAogICAgICAgIGNsYXNzTmFtZQogICAgICApfQogICAgICB7Li4ucHJvcHN9CiAgICA+CiAgICAgIHtjaGlsZHJlbn0KICAgIDwvYnV0dG9uPgogICk7Cn0=" 68 + "content": "aW1wb3J0IHsgQ29tcG9uZW50Q2hpbGRyZW4sIEpTWCB9IGZyb20gInByZWFjdCI7CmltcG9ydCB7IGNuIH0gZnJvbSAiLi4vLi4vdXRpbHMvY24udHMiOwoKaW50ZXJmYWNlIEJ1dHRvblByb3BzIGV4dGVuZHMgT21pdDxKU1guSW50cmluc2ljRWxlbWVudHNbJ2J1dHRvbiddLCAic2l6ZSI+IHsKICBjaGlsZHJlbjogQ29tcG9uZW50Q2hpbGRyZW47CiAgdmFyaWFudD86ICJwcmltYXJ5IiB8ICJzZWNvbmRhcnkiIHwgImRhbmdlciI7CiAgc2l6ZT86ICJzbSIgfCAibWQiIHwgImxnIjsKfQoKZXhwb3J0IGZ1bmN0aW9uIEJ1dHRvbih7CiAgY2hpbGRyZW4sCiAgdHlwZSA9ICJidXR0b24iLAogIHZhcmlhbnQgPSAicHJpbWFyeSIsCiAgc2l6ZSA9ICJtZCIsCiAgY2xhc3NOYW1lLAogIGRpc2FibGVkLAogIC4uLnByb3BzCn06IEJ1dHRvblByb3BzKSB7CiAgY29uc3QgYmFzZUNsYXNzZXMgPQogICAgImlubGluZS1mbGV4IGl0ZW1zLWNlbnRlciBqdXN0aWZ5LWNlbnRlciBmb250LW1lZGl1bSByb3VuZGVkLW1kIGZvY3VzOm91dGxpbmUtbm9uZSBmb2N1czpyaW5nLTIgZm9jdXM6cmluZy1vZmZzZXQtMiBkaXNhYmxlZDpvcGFjaXR5LTUwIGRpc2FibGVkOmN1cnNvci1ub3QtYWxsb3dlZCI7CgogIGNvbnN0IHZhcmlhbnRDbGFzc2VzID0gewogICAgcHJpbWFyeTogImJnLWJsdWUtNjAwIGhvdmVyOmJnLWJsdWUtNzAwIHRleHQtd2hpdGUgZm9jdXM6cmluZy1ibHVlLTUwMCIsCiAgICBzZWNvbmRhcnk6CiAgICAgICJiZy1ncmF5LTIwMCBob3ZlcjpiZy1ncmF5LTMwMCB0ZXh0LWdyYXktOTAwIGZvY3VzOnJpbmctZ3JheS01MDAiLAogICAgZGFuZ2VyOiAiYmctcmVkLTYwMCBob3ZlcjpiZy1yZWQtNzAwIHRleHQtd2hpdGUgZm9jdXM6cmluZy1yZWQtNTAwIiwKICB9OwoKICBjb25zdCBzaXplQ2xhc3NlcyA9IHsKICAgIHNtOiAicHgtMyBweS0xLjUgdGV4dC1zbSIsCiAgICBtZDogInB4LTQgcHktMiB0ZXh0LXNtIiwKICAgIGxnOiAicHgtNiBweS0zIHRleHQtYmFzZSIsCiAgfTsKCiAgcmV0dXJuICgKICAgIDxidXR0b24KICAgICAgdHlwZT17dHlwZX0KICAgICAgZGlzYWJsZWQ9e2Rpc2FibGVkfQogICAgICBjbGFzc05hbWU9e2NuKAogICAgICAgIGJhc2VDbGFzc2VzLAogICAgICAgIHZhcmlhbnRDbGFzc2VzW3ZhcmlhbnRdLAogICAgICAgIHNpemVDbGFzc2VzW3NpemVdLAogICAgICAgIGNsYXNzTmFtZQogICAgICApfQogICAgICB7Li4ucHJvcHN9CiAgICA+CiAgICAgIHtjaGlsZHJlbn0KICAgIDwvYnV0dG9uPgogICk7Cn0K" 57 69 }, 58 70 { 59 71 "path": "src/shared/fragments/Input.tsx", 60 - "content": "aW1wb3J0IHsgY2xzeCB9IGZyb20gImNsc3giOwoKaW50ZXJmYWNlIElucHV0UHJvcHMgewogIHR5cGU/OiBzdHJpbmc7CiAgbmFtZT86IHN0cmluZzsKICBpZD86IHN0cmluZzsKICBwbGFjZWhvbGRlcj86IHN0cmluZzsKICByZXF1aXJlZD86IGJvb2xlYW47CiAgZGlzYWJsZWQ/OiBib29sZWFuOwogIGNsYXNzTmFtZT86IHN0cmluZzsKICB2YWx1ZT86IHN0cmluZzsKICBba2V5OiBzdHJpbmddOiBhbnk7Cn0KCmV4cG9ydCBmdW5jdGlvbiBJbnB1dCh7CiAgdHlwZSA9ICJ0ZXh0IiwKICBjbGFzc05hbWUsCiAgLi4ucHJvcHMKfTogSW5wdXRQcm9wcykgewogIHJldHVybiAoCiAgICA8aW5wdXQKICAgICAgdHlwZT17dHlwZX0KICAgICAgY2xhc3NOYW1lPXtjbHN4KAogICAgICAgICJibG9jayB3LWZ1bGwgcHgtMyBweS0yIGJvcmRlciBib3JkZXItZ3JheS0zMDAgcm91bmRlZC1tZCBzaGFkb3ctc20iLAogICAgICAgICJmb2N1czpvdXRsaW5lLW5vbmUgZm9jdXM6cmluZy1ibHVlLTUwMCBmb2N1czpib3JkZXItYmx1ZS01MDAiLAogICAgICAgICJkaXNhYmxlZDpiZy1ncmF5LTUwIGRpc2FibGVkOnRleHQtZ3JheS01MDAiLAogICAgICAgIGNsYXNzTmFtZQogICAgICApfQogICAgICB7Li4ucHJvcHN9CiAgICAvPgogICk7Cn0=" 72 + "content": "aW1wb3J0IHsgSlNYIH0gZnJvbSAicHJlYWN0IjsKaW1wb3J0IHsgY24gfSBmcm9tICIuLi8uLi91dGlscy9jbi50cyI7Cgp0eXBlIElucHV0UHJvcHMgPSBKU1guSW50cmluc2ljRWxlbWVudHNbJ2lucHV0J107CgpleHBvcnQgZnVuY3Rpb24gSW5wdXQoewogIHR5cGUgPSAidGV4dCIsCiAgY2xhc3NOYW1lLAogIC4uLnByb3BzCn06IElucHV0UHJvcHMpIHsKICByZXR1cm4gKAogICAgPGlucHV0CiAgICAgIHR5cGU9e3R5cGV9CiAgICAgIGNsYXNzTmFtZT17Y24oCiAgICAgICAgImJsb2NrIHctZnVsbCBweC0zIHB5LTIgYm9yZGVyIGJvcmRlci1ncmF5LTMwMCByb3VuZGVkLW1kIHNoYWRvdy1zbSIsCiAgICAgICAgImZvY3VzOm91dGxpbmUtbm9uZSBmb2N1czpyaW5nLWJsdWUtNTAwIGZvY3VzOmJvcmRlci1ibHVlLTUwMCIsCiAgICAgICAgImRpc2FibGVkOmJnLWdyYXktNTAgZGlzYWJsZWQ6dGV4dC1ncmF5LTUwMCIsCiAgICAgICAgY2xhc3NOYW1lCiAgICAgICl9CiAgICAgIHsuLi5wcm9wc30KICAgIC8+CiAgKTsKfQ==" 61 73 }, 62 74 { 63 75 "path": "src/config.ts", 64 - "content": "aW1wb3J0IHsgT0F1dGhDbGllbnQsIFNRTGl0ZU9BdXRoU3RvcmFnZSB9IGZyb20gIkBzbGljZXMvb2F1dGgiOwppbXBvcnQgeyBTZXNzaW9uU3RvcmUsIFNRTGl0ZUFkYXB0ZXIsIHdpdGhPQXV0aFNlc3Npb24gfSBmcm9tICJAc2xpY2VzL3Nlc3Npb24iOwoKY29uc3QgT0FVVEhfQ0xJRU5UX0lEID0gRGVuby5lbnYuZ2V0KCJPQVVUSF9DTElFTlRfSUQiKTsKY29uc3QgT0FVVEhfQ0xJRU5UX1NFQ1JFVCA9IERlbm8uZW52LmdldCgiT0FVVEhfQ0xJRU5UX1NFQ1JFVCIpOwpjb25zdCBPQVVUSF9SRURJUkVDVF9VUkkgPSBEZW5vLmVudi5nZXQoIk9BVVRIX1JFRElSRUNUX1VSSSIpOwpjb25zdCBPQVVUSF9BSVBfQkFTRV9VUkwgPSBEZW5vLmVudi5nZXQoIk9BVVRIX0FJUF9CQVNFX1VSTCIpOwpjb25zdCBBUElfVVJMID0gRGVuby5lbnYuZ2V0KCJBUElfVVJMIik7CmV4cG9ydCBjb25zdCBTTElDRV9VUkkgPSBEZW5vLmVudi5nZXQoIlNMSUNFX1VSSSIpOwoKaWYgKAogICFPQVVUSF9DTElFTlRfSUQgfHwKICAhT0FVVEhfQ0xJRU5UX1NFQ1JFVCB8fAogICFPQVVUSF9SRURJUkVDVF9VUkkgfHwKICAhT0FVVEhfQUlQX0JBU0VfVVJMIHx8CiAgIUFQSV9VUkwgfHwKICAhU0xJQ0VfVVJJCikgewogIHRocm93IG5ldyBFcnJvcigKICAgICJNaXNzaW5nIE9BdXRoIGNvbmZpZ3VyYXRpb24uIFBsZWFzZSBlbnN1cmUgLmVudiBmaWxlIGNvbnRhaW5zOlxuIiArCiAgICAgICJPQVVUSF9DTElFTlRfSUQsIE9BVVRIX0NMSUVOVF9TRUNSRVQsIE9BVVRIX1JFRElSRUNUX1VSSSwgT0FVVEhfQUlQX0JBU0VfVVJMLCBBUElfVVJMLCBTTElDRV9VUkkiCiAgKTsKfQoKY29uc3QgREFUQUJBU0VfVVJMID0gRGVuby5lbnYuZ2V0KCJEQVRBQkFTRV9VUkwiKSB8fCAic2xpY2VzLmRiIjsKCi8vIE9BdXRoIHNldHVwCmNvbnN0IG9hdXRoU3RvcmFnZSA9IG5ldyBTUUxpdGVPQXV0aFN0b3JhZ2UoREFUQUJBU0VfVVJMKTsKY29uc3Qgb2F1dGhDbGllbnQgPSBuZXcgT0F1dGhDbGllbnQoCiAgewogICAgY2xpZW50SWQ6IE9BVVRIX0NMSUVOVF9JRCwKICAgIGNsaWVudFNlY3JldDogT0FVVEhfQ0xJRU5UX1NFQ1JFVCwKICAgIGF1dGhCYXNlVXJsOiBPQVVUSF9BSVBfQkFTRV9VUkwsCiAgICByZWRpcmVjdFVyaTogT0FVVEhfUkVESVJFQ1RfVVJJLAogICAgc2NvcGVzOiBbCiAgICAgICJvcGVuaWQiLAogICAgICAiZW1haWwiLAogICAgICAicHJvZmlsZSIsCiAgICAgICJhdHByb3RvIiwKICAgICAgInRyYW5zaXRpb246Z2VuZXJpYyIsCiAgICBdLAogIH0sCiAgb2F1dGhTdG9yYWdlCik7CgovLyBTZXNzaW9uIHNldHVwIChzaGFyZWQgZGF0YWJhc2UpCmV4cG9ydCBjb25zdCBzZXNzaW9uU3RvcmUgPSBuZXcgU2Vzc2lvblN0b3JlKHsKICBhZGFwdGVyOiBuZXcgU1FMaXRlQWRhcHRlcihEQVRBQkFTRV9VUkwpLAogIGNvb2tpZU9wdGlvbnM6IHsKICAgIGh0dHBPbmx5OiB0cnVlLAogICAgc2VjdXJlOiBEZW5vLmVudi5nZXQoIkRFTk9fRU5WIikgPT09ICJwcm9kdWN0aW9uIiwKICAgIHNhbWVTaXRlOiAibGF4IiwKICAgIHBhdGg6ICIvIiwKICB9LAp9KTsKCi8vIE9BdXRoICsgU2Vzc2lvbiBpbnRlZ3JhdGlvbgpleHBvcnQgY29uc3Qgb2F1dGhTZXNzaW9ucyA9IHdpdGhPQXV0aFNlc3Npb24oc2Vzc2lvblN0b3JlLCBvYXV0aENsaWVudCwgewogIGF1dG9SZWZyZXNoOiB0cnVlLAp9KTsKCmV4cG9ydCB7IG9hdXRoQ2xpZW50IH07" 76 + "content": "aW1wb3J0IHsgT0F1dGhDbGllbnQsIFNRTGl0ZU9BdXRoU3RvcmFnZSB9IGZyb20gIkBzbGljZXMvb2F1dGgiOwppbXBvcnQgeyBTZXNzaW9uU3RvcmUsIFNRTGl0ZUFkYXB0ZXIsIHdpdGhPQXV0aFNlc3Npb24gfSBmcm9tICJAc2xpY2VzL3Nlc3Npb24iOwppbXBvcnQgeyBBdFByb3RvQ2xpZW50IH0gZnJvbSAiLi9nZW5lcmF0ZWRfY2xpZW50LnRzIjsKCmNvbnN0IE9BVVRIX0NMSUVOVF9JRCA9IERlbm8uZW52LmdldCgiT0FVVEhfQ0xJRU5UX0lEIik7CmNvbnN0IE9BVVRIX0NMSUVOVF9TRUNSRVQgPSBEZW5vLmVudi5nZXQoIk9BVVRIX0NMSUVOVF9TRUNSRVQiKTsKY29uc3QgT0FVVEhfUkVESVJFQ1RfVVJJID0gRGVuby5lbnYuZ2V0KCJPQVVUSF9SRURJUkVDVF9VUkkiKTsKY29uc3QgT0FVVEhfQUlQX0JBU0VfVVJMID0gRGVuby5lbnYuZ2V0KCJPQVVUSF9BSVBfQkFTRV9VUkwiKTsKY29uc3QgQVBJX1VSTCA9IERlbm8uZW52LmdldCgiQVBJX1VSTCIpOwpleHBvcnQgY29uc3QgU0xJQ0VfVVJJID0gRGVuby5lbnYuZ2V0KCJTTElDRV9VUkkiKTsKCmlmICgKICAhT0FVVEhfQ0xJRU5UX0lEIHx8CiAgIU9BVVRIX0NMSUVOVF9TRUNSRVQgfHwKICAhT0FVVEhfUkVESVJFQ1RfVVJJIHx8CiAgIU9BVVRIX0FJUF9CQVNFX1VSTCB8fAogICFBUElfVVJMIHx8CiAgIVNMSUNFX1VSSQopIHsKICB0aHJvdyBuZXcgRXJyb3IoCiAgICAiTWlzc2luZyBPQXV0aCBjb25maWd1cmF0aW9uLiBQbGVhc2UgZW5zdXJlIC5lbnYgZmlsZSBjb250YWluczpcbiIgKwogICAgICAiT0FVVEhfQ0xJRU5UX0lELCBPQVVUSF9DTElFTlRfU0VDUkVULCBPQVVUSF9SRURJUkVDVF9VUkksIE9BVVRIX0FJUF9CQVNFX1VSTCwgQVBJX1VSTCwgU0xJQ0VfVVJJIgogICk7Cn0KCmNvbnN0IERBVEFCQVNFX1VSTCA9IERlbm8uZW52LmdldCgiREFUQUJBU0VfVVJMIikgfHwgInNsaWNlcy5kYiI7CgovLyBPQXV0aCBzZXR1cApjb25zdCBvYXV0aFN0b3JhZ2UgPSBuZXcgU1FMaXRlT0F1dGhTdG9yYWdlKERBVEFCQVNFX1VSTCk7CmNvbnN0IG9hdXRoQ29uZmlnID0gewogIGNsaWVudElkOiBPQVVUSF9DTElFTlRfSUQsCiAgY2xpZW50U2VjcmV0OiBPQVVUSF9DTElFTlRfU0VDUkVULAogIGF1dGhCYXNlVXJsOiBPQVVUSF9BSVBfQkFTRV9VUkwsCiAgcmVkaXJlY3RVcmk6IE9BVVRIX1JFRElSRUNUX1VSSSwKICBzY29wZXM6IFsiYXRwcm90byIsICJvcGVuaWQiLCAicHJvZmlsZSJdLAp9OwoKLy8gRXhwb3J0IGNvbmZpZyBhbmQgc3RvcmFnZSBmb3IgY3JlYXRpbmcgdXNlci1zY29wZWQgY2xpZW50cwpleHBvcnQgeyBvYXV0aENvbmZpZywgb2F1dGhTdG9yYWdlIH07CgovLyBTZXNzaW9uIHNldHVwIChzaGFyZWQgZGF0YWJhc2UpCmV4cG9ydCBjb25zdCBzZXNzaW9uU3RvcmUgPSBuZXcgU2Vzc2lvblN0b3JlKHsKICBhZGFwdGVyOiBuZXcgU1FMaXRlQWRhcHRlcihEQVRBQkFTRV9VUkwpLAogIGNvb2tpZU5hbWU6ICJ7e1BST0pFQ1RfTkFNRX19LXNlc3Npb24iLAogIGNvb2tpZU9wdGlvbnM6IHsKICAgIGh0dHBPbmx5OiB0cnVlLAogICAgc2VjdXJlOiBEZW5vLmVudi5nZXQoIkRFTk9fRU5WIikgPT09ICJwcm9kdWN0aW9uIiwKICAgIHNhbWVTaXRlOiAibGF4IiwKICAgIHBhdGg6ICIvIiwKICB9LAp9KTsKCi8vIE9BdXRoICsgU2Vzc2lvbiBpbnRlZ3JhdGlvbgpleHBvcnQgY29uc3Qgb2F1dGhTZXNzaW9ucyA9IHdpdGhPQXV0aFNlc3Npb24oCiAgc2Vzc2lvblN0b3JlLAogIG9hdXRoQ29uZmlnLAogIG9hdXRoU3RvcmFnZSwKICB7CiAgICBhdXRvUmVmcmVzaDogdHJ1ZSwKICB9Cik7CgovLyBIZWxwZXIgZnVuY3Rpb24gdG8gY3JlYXRlIHVzZXItc2NvcGVkIE9BdXRoIGNsaWVudApleHBvcnQgZnVuY3Rpb24gY3JlYXRlT0F1dGhDbGllbnQodXNlcklkOiBzdHJpbmcpOiBPQXV0aENsaWVudCB7CiAgcmV0dXJuIG5ldyBPQXV0aENsaWVudChvYXV0aENvbmZpZywgb2F1dGhTdG9yYWdlLCB1c2VySWQpOwp9CgovLyBIZWxwZXIgZnVuY3Rpb24gdG8gY3JlYXRlIGF1dGhlbnRpY2F0ZWQgQXRQcm90byBjbGllbnQgZm9yIGEgdXNlcgpleHBvcnQgZnVuY3Rpb24gY3JlYXRlU2Vzc2lvbkNsaWVudCh1c2VySWQ6IHN0cmluZyk6IEF0UHJvdG9DbGllbnQgewogIGNvbnN0IHVzZXJPQXV0aENsaWVudCA9IGNyZWF0ZU9BdXRoQ2xpZW50KHVzZXJJZCk7CiAgcmV0dXJuIG5ldyBBdFByb3RvQ2xpZW50KEFQSV9VUkwhLCBTTElDRV9VUkkhLCB1c2VyT0F1dGhDbGllbnQpOwp9CgovLyBQdWJsaWMgY2xpZW50IGZvciB1bmF1dGhlbnRpY2F0ZWQgcmVxdWVzdHMKZXhwb3J0IGNvbnN0IHB1YmxpY0NsaWVudCA9IG5ldyBBdFByb3RvQ2xpZW50KEFQSV9VUkwsIFNMSUNFX1VSSSk7" 65 77 }, 66 78 { 67 79 "path": "src/routes/middleware.ts", 68 - "content": "aW1wb3J0IHsgc2Vzc2lvblN0b3JlLCBvYXV0aFNlc3Npb25zLCBvYXV0aENsaWVudCB9IGZyb20gIi4uL2NvbmZpZy50cyI7CgpleHBvcnQgaW50ZXJmYWNlIEF1dGhDb250ZXh0IHsKICBjdXJyZW50VXNlcjogewogICAgc3ViOiBzdHJpbmc7CiAgICBuYW1lPzogc3RyaW5nOwogICAgZW1haWw/OiBzdHJpbmc7CiAgfSB8IG51bGw7CiAgc2Vzc2lvbklkOiBzdHJpbmcgfCBudWxsOwp9CgpleHBvcnQgYXN5bmMgZnVuY3Rpb24gd2l0aEF1dGgocmVxOiBSZXF1ZXN0KTogUHJvbWlzZTxBdXRoQ29udGV4dD4gewogIGNvbnN0IHNlc3Npb24gPSBhd2FpdCBzZXNzaW9uU3RvcmUuZ2V0U2Vzc2lvbkZyb21SZXF1ZXN0KHJlcSk7CgogIGlmICghc2Vzc2lvbikgewogICAgcmV0dXJuIHsgY3VycmVudFVzZXI6IG51bGwsIHNlc3Npb25JZDogbnVsbCB9OwogIH0KCiAgdHJ5IHsKICAgIC8vIEdldCB1c2VyIGluZm8gZnJvbSBPQXV0aCBjbGllbnQKICAgIGNvbnN0IHVzZXJJbmZvID0gYXdhaXQgb2F1dGhDbGllbnQuZ2V0VXNlckluZm8oKTsKICAgIHJldHVybiB7CiAgICAgIGN1cnJlbnRVc2VyOiB1c2VySW5mbyB8fCBudWxsLAogICAgICBzZXNzaW9uSWQ6IHNlc3Npb24uc2Vzc2lvbklkLAogICAgfTsKICB9IGNhdGNoIHsKICAgIHJldHVybiB7IGN1cnJlbnRVc2VyOiBudWxsLCBzZXNzaW9uSWQ6IHNlc3Npb24uc2Vzc2lvbklkIH07CiAgfQp9CgpleHBvcnQgZnVuY3Rpb24gcmVxdWlyZUF1dGgoaGFuZGxlcjogKHJlcTogUmVxdWVzdCwgY29udGV4dDogQXV0aENvbnRleHQpID0+IFByb21pc2U8UmVzcG9uc2U+KSB7CiAgcmV0dXJuIGFzeW5jIChyZXE6IFJlcXVlc3QpOiBQcm9taXNlPFJlc3BvbnNlPiA9PiB7CiAgICBjb25zdCBjb250ZXh0ID0gYXdhaXQgd2l0aEF1dGgocmVxKTsKCiAgICBpZiAoIWNvbnRleHQuY3VycmVudFVzZXIpIHsKICAgICAgcmV0dXJuIFJlc3BvbnNlLnJlZGlyZWN0KG5ldyBVUkwoIi9sb2dpbiIsIHJlcS51cmwpLCAzMDIpOwogICAgfQoKICAgIHJldHVybiBoYW5kbGVyKHJlcSwgY29udGV4dCk7CiAgfTsKfQ==" 80 + "content": "aW1wb3J0IHsgc2Vzc2lvblN0b3JlLCBjcmVhdGVPQXV0aENsaWVudCB9IGZyb20gIi4uL2NvbmZpZy50cyI7CgpleHBvcnQgaW50ZXJmYWNlIEF1dGhDb250ZXh0IHsKICBjdXJyZW50VXNlcjogewogICAgc3ViOiBzdHJpbmc7CiAgICBuYW1lPzogc3RyaW5nOwogICAgZW1haWw/OiBzdHJpbmc7CiAgfSB8IG51bGw7CiAgc2Vzc2lvbklkOiBzdHJpbmcgfCBudWxsOwp9CgpleHBvcnQgYXN5bmMgZnVuY3Rpb24gd2l0aEF1dGgocmVxOiBSZXF1ZXN0KTogUHJvbWlzZTxBdXRoQ29udGV4dD4gewogIGNvbnN0IHNlc3Npb24gPSBhd2FpdCBzZXNzaW9uU3RvcmUuZ2V0U2Vzc2lvbkZyb21SZXF1ZXN0KHJlcSk7CgogIGlmICghc2Vzc2lvbikgewogICAgcmV0dXJuIHsgY3VycmVudFVzZXI6IG51bGwsIHNlc3Npb25JZDogbnVsbCB9OwogIH0KCiAgdHJ5IHsKICAgIGNvbnN0IHNlc3Npb25PQXV0aENsaWVudCA9IGNyZWF0ZU9BdXRoQ2xpZW50KHNlc3Npb24uc2Vzc2lvbklkKTsKICAgIGNvbnN0IHVzZXJJbmZvID0gYXdhaXQgc2Vzc2lvbk9BdXRoQ2xpZW50LmdldFVzZXJJbmZvKCk7CiAgICByZXR1cm4gewogICAgICBjdXJyZW50VXNlcjogdXNlckluZm8gfHwgbnVsbCwKICAgICAgc2Vzc2lvbklkOiBzZXNzaW9uLnNlc3Npb25JZCwKICAgIH07CiAgfSBjYXRjaCB7CiAgICByZXR1cm4geyBjdXJyZW50VXNlcjogbnVsbCwgc2Vzc2lvbklkOiBzZXNzaW9uLnNlc3Npb25JZCB9OwogIH0KfQoKZXhwb3J0IGZ1bmN0aW9uIHJlcXVpcmVBdXRoKAogIGhhbmRsZXI6IChyZXE6IFJlcXVlc3QsIGNvbnRleHQ6IEF1dGhDb250ZXh0KSA9PiBQcm9taXNlPFJlc3BvbnNlPgopIHsKICByZXR1cm4gYXN5bmMgKHJlcTogUmVxdWVzdCk6IFByb21pc2U8UmVzcG9uc2U+ID0+IHsKICAgIGNvbnN0IGNvbnRleHQgPSBhd2FpdCB3aXRoQXV0aChyZXEpOwoKICAgIGlmICghY29udGV4dC5jdXJyZW50VXNlcikgewogICAgICByZXR1cm4gUmVzcG9uc2UucmVkaXJlY3QobmV3IFVSTCgiL2xvZ2luIiwgcmVxLnVybCksIDMwMik7CiAgICB9CgogICAgcmV0dXJuIGhhbmRsZXIocmVxLCBjb250ZXh0KTsKICB9Owp9Cg==" 69 81 }, 70 82 { 71 83 "path": "src/routes/mod.ts", 72 - "content": "aW1wb3J0IHR5cGUgeyBSb3V0ZSB9IGZyb20gIkBzdGQvaHR0cC91bnN0YWJsZS1yb3V0ZSI7CmltcG9ydCB7IGF1dGhSb3V0ZXMgfSBmcm9tICIuLi9mZWF0dXJlcy9hdXRoL2hhbmRsZXJzLnRzeCI7CgpleHBvcnQgY29uc3QgYWxsUm91dGVzOiBSb3V0ZVtdID0gWwogIC8vIFJvb3QgcmVkaXJlY3QgdG8gbG9naW4gZm9yIG5vdwogIHsKICAgIG1ldGhvZDogIkdFVCIsCiAgICBwYXR0ZXJuOiBuZXcgVVJMUGF0dGVybih7IHBhdGhuYW1lOiAiLyIgfSksCiAgICBoYW5kbGVyOiAocmVxKSA9PiBSZXNwb25zZS5yZWRpcmVjdChuZXcgVVJMKCIvbG9naW4iLCByZXEudXJsKSwgMzAyKSwKICB9LAoKICAvLyBBdXRoIHJvdXRlcwogIC4uLmF1dGhSb3V0ZXMsCl07" 84 + "content": "aW1wb3J0IHR5cGUgeyBSb3V0ZSB9IGZyb20gIkBzdGQvaHR0cC91bnN0YWJsZS1yb3V0ZSI7CmltcG9ydCB7IGF1dGhSb3V0ZXMgfSBmcm9tICIuLi9mZWF0dXJlcy9hdXRoL2hhbmRsZXJzLnRzeCI7CmltcG9ydCB7IGRhc2hib2FyZFJvdXRlcyB9IGZyb20gIi4uL2ZlYXR1cmVzL2Rhc2hib2FyZC9oYW5kbGVycy50c3giOwoKZXhwb3J0IGNvbnN0IGFsbFJvdXRlczogUm91dGVbXSA9IFsKICAvLyBSb290IHJlZGlyZWN0IHRvIGxvZ2luIGZvciBub3cKICB7CiAgICBtZXRob2Q6ICJHRVQiLAogICAgcGF0dGVybjogbmV3IFVSTFBhdHRlcm4oeyBwYXRobmFtZTogIi8iIH0pLAogICAgaGFuZGxlcjogKHJlcSkgPT4gUmVzcG9uc2UucmVkaXJlY3QobmV3IFVSTCgiL2xvZ2luIiwgcmVxLnVybCksIDMwMiksCiAgfSwKCiAgLy8gQXV0aCByb3V0ZXMKICAuLi5hdXRoUm91dGVzLAoKICAvLyBEYXNoYm9hcmQgcm91dGVzCiAgLi4uZGFzaGJvYXJkUm91dGVzLApdOw==" 73 85 } 74 86 ]; 75 87
+14
packages/cli/src/utils/constants.ts
··· 1 + /** 2 + * System-wide constants for the Slices CLI 3 + */ 4 + 5 + // The main Slices platform slice URI and DID 6 + export const SYSTEM_SLICE_URI = "at://did:plc:bcgltzqazw5tb6k2g3ttenbj/network.slices.slice/3lymhd4jhrd2z"; 7 + export const SYSTEM_SLICE_DID = "did:plc:bcgltzqazw5tb6k2g3ttenbj"; 8 + 9 + // Reference slice with base lexicons for new projects 10 + export const REFERENCE_SLICE_URI = "at://did:plc:bcgltzqazw5tb6k2g3ttenbj/network.slices.slice/3lzbzumcmvo2z"; 11 + 12 + // API endpoints 13 + export const DEFAULT_API_URL = "https://api.slices.network"; 14 + export const DEFAULT_AIP_BASE_URL = "https://auth.slices.network";
+61
packages/cli/src/utils/name_generator.ts
··· 1 + /** 2 + * Simple Docker-style name generator for slices 3 + * Generates names in the format: adjective-noun 4 + */ 5 + 6 + const adjectives = [ 7 + "awesome", "blazing", "brilliant", "clever", "cool", 8 + "dazzling", "dynamic", "elegant", "epic", "fast", 9 + "fearless", "friendly", "gentle", "happy", "jolly", 10 + "kind", "lively", "mighty", "nimble", "peaceful", 11 + "quick", "radiant", "serene", "sharp", "smooth", 12 + "stellar", "swift", "tender", "vibrant", "wise", 13 + "zen", "cosmic", "digital", "quantum", "cyber", 14 + "neon", "pixel", "sonic", "turbo", "ultra" 15 + ]; 16 + 17 + const nouns = [ 18 + "slice", "wave", "spark", "pulse", "stream", 19 + "beacon", "bridge", "cloud", "comet", "crystal", 20 + "delta", "echo", "flame", "galaxy", "harbor", 21 + "horizon", "iris", "jet", "lens", "meteor", 22 + "nexus", "orbit", "phoenix", "prism", "quasar", 23 + "ray", "sphere", "star", "storm", "tide", 24 + "vertex", "vortex", "zen", "zone", "arc", 25 + "beam", "core", "drift", "edge", "flux" 26 + ]; 27 + 28 + /** 29 + * Generates a random slice name in the format: adjective-noun-xxxx 30 + * @param separator The separator to use between words (default: "-") 31 + * @returns A randomly generated name with 4-digit suffix 32 + */ 33 + export function generateSliceName(separator: string = "-"): string { 34 + const adjective = adjectives[Math.floor(Math.random() * adjectives.length)]; 35 + const noun = nouns[Math.floor(Math.random() * nouns.length)]; 36 + const randomSuffix = Math.floor(Math.random() * 10000).toString().padStart(4, '0'); 37 + return `${adjective}${separator}${noun}${separator}${randomSuffix}`; 38 + } 39 + 40 + /** 41 + * Generates a unique slice name with a random number suffix 42 + * @param separator The separator to use between words (default: "-") 43 + * @returns A randomly generated name with number suffix 44 + */ 45 + export function generateUniqueSliceName(separator: string = "-"): string { 46 + const baseName = generateSliceName(separator); 47 + const randomNum = Math.floor(Math.random() * 9999); 48 + return `${baseName}${separator}${randomNum}`; 49 + } 50 + 51 + /** 52 + * Generates a unique random domain name 53 + * @returns A unique domain in the format: network.slices.adjective-noun-xxxx 54 + */ 55 + export function generateDomain(): string { 56 + // Generate a completely random domain, independent of slice name 57 + const adjective = adjectives[Math.floor(Math.random() * adjectives.length)]; 58 + const noun = nouns[Math.floor(Math.random() * nouns.length)]; 59 + const randomSuffix = Math.floor(Math.random() * 10000).toString().padStart(4, '0'); 60 + return `network.slices.${adjective}-${noun}-${randomSuffix}`; 61 + }
+18
packages/cli/src/utils/strings.ts
··· 1 + /** 2 + * Convert a string to kebab-case (dash-separated lowercase) 3 + * 4 + * Examples: 5 + * "MyProject" -> "my-project" 6 + * "my_project" -> "my-project" 7 + * "My Cool Project" -> "my-cool-project" 8 + * "myProject123" -> "my-project123" 9 + */ 10 + export function dasherize(str: string): string { 11 + return str 12 + .replace(/([a-z])([A-Z])/g, '$1-$2') // camelCase to kebab-case 13 + .replace(/[\s_]+/g, '-') // spaces and underscores to dashes 14 + .replace(/[^a-z0-9-]/gi, '') // remove invalid characters 15 + .replace(/-+/g, '-') // collapse multiple dashes 16 + .replace(/^-|-$/g, '') // trim dashes from start/end 17 + .toLowerCase(); 18 + }
+6 -20
packages/cli/src/utils/waitlist.ts
··· 1 1 import { AtProtoClient } from "../generated_client.ts"; 2 2 import { logger } from "./logger.ts"; 3 - 4 - const DEFAULT_SLICE_URI = 5 - "at://did:plc:bcgltzqazw5tb6k2g3ttenbj/network.slices.slice/3lymhd4jhrd2z"; 6 - const DEFAULT_ADMIN_DID = "did:plc:bcgltzqazw5tb6k2g3ttenbj"; 7 - 8 - function getSliceUri(): string { 9 - return Deno.env.get("SLICE_URI") || DEFAULT_SLICE_URI; 10 - } 11 - 12 - function getAdminDid(): string { 13 - return Deno.env.get("ADMIN_DID") || DEFAULT_ADMIN_DID; 14 - } 3 + import { SYSTEM_SLICE_URI, SYSTEM_SLICE_DID, DEFAULT_API_URL } from "./constants.ts"; 15 4 16 5 export interface WaitlistCheckResult { 17 6 hasAccess: boolean; ··· 20 9 21 10 export async function checkUserWaitlistAccess( 22 11 userDid: string, 23 - apiUrl = "https://api.slices.network" 12 + apiUrl = DEFAULT_API_URL 24 13 ): Promise<WaitlistCheckResult> { 25 14 try { 26 - const sliceUri = getSliceUri(); 27 - const adminDid = getAdminDid(); 28 - 29 15 // Create a public client for checking waitlist status (no auth needed) 30 - const client = new AtProtoClient(apiUrl, sliceUri); 16 + const client = new AtProtoClient(apiUrl, SYSTEM_SLICE_URI); 31 17 32 18 // Query for invites for this DID - using json field to query the record content 33 19 const invitesResult = 34 20 await client.network.slices.waitlist.invite.getRecords({ 35 21 where: { 36 - did: { eq: adminDid }, 37 - slice: { eq: sliceUri }, 22 + did: { eq: SYSTEM_SLICE_DID }, 23 + slice: { eq: SYSTEM_SLICE_URI }, 38 24 json: { contains: userDid }, 39 25 }, 40 26 limit: 1, ··· 60 46 const requestsResult = 61 47 await client.network.slices.waitlist.request.getRecords({ 62 48 where: { 63 - slice: { eq: sliceUri }, 49 + slice: { eq: SYSTEM_SLICE_URI }, 64 50 json: { eq: userDid }, 65 51 }, 66 52 limit: 1,