WIP! A BB-style forum, on the ATmosphere! We're still working... we'll be back soon when we have something to show off!
node typescript hono htmx atproto
4
fork

Configure Feed

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

feat(appview): index space.atbb.forum.theme and themePolicy from firehose (ATB-55)

Malpercio 6993441e 2d42f845

+111
+12
apps/appview/src/lib/firehose.ts
··· 141 141 onCreate: this.createWrappedHandler("handleReactionCreate"), 142 142 onUpdate: this.createWrappedHandler("handleReactionUpdate"), 143 143 onDelete: this.createWrappedHandler("handleReactionDelete"), 144 + }) 145 + .register({ 146 + collection: "space.atbb.forum.theme", 147 + onCreate: this.createWrappedHandler("handleThemeCreate"), 148 + onUpdate: this.createWrappedHandler("handleThemeUpdate"), 149 + onDelete: this.createWrappedHandler("handleThemeDelete"), 150 + }) 151 + .register({ 152 + collection: "space.atbb.forum.themePolicy", 153 + onCreate: this.createWrappedHandler("handleThemePolicyCreate"), 154 + onUpdate: this.createWrappedHandler("handleThemePolicyUpdate"), 155 + onDelete: this.createWrappedHandler("handleThemePolicyDelete"), 144 156 }); 145 157 } 146 158
+99
apps/appview/src/lib/indexer.ts
··· 15 15 modActions, 16 16 roles, 17 17 rolePermissions, 18 + themes, 19 + themePolicies, 20 + themePolicyAvailableThemes, 18 21 } from "@atbb/db"; 19 22 import { eq, and } from "drizzle-orm"; 20 23 import { parseAtUri } from "./at-uri.js"; ··· 27 30 SpaceAtbbMembership as Membership, 28 31 SpaceAtbbModAction as ModAction, 29 32 SpaceAtbbForumRole as Role, 33 + SpaceAtbbForumTheme as Theme, 34 + SpaceAtbbForumThemePolicy as ThemePolicy, 30 35 } from "@atbb/lexicon"; 31 36 32 37 // ── Collection Config Types ───────────────────────────── ··· 354 359 }, 355 360 }; 356 361 362 + private themeConfig: CollectionConfig<Theme.Record> = { 363 + name: "Theme", 364 + table: themes, 365 + deleteStrategy: "hard", 366 + toInsertValues: async (event, record) => ({ 367 + did: event.did, 368 + rkey: event.commit.rkey, 369 + cid: event.commit.cid, 370 + name: record.name, 371 + colorScheme: record.colorScheme as string, 372 + tokens: record.tokens, 373 + cssOverrides: (record.cssOverrides as string | undefined) ?? null, 374 + fontUrls: (record.fontUrls as string[] | undefined) ?? null, 375 + createdAt: new Date(record.createdAt as string), 376 + indexedAt: new Date(), 377 + }), 378 + toUpdateValues: async (event, record) => ({ 379 + cid: event.commit.cid, 380 + name: record.name, 381 + colorScheme: record.colorScheme as string, 382 + tokens: record.tokens, 383 + cssOverrides: (record.cssOverrides as string | undefined) ?? null, 384 + fontUrls: (record.fontUrls as string[] | undefined) ?? null, 385 + indexedAt: new Date(), 386 + }), 387 + }; 388 + 389 + private themePolicyConfig: CollectionConfig<ThemePolicy.Record> = { 390 + name: "ThemePolicy", 391 + table: themePolicies, 392 + deleteStrategy: "hard", 393 + toInsertValues: async (event, record) => ({ 394 + did: event.did, 395 + rkey: event.commit.rkey, 396 + cid: event.commit.cid, 397 + defaultLightThemeUri: record.defaultLightTheme.theme.uri, 398 + defaultDarkThemeUri: record.defaultDarkTheme.theme.uri, 399 + allowUserChoice: record.allowUserChoice, 400 + indexedAt: new Date(), 401 + }), 402 + toUpdateValues: async (event, record) => ({ 403 + cid: event.commit.cid, 404 + defaultLightThemeUri: record.defaultLightTheme.theme.uri, 405 + defaultDarkThemeUri: record.defaultDarkTheme.theme.uri, 406 + allowUserChoice: record.allowUserChoice, 407 + indexedAt: new Date(), 408 + }), 409 + afterUpsert: async (_event, record, policyId, tx) => { 410 + // Atomically replace all available-theme rows for this policy 411 + await tx 412 + .delete(themePolicyAvailableThemes) 413 + .where(eq(themePolicyAvailableThemes.policyId, policyId)); 414 + 415 + const available = record.availableThemes ?? []; 416 + if (available.length > 0) { 417 + await tx.insert(themePolicyAvailableThemes).values( 418 + available.map((themeRef) => ({ 419 + policyId, 420 + themeUri: themeRef.theme.uri, 421 + themeCid: themeRef.theme.cid, 422 + })) 423 + ); 424 + } 425 + }, 426 + }; 427 + 357 428 private membershipConfig: CollectionConfig<Membership.Record> = { 358 429 name: "Membership", 359 430 table: memberships, ··· 739 810 740 811 async handleRoleDelete(event: CommitDeleteEvent<"space.atbb.forum.role">) { 741 812 await this.genericDelete(this.roleConfig, event); 813 + } 814 + 815 + // ── Theme Handlers ────────────────────────────────────── 816 + 817 + async handleThemeCreate(event: CommitCreateEvent<"space.atbb.forum.theme">) { 818 + await this.genericCreate(this.themeConfig, event); 819 + } 820 + 821 + async handleThemeUpdate(event: CommitUpdateEvent<"space.atbb.forum.theme">) { 822 + await this.genericUpdate(this.themeConfig, event); 823 + } 824 + 825 + async handleThemeDelete(event: CommitDeleteEvent<"space.atbb.forum.theme">) { 826 + await this.genericDelete(this.themeConfig, event); 827 + } 828 + 829 + // ── ThemePolicy Handlers ───────────────────────────────── 830 + 831 + async handleThemePolicyCreate(event: CommitCreateEvent<"space.atbb.forum.themePolicy">) { 832 + await this.genericCreate(this.themePolicyConfig, event); 833 + } 834 + 835 + async handleThemePolicyUpdate(event: CommitUpdateEvent<"space.atbb.forum.themePolicy">) { 836 + await this.genericUpdate(this.themePolicyConfig, event); 837 + } 838 + 839 + async handleThemePolicyDelete(event: CommitDeleteEvent<"space.atbb.forum.themePolicy">) { 840 + await this.genericDelete(this.themePolicyConfig, event); 742 841 } 743 842 744 843 // ── Membership Handlers ─────────────────────────────────