See the best posts from any Bluesky account
0
fork

Configure Feed

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

Add notified_thresholds table and Lucid model

Per-(post, threshold) dedup store for the firehose threshold-scan job: once
we fire a Discord webhook for a post crossing a threshold, we insert a row
here so we don't re-fire on subsequent polls. Composite primary key on
(subject_uri, threshold); Lucid handles this with isPrimary on both columns.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+85
+23
app/models/notified_threshold.ts
··· 1 + import { BaseModel, column } from '@adonisjs/lucid/orm' 2 + 3 + /** 4 + * Per-(post, threshold) dedup row so the firehose threshold-scan job only 5 + * fires a Discord webhook the first time a post crosses each threshold. 6 + * 7 + * Composite primary key on (subject_uri, threshold) — Lucid supports this 8 + * by marking both columns as `isPrimary`. Queries that need to load a row 9 + * should use `.query().where(...).first()` rather than `findOrFail(...)`. 10 + */ 11 + export default class NotifiedThreshold extends BaseModel { 12 + static table = 'notified_thresholds' 13 + static selfAssignPrimaryKey = true 14 + 15 + @column({ isPrimary: true }) 16 + declare subjectUri: string 17 + 18 + @column({ isPrimary: true }) 19 + declare threshold: number 20 + 21 + @column() 22 + declare firedAt: number 23 + }
+16
database/migrations/1776167848712_create_notified_thresholds.ts
··· 1 + import { BaseSchema } from '@adonisjs/lucid/schema' 2 + 3 + export default class extends BaseSchema { 4 + async up() { 5 + this.schema.createTable('notified_thresholds', (table) => { 6 + table.text('subject_uri').notNullable() 7 + table.integer('threshold').notNullable() 8 + table.integer('fired_at').notNullable() 9 + table.primary(['subject_uri', 'threshold']) 10 + }) 11 + } 12 + 13 + async down() { 14 + this.schema.dropTable('notified_thresholds') 15 + } 16 + }
+46
tests/unit/notified_threshold.spec.ts
··· 1 + import { test } from '@japa/runner' 2 + import testUtils from '@adonisjs/core/services/test_utils' 3 + import NotifiedThreshold from '#models/notified_threshold' 4 + 5 + test.group('Lucid schemas — NotifiedThreshold', (group) => { 6 + group.each.setup(() => testUtils.db().withGlobalTransaction()) 7 + 8 + test('can create and read back a dedup row', async ({ assert }) => { 9 + const firedAt = Date.now() 10 + await NotifiedThreshold.create({ 11 + subjectUri: 'at://did:plc:example/app.bsky.feed.post/3k', 12 + threshold: 1000, 13 + firedAt, 14 + }) 15 + 16 + const row = await NotifiedThreshold.query() 17 + .where('subject_uri', 'at://did:plc:example/app.bsky.feed.post/3k') 18 + .where('threshold', 1000) 19 + .first() 20 + 21 + assert.isNotNull(row) 22 + assert.equal(row!.subjectUri, 'at://did:plc:example/app.bsky.feed.post/3k') 23 + assert.equal(row!.threshold, 1000) 24 + assert.equal(row!.firedAt, firedAt) 25 + }) 26 + 27 + test('allows multiple thresholds per subject', async ({ assert }) => { 28 + const subjectUri = 'at://did:plc:example/app.bsky.feed.post/multi' 29 + await NotifiedThreshold.create({ subjectUri, threshold: 1000, firedAt: Date.now() }) 30 + await NotifiedThreshold.create({ subjectUri, threshold: 10_000, firedAt: Date.now() }) 31 + 32 + const rows = await NotifiedThreshold.query().where('subject_uri', subjectUri) 33 + assert.lengthOf(rows, 2) 34 + const thresholds = rows.map((r) => r.threshold).sort((a, b) => a - b) 35 + assert.deepEqual(thresholds, [1000, 10_000]) 36 + }) 37 + 38 + test('composite primary key prevents duplicate (subject, threshold)', async ({ assert }) => { 39 + const subjectUri = 'at://did:plc:example/app.bsky.feed.post/dup' 40 + await NotifiedThreshold.create({ subjectUri, threshold: 1000, firedAt: Date.now() }) 41 + 42 + await assert.rejects(async () => { 43 + await NotifiedThreshold.create({ subjectUri, threshold: 1000, firedAt: Date.now() }) 44 + }) 45 + }) 46 + })