Something went wrong. Try again.
Automations and webhooks for the AT Protocol airglow.run
automation webhook atproto atprotocol
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798// The sync page state, shared by the settings route (initial render) and the// API (every response), so the island always receives one shape.
import { config } from "../config.js";import type { FollowTarget, SyncOutcome, SyncSource } from "../db/schema.js";import { followCounts, type FollowCount } from "./follow-counts.js";import { findRedundantAutomations, type RedundantAutomation } from "./redundant-automations.js";import { backfillProgress, getSet, listActivity, pendingQueueCount, waitingCounts, type ActivityRow,} from "./store.js";import { MIN_SYNC_APPS, appName, syncableApps } from "./targets.js";
export const ACTIVITY_PAGE_SIZE = 50;
export type SyncActivityItem = { id: number; app: FollowTarget | null; outcome: SyncOutcome; reason: string | null; source: SyncSource | null; subject: string | null; createdAt: number;};
export type SyncState = { enabled: boolean; paused: boolean; apps: FollowTarget[]; syncableApps: Array<{ key: FollowTarget; name: string }>; minApps: number; hasFullScope: boolean; followCounts: Partial<Record<FollowTarget, FollowCount>>; /** Null total while the backfill is still listing follows. */ progress: { total: number | null; completed: number } | null; waiting: Partial<Record<FollowTarget, number>>; pending: number; activity: SyncActivityItem[]; activityCursor: number | null; redundantAutomations: RedundantAutomation[];};
export function hasFullScope(scope: string | null | undefined): boolean { return Boolean(scope?.split(/\s+/).includes("transition:generic"));}
export function toActivityItem(row: ActivityRow): SyncActivityItem { return { id: row.id, app: row.app, outcome: row.outcome, reason: row.reason, source: row.source, subject: row.subject, createdAt: row.createdAt.getTime(), };}
export async function buildSyncState(user: { did: string; scope: string | null;}): Promise<SyncState> { const syncable = syncableApps(config.nsidAllowlist, config.nsidBlocklist); const set = await getSet(user.did); const enabled = set?.enabled ?? false; const apps = set?.apps.filter((app) => syncable.includes(app)) ?? []; // Before enabling, redundancy is judged against every syncable app so the // page can list candidates as the user ticks boxes. const redundancyApps = enabled ? apps : syncable; const [counts, redundant, page, progress, waiting, pending] = await Promise.all([ followCounts(user.did, syncable), findRedundantAutomations(user.did, redundancyApps), listActivity(user.did, { limit: ACTIVITY_PAGE_SIZE }), set && enabled ? backfillProgress(set) : null, waitingCounts(user.did), pendingQueueCount(user.did), ]); return { enabled, paused: Boolean(set?.pausedAt), apps, syncableApps: syncable.map((key) => ({ key, name: appName(key) })), minApps: MIN_SYNC_APPS, hasFullScope: hasFullScope(user.scope), followCounts: counts, progress, waiting, pending, activity: page.rows.map(toActivityItem), activityCursor: page.nextCursor, redundantAutomations: redundant, };}