Something went wrong. Try again.
mirror your GitHub repos to tangled.org automatically synchub.to
mirror sync tangled github git
Something went wrong. Try again.
7.0 kB · 150 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151import { sql } from 'drizzle-orm'import { bigint, bigserial, check, customType, index, integer, jsonb, pgTable, text, timestamp, unique, uuid,} from 'drizzle-orm/pg-core'
const bytea = customType<{ data: Buffer, notNull: false, default: false }>({ dataType() { return 'bytea' },})
export const installation = pgTable('installation', { id: bigint('id', { mode: 'number' }).primaryKey(), // Cache of GitHub's account.login at last webhook; can drift if the user/org // is renamed. Refresh on every webhook. Never use as a join key — join on // accountId, which is stable. accountLogin: text('account_login').notNull(), accountId: bigint('account_id', { mode: 'number' }).notNull(), accountType: text('account_type').notNull(), // Set when GitHub fires installation.suspend; cleared on installation.unsuspend. // The worker refuses to process jobs for suspended installs. suspendedAt: timestamp('suspended_at', { withTimezone: true }), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),}, table => [ check('installation_account_type_chk', sql`${table.accountType} in ('User','Organization')`),])
export const userIdentity = pgTable('user_identity', { did: text('did').primaryKey(), handle: text('handle'), installationId: bigint('installation_id', { mode: 'number' }) .references(() => installation.id, { onDelete: 'cascade' }), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }),}, table => [ index('user_identity_installation_idx').on(table.installationId),])
export const sshKey = pgTable('ssh_key', { id: bigserial('id', { mode: 'number' }).primaryKey(), installationId: bigint('installation_id', { mode: 'number' }).notNull() .references(() => installation.id, { onDelete: 'cascade' }), did: text('did').notNull(), publicKey: text('public_key').notNull(), privateKeyCiphertext: bytea('private_key_ciphertext').notNull(), privateKeyNonce: bytea('private_key_nonce').notNull(), tangledKeyRkey: text('tangled_key_rkey'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), rotatedAt: timestamp('rotated_at', { withTimezone: true }),}, table => [ unique('ssh_key_installation_did_unq').on(table.installationId, table.did), index('ssh_key_installation_idx').on(table.installationId),])
export const repoMapping = pgTable('repo_mapping', { id: bigserial('id', { mode: 'number' }).primaryKey(), installationId: bigint('installation_id', { mode: 'number' }).notNull() .references(() => installation.id, { onDelete: 'cascade' }), githubRepoId: bigint('github_repo_id', { mode: 'number' }).notNull(), // Cache of `<owner>/<name>` at last webhook; renames update this. Display only; // never join on it. Stable identity is githubRepoId. githubFullName: text('github_full_name').notNull(), tangledRepoDid: text('tangled_repo_did'), tangledFullName: text('tangled_full_name'), knot: text('knot'), primarySide: text('primary_side').notNull().default('github'), // Loop-avoidance state: { 'refs/heads/main': '<sha>', ... }. Updated after a // successful push to the replica side. See PLAN.md "Loop avoidance". lastSyncedRefs: jsonb('last_synced_refs').notNull().default(sql`'{}'::jsonb`), status: text('status').notNull().default('pending'), lastError: text('last_error'), // User-initiated stop. Worker skips jobs for repos with disabledAt set. // Preserves the row (and its status) so we can resume on re-enable. disabledAt: timestamp('disabled_at', { withTimezone: true }), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }),}, table => [ unique('repo_mapping_installation_repo_unq').on(table.installationId, table.githubRepoId), index('repo_mapping_installation_idx').on(table.installationId), check('repo_mapping_primary_side_chk', sql`${table.primarySide} in ('github','tangled')`), check('repo_mapping_status_chk', sql`${table.status} in ('pending','enrolling','active','error')`),])
// Hand-rolled queue. Worker claim pattern: SELECT ... FOR UPDATE SKIP LOCKED.//// IMPORTANT: payload is a *minimal envelope* like// { deliveryId, installationId, githubRepoId, ref, before, after, kind }.// Do NOT store full webhook bodies here — they contain unbounded user-controlled// input (commit messages, descriptions, branch names). Re-fetch from the GitHub// API at job run time using the installation token; that gives us fresh data on// retry too.export const job = pgTable('job', { id: bigserial('id', { mode: 'number' }).primaryKey(), kind: text('kind').notNull(), payload: jsonb('payload').notNull(), attempts: integer('attempts').notNull().default(0), runAfter: timestamp('run_after', { withTimezone: true }).defaultNow().notNull(), lockedBy: text('locked_by'), lockedUntil: timestamp('locked_until', { withTimezone: true }), status: text('status').notNull().default('queued'), lastError: text('last_error'), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }),}, table => [ index('job_status_run_after_idx').on(table.status, table.runAfter), check('job_status_chk', sql`${table.status} in ('queued','running','done','failed')`),])
// Idempotency + audit trail for incoming webhooks. Keyed by X-GitHub-Delivery so// redeliveries are free-on-conflict. Intentionally has no `payload` column — see// the note on `job` above; we do not persist webhook bodies.export const webhookEvent = pgTable('webhook_event', { deliveryId: uuid('delivery_id').primaryKey(), source: text('source').notNull(), event: text('event').notNull(), receivedAt: timestamp('received_at', { withTimezone: true }).defaultNow().notNull(), processedAt: timestamp('processed_at', { withTimezone: true }),}, table => [ check('webhook_event_source_chk', sql`${table.source} in ('github','tangled')`),])
// AT Protocol OAuth stores. The values are encrypted at rest because they// contain access tokens, refresh tokens, and the DPoP private key for the// user's PDS. The encryption layer wraps the OAuth library's store interface// (encrypt in set, decrypt in get).export const atprotoState = pgTable('atproto_state', { key: text('key').primaryKey(), valueCiphertext: bytea('value_ciphertext').notNull(), valueNonce: bytea('value_nonce').notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),})
export const atprotoSession = pgTable('atproto_session', { sub: text('sub').primaryKey(), valueCiphertext: bytea('value_ciphertext').notNull(), valueNonce: bytea('value_nonce').notNull(), createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true }),})