See the best posts from any Bluesky account
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

wire up BackfillJob dispatch in ProfileController

Implements spec §6 Flow 2 web request handler:
- ProfileController.#ensureBackfillStarted() resolves handle→DID via
HandleResolver.resolveToDid(), upserts User row via firstOrCreate,
and attempts to INSERT BackfillJobRow — using the PK unique constraint
as a dedup gate. Only the winner of the insert race calls BackfillJob.dispatch().
- HandleNotFoundError renders 404; BlueskyRateLimitedError renders 503.
- server_error.edge now renders the optional `message` variable so callers
can supply context-specific error text.
- isUniquePrimaryKeyViolation() helper detects SQLITE_CONSTRAINT_PRIMARYKEY
without silently swallowing other error types.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

+92 -3
+91 -2
apps/web/app/controllers/profile_controller.ts
··· 1 1 import { inject } from '@adonisjs/core' 2 2 import type { HttpContext } from '@adonisjs/core/http' 3 - import { HandleResolver, InvalidHandleError } from '#services/handle_resolver' 3 + import { HandleResolver, InvalidHandleError, HandleNotFoundError } from '#services/handle_resolver' 4 + import { BlueskyRateLimitedError } from '@skystar/atproto' 4 5 import { ClickHouseStore } from '@skystar/clickhouse' 5 6 import User from '#models/user' 7 + import BackfillJobRow from '#models/backfill_job' 8 + import BackfillJob from '#jobs/backfill_job' 6 9 import env from '#start/env' 7 10 8 11 type Kind = 'likes' | 'reposts' ··· 119 122 120 123 // 4. Branch on user state 121 124 if (!user || user.backfilledAt === null) { 122 - // Loading page — backfill not yet complete 125 + // Loading page — backfill not yet complete (trigger if not already running) 126 + try { 127 + await this.#ensureBackfillStarted(canonicalHandle) 128 + } catch (err) { 129 + if (err instanceof HandleNotFoundError) { 130 + response.status(404) 131 + return view.render('pages/errors/not_found', { 132 + handle: canonicalHandle, 133 + message: `We can't find @${canonicalHandle} on Bluesky.`, 134 + }) 135 + } 136 + if (err instanceof BlueskyRateLimitedError) { 137 + response.status(503) 138 + return view.render('pages/errors/server_error', { 139 + message: "We're rate-limited by Bluesky right now. Try again in a moment.", 140 + }) 141 + } 142 + throw err 143 + } 123 144 return view.render('pages/profile/loading', { handle: canonicalHandle }) 124 145 } 125 146 ··· 165 186 canonicalUrl, 166 187 }) 167 188 } 189 + 190 + /** 191 + * Ensure the backfill is started for a handle that hasn't been backfilled yet. 192 + * 193 + * 1. Resolves the handle to a DID (network call). 194 + * 2. Upserts the User row (idempotent). 195 + * 3. Attempts to insert a BackfillJobRow. If we win the insert race, dispatches 196 + * the BackfillJob queue job. If another request already inserted the row 197 + * (duplicate primary key), we skip the dispatch — the dedup gate. 198 + * 199 + * @throws HandleNotFoundError when the handle doesn't exist on Bluesky 200 + * @throws BlueskyRateLimitedError when we're rate-limited 201 + */ 202 + async #ensureBackfillStarted(canonicalHandle: string): Promise<void> { 203 + // 1. Resolve handle → DID (only network call in this method) 204 + const did = await this.handleResolver.resolveToDid(canonicalHandle) 205 + 206 + // 2. Upsert User row (idempotent — firstOrCreate won't duplicate) 207 + await User.firstOrCreate( 208 + { did }, 209 + { 210 + did, 211 + handle: canonicalHandle, 212 + firstSeenAt: Date.now(), 213 + } 214 + ) 215 + 216 + // 3. Attempt to insert BackfillJobRow as the dedup gate. 217 + // Only the first request succeeds; concurrent requests get a unique 218 + // constraint violation and skip dispatch. 219 + let weCreatedTheJob = false 220 + try { 221 + await BackfillJobRow.create({ 222 + did, 223 + startedAt: Date.now(), 224 + state: 'running', 225 + fetchedPosts: 0, 226 + }) 227 + weCreatedTheJob = true 228 + } catch (err) { 229 + if (!isUniquePrimaryKeyViolation(err)) throw err 230 + // A parallel request already inserted the row — skip dispatch 231 + } 232 + 233 + // 4. Dispatch the queue job only if we won the race 234 + if (weCreatedTheJob) { 235 + await BackfillJob.dispatch({ did }) 236 + } 237 + } 168 238 } 169 239 170 240 // --------------------------------------------------------------------------- 171 241 // Helpers 172 242 // --------------------------------------------------------------------------- 243 + 244 + /** 245 + * Returns true when `err` is a SQLite primary-key unique constraint violation. 246 + * 247 + * better-sqlite3 throws a `SqliteError` with `code === 'SQLITE_CONSTRAINT_PRIMARYKEY'` 248 + * for INSERT OR REPLACE / duplicate-PK inserts. Knex does not wrap this error — 249 + * it propagates the original better-sqlite3 error unchanged. 250 + * 251 + * We only swallow THIS specific error (not generic SQLITE_CONSTRAINT, not foreign- 252 + * key violations, not anything from other databases). Anything else propagates. 253 + */ 254 + function isUniquePrimaryKeyViolation(err: unknown): boolean { 255 + return ( 256 + typeof err === 'object' && 257 + err !== null && 258 + 'code' in err && 259 + (err as { code: string }).code === 'SQLITE_CONSTRAINT_PRIMARYKEY' 260 + ) 261 + } 173 262 174 263 /** 175 264 * Converts an AT-URI to a bsky.app URL.
+1 -1
apps/web/resources/views/pages/errors/server_error.edge
··· 6 6 @slot('main') 7 7 <div style="padding: 64px 0; text-align: center;"> 8 8 <h1 style="font-size: 1.6rem; margin-bottom: 16px;"> 9 - We're having a moment, try again in a sec. 9 + {{ message ?? "We're having a moment, try again in a sec." }} 10 10 </h1> 11 11 <p><a href="/">← Back to search</a></p> 12 12 </div>