feat: Phase 6 - MCP + Webhooks + Worker + Polish
- MCP server: stateless JSON-RPC 2.0 with 18 tools (tasks, habits, projects, notes, domains, search, activity) - Webhooks API: CRUD routes under /api/domains/[domainId]/webhooks/ with test endpoint and deliveries log - Webhook delivery: HMAC-SHA256 signed POST with retry (exponential backoff, max 6) - Worker rewrite: Drizzle ORM instead of PocketBase, polls jobs table, handles webhook_delivery, recurring_spawn, ai_dispatch - Rate limiting: token bucket per IP/API key (100 req/min REST, 300 req/min MCP) - Keyboard help overlay: ? opens Radix Dialog with search/filter, Esc closes - AI @mention stub: @agent in command palette dispatches CustomEvent - Mobile responsive: bottom nav, single-column kanban, day view calendar, 44px touch targets - Accessibility: skip-to-content link, focus rings, aria-labels, color contrast - E2E tests: mcp.spec.ts, webhooks.spec.ts, realtime.spec.ts added - Schema: api_keys and webhook_deliveries tables with migration - Removed old PocketBase-style database.ts from worker
This commit is contained in:
@@ -479,3 +479,51 @@ export const webhooks = pgTable(
|
||||
index('webhooks_active_idx').on(table.active),
|
||||
]
|
||||
);
|
||||
|
||||
// ── Webhook Deliveries ──────────────────────────────────────────────────────────
|
||||
|
||||
export const webhookDeliveries = pgTable(
|
||||
'webhook_deliveries',
|
||||
{
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
webhookId: uuid('webhook_id')
|
||||
.notNull()
|
||||
.references((): any => webhooks.id, { onDelete: 'cascade' }),
|
||||
event: text('event').notNull(),
|
||||
payload: jsonb('payload').$type<Record<string, unknown>>().default({}),
|
||||
status: text('status').notNull().default('pending'),
|
||||
statusCode: integer('status_code').default(0),
|
||||
responseBody: text('response_body'),
|
||||
attempts: integer('attempts').default(0),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index('webhook_deliveries_webhook_id_idx').on(table.webhookId),
|
||||
index('webhook_deliveries_status_idx').on(table.status),
|
||||
index('webhook_deliveries_created_at_idx').on(table.createdAt),
|
||||
]
|
||||
);
|
||||
|
||||
// ── API Keys ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
export const apiKeys = pgTable(
|
||||
'api_keys',
|
||||
{
|
||||
id: uuid('id').defaultRandom().primaryKey(),
|
||||
userId: uuid('user_id')
|
||||
.notNull()
|
||||
.references((): any => users.id, { onDelete: 'cascade' }),
|
||||
name: text('name').notNull(),
|
||||
keyHash: text('key_hash').notNull(),
|
||||
keyPrefix: text('key_prefix').notNull(),
|
||||
active: boolean('active').default(true),
|
||||
lastUsedAt: timestamp('last_used_at', { withTimezone: true }),
|
||||
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index('api_keys_user_id_idx').on(table.userId),
|
||||
index('api_keys_key_hash_idx').on(table.keyHash),
|
||||
index('api_keys_active_idx').on(table.active),
|
||||
]
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user