Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 54x 54x 54x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 53x 53x 53x 53x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x 2x | import { createId } from '@paralleldrive/cuid2'
import { index, integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'
import { users } from './users'
/**
* Print-service connections — per-account pairings with a THH print service
* (Abacus Studio Phase 2a, ticket #8).
*
* Each row is one paired connection owned by exactly one user (the tenant key
* for everything downstream: proxy reads, job ownership, doorbell fan-out).
* The bearer token and the webhook ring secret are encrypted at rest with
* `@/lib/secret-box` — the browser-facing PrintConnection shape never carries
* either sealed field.
*/
export const printServiceConnections = sqliteTable(
'print_service_connections',
{
id: text('id')
.primaryKey()
.$defaultFn(() => createId()),
/** Owning account — the tenant boundary for reads, jobs, and doorbell emits */
userId: text('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
/** Human-readable connection name (e.g. "Home X1C") */
name: text('name').notNull(),
/** Print-service origin, e.g. "https://things.haunt.house" (no trailing slash) */
origin: text('origin').notNull(),
/** Durable bearer token from POST /pair, sealed with secret-box */
tokenSealed: text('token_sealed').notNull(),
/** Per-connection webhook ring secret, sealed with secret-box (null until webhook registered) */
ringSecretSealed: text('ring_secret_sealed'),
/** When PUT /webhook last succeeded for this connection (null = no doorbell) */
webhookRegisteredAt: integer('webhook_registered_at', { mode: 'timestamp' }),
createdAt: integer('created_at', { mode: 'timestamp' })
.notNull()
.$defaultFn(() => new Date()),
updatedAt: integer('updated_at', { mode: 'timestamp' })
.notNull()
.$defaultFn(() => new Date()),
},
(table) => ({
/** Roster lookups are always per-owner */
userIdIdx: index('print_service_connections_user_id_idx').on(table.userId),
})
)
/**
* Print-job ownership map. The print service stays the single source of truth
* for job state — this table only records which connection/user a submitted
* job belongs to, so `jobs/*` proxy reads can be authorized and doorbell rings
* resolved to the owning tenant.
*/
export const printJobs = sqliteTable(
'print_jobs',
{
/** Job id as returned by the print service's 202 submit response */
jobId: text('job_id').primaryKey(),
connectionId: text('connection_id')
.notNull()
.references(() => printServiceConnections.id, { onDelete: 'cascade' }),
/** Denormalized owner — matches the connection's userId at submit time */
userId: text('user_id')
.notNull()
.references(() => users.id, { onDelete: 'cascade' }),
/** Printer the job was submitted to (service-side printer id) */
printerId: text('printer_id').notNull(),
createdAt: integer('created_at', { mode: 'timestamp' })
.notNull()
.$defaultFn(() => new Date()),
},
(table) => ({
/** "My active jobs" listings */
userIdIdx: index('print_jobs_user_id_idx').on(table.userId),
connectionIdIdx: index('print_jobs_connection_id_idx').on(table.connectionId),
})
)
/**
* Per-user print-settings overlay for the abacus print dialog.
*
* ONE row per user (not per printer): the `style` column stores the user's
* TicketStyle edits verbatim as JSON — base preset id + sparse process
* overrides — never the service's `applied` clamp echoes. Absent row = unset;
* there is no honest server-side default because the default intent lives in
* each printer's capability doc, and `resolveIntentId` degrades a saved
* basePreset gracefully against whatever doc the paired printer serves.
* Filament overrides and printer profile are device/view state, not part of
* the overlay.
*/
export const abacusPrintSettings = sqliteTable('abacus_print_settings', {
/** Owning account (primary key — one settings row per user) */
userId: text('user_id')
.primaryKey()
.references(() => users.id, { onDelete: 'cascade' }),
/** TicketStyle JSON, stored verbatim (validated by parseTicketStyle on write) */
style: text('style').notNull(),
updatedAt: integer('updated_at', { mode: 'timestamp' })
.notNull()
.$defaultFn(() => new Date()),
})
export type PrintServiceConnection = typeof printServiceConnections.$inferSelect
export type NewPrintServiceConnection = typeof printServiceConnections.$inferInsert
export type PrintJob = typeof printJobs.$inferSelect
export type NewPrintJob = typeof printJobs.$inferInsert
export type AbacusPrintSettings = typeof abacusPrintSettings.$inferSelect
export type NewAbacusPrintSettings = typeof abacusPrintSettings.$inferInsert
|