🔨 feat(db): add push_tokens table + tasks.editor_data column#15186
🔨 feat(db): add push_tokens table + tasks.editor_data column#15186sudongyuer wants to merge 2 commits into
Conversation
|
Deployment failed with the following error: View Documentation: https://vercel.com/docs/accounts/team-members-and-roles |
There was a problem hiding this comment.
Sorry @sudongyuer, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 993113ee38
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (outcomes.every((o) => o === undefined)) continue; | ||
|
|
||
| const errors = outcomes.filter((o) => o?.status === 'error'); | ||
|
|
||
| if (errors.length === 0) { | ||
| updateTasks.push( |
There was a problem hiding this comment.
Wait for all ticket receipts before finalizing delivery
This logic can mark a delivery as delivered when Expo has only returned receipts for a subset of tickets: if at least one outcome is ok and the rest are still missing, errors.length === 0 triggers a final delivered update. In that case, later error receipts (for still-pending tickets) will never be reconciled because the row is no longer sent, which can hide failed pushes and skip invalid-token cleanup. This occurs when getPushNotificationReceiptsAsync returns partial results for a multi-token delivery.
Useful? React with 👍 / 👎.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## canary #15186 +/- ##
=======================================
Coverage 70.89% 70.90%
=======================================
Files 3144 3145 +1
Lines 312964 313006 +42
Branches 33148 33149 +1
=======================================
+ Hits 221881 221923 +42
Misses 90917 90917
Partials 166 166
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Introduce the persistence layer for mobile push delivery (LOBE-8771): - `push_tokens` table keyed by `(user_id, device_id)` with cascade delete and indexes for user / last-seen lookups. Lets one account register multiple devices independently. - `PushTokenModel` with idempotent upsert (`onConflictDoUpdate`), scoped `unregister`, and a static `deletePushTokensByExpoTokens` helper for the receipt-cleanup worker. - `notification_deliveries.channel` widened to `'email' | 'inbox' | 'push'`. The send-side machinery (`PushChannel`, `processPushReceipts`, tRPC routers, dev test endpoint) lives in a companion PR that stacks on this. Tests: 10 model tests covering upsert conflict, multi-device, cascade delete, scoped unregister, and `deletePushTokensByExpoTokens`. Linear: https://linear.app/lobehub/issue/LOBE-8771 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
993113e to
5d36ccd
Compare
Adds the `editor_data jsonb` column on the `tasks` table so the Task instruction editor can persist its Lexical state — image sizes, custom nodes, and other rich attributes that markdown drops. `task_comments.editor_data` already exists on canary; only `tasks` needed this column. Required by LOBE-8967. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…eId) This reverts commit addf14c (device-only unique index). The device-only index conflicts with #15186's pushToken upsert, whose onConflict target is (userId, deviceId). Restore the composite unique index so the upsert lands consistently with both PRs. Also re-point 0105 snapshot prevId to the restored 0104 id and carry the (userId, deviceId) index forward so the migration chain stays consistent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… document shares migrations (#15280) * 🔨 feat(db): batch topic usage stats, push tokens, tasks editor_data & document shares Bundle four independent schema changes onto one migration branch: - 0104 topics: add usage/cost aggregate columns (total_cost, token totals, cost/usage jsonb, model, provider) + model/provider indexes - 0105 push_tokens: new table for Expo push notification tokens - 0106 tasks: add editor_data jsonb column - 0107 document_shares: new table for document share flow Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * 🔨 chore(db): combine batch schema changes into a single migration Squash the four sequential migrations (0104-0107) into one 0104 SQL file containing all DDL: topic usage/cost columns, push_tokens table, tasks.editor_data column, and document_shares table. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * 🔨 chore(db): make push_tokens unique constraint device-only Drop the userId prefix from the push_tokens unique index — one row per device, reassigned to the new user on switch (upsert by deviceId). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ✨ feat(db): add user_connectors and user_connector_tools schema Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ✨ feat(db): add user_connectors and user_connector_tools schema Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ♻️ refactor(db): merge connectorTool schema into connector.ts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * ⏪ revert(db): restore push_tokens unique constraint to (userId, deviceId) This reverts commit addf14c (device-only unique index). The device-only index conflicts with #15186's pushToken upsert, whose onConflict target is (userId, deviceId). Restore the composite unique index so the upsert lands consistently with both PRs. Also re-point 0105 snapshot prevId to the restored 0104 id and carry the (userId, deviceId) index forward so the migration chain stays consistent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ✨ feat(db): add devices table and consolidate batch migration into 0104 Add the `devices` identity anchor (surrogate uuid PK + unique(userId, deviceId)) as the stable, reinstall-proof base for binding agent runtime instances per machine. Fold the prior 0104/0105 migrations and the new table into a single idempotent 0104 migration. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ✅ test(db): add topic usage/cost columns to topic.create assertions The batch added 8 nullable topic columns (totalCost/usage/model/...) but topic.create.test.ts still asserted the pre-batch 19-field shape via toEqual. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * ♻️ refactor(db): use uuid primary key for document_shares Align document_shares.id with the other new batch tables (uuid defaultRandom); table has no consumers yet so no compat impact. Regenerated 0104 + snapshot. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: ONLY-yours <1349021570@qq.com>
Send-side machinery for mobile push notifications (LOBE-8771), stacked on top of the schema PR (#15186). ### tRPC - `pushToken.register` / `pushToken.unregister` exposed on both `MobileRouter` and `LambdaRouter`. ### `PushChannel` - Structurally compatible with cloud's `NotificationChannel` so cloud can register it without casts. - Fans a single notification out to all of a user's tokens, chunks via `expo-server-sdk`, respects the 600 msg/sec project limit with 100ms throttle between chunks. - Embeds `(ticketId, expoToken)` pairs in `providerMessageId` for receipt reconciliation. - Returns `no_tokens` / `invalid_tokens` / `rate_limited` / `all_send_failed` so callers can distinguish. ### `processPushReceipts` - Pure helper to be called by cloud's Vercel cron (companion PR). - Polls Expo receipts in parallel (`Promise.all` across chunks), updates `notification_deliveries` in bulk, prunes `push_tokens` rows flagged `DeviceNotRegistered`. - Configurable lookback window + min-age guard (default: 24h / 15min). ### Dev tooling - `/api/dev/test-push` (404s in production) lets you fire a real push directly to a user's registered tokens, bypassing `NotificationService`. Useful for end-to-end verification before cloud submodule sync. ### Types - `NotificationSettings` gains an optional `push` channel. Tests: 21 added (router 7, PushChannel 7, processPushReceipts 7). Linear: https://linear.app/lobehub/issue/LOBE-8771 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Send-side machinery for mobile push notifications (LOBE-8771), stacked on top of the schema PR (#15186). ### tRPC - `pushToken.register` / `pushToken.unregister` exposed on both `MobileRouter` and `LambdaRouter`. ### `PushChannel` - Structurally compatible with cloud's `NotificationChannel` so cloud can register it without casts. - Fans a single notification out to all of a user's tokens, chunks via `expo-server-sdk`, respects the 600 msg/sec project limit with 100ms throttle between chunks. - Embeds `(ticketId, expoToken)` pairs in `providerMessageId` for receipt reconciliation. - Returns `no_tokens` / `invalid_tokens` / `rate_limited` / `all_send_failed` so callers can distinguish. ### `processPushReceipts` - Pure helper to be called by cloud's Vercel cron (companion PR). - Polls Expo receipts in parallel (`Promise.all` across chunks), updates `notification_deliveries` in bulk, prunes `push_tokens` rows flagged `DeviceNotRegistered`. - Configurable lookback window + min-age guard (default: 24h / 15min). ### Dev tooling - `/api/dev/test-push` (404s in production) lets you fire a real push directly to a user's registered tokens, bypassing `NotificationService`. Useful for end-to-end verification before cloud submodule sync. ### Types - `NotificationSettings` gains an optional `push` channel. Tests: 21 added (router 7, PushChannel 7, processPushReceipts 7). Linear: https://linear.app/lobehub/issue/LOBE-8771 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Send-side machinery for mobile push notifications (LOBE-8771), stacked on top of the schema PR (#15186). ### tRPC - `pushToken.register` / `pushToken.unregister` exposed on both `MobileRouter` and `LambdaRouter`. ### `PushChannel` - Structurally compatible with cloud's `NotificationChannel` so cloud can register it without casts. - Fans a single notification out to all of a user's tokens, chunks via `expo-server-sdk`, respects the 600 msg/sec project limit with 100ms throttle between chunks. - Embeds `(ticketId, expoToken)` pairs in `providerMessageId` for receipt reconciliation. - Returns `no_tokens` / `invalid_tokens` / `rate_limited` / `all_send_failed` so callers can distinguish. ### `processPushReceipts` - Pure helper to be called by cloud's Vercel cron (companion PR). - Polls Expo receipts in parallel (`Promise.all` across chunks), updates `notification_deliveries` in bulk, prunes `push_tokens` rows flagged `DeviceNotRegistered`. - Configurable lookback window + min-age guard (default: 24h / 15min). ### Dev tooling - `/api/dev/test-push` (404s in production) lets you fire a real push directly to a user's registered tokens, bypassing `NotificationService`. Useful for end-to-end verification before cloud submodule sync. ### Types - `NotificationSettings` gains an optional `push` channel. Tests: 21 added (router 7, PushChannel 7, processPushReceipts 7). Linear: https://linear.app/lobehub/issue/LOBE-8771 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Send-side machinery for mobile push notifications (LOBE-8771), stacked on top of the schema PR (#15186). ### tRPC - `pushToken.register` / `pushToken.unregister` exposed on both `MobileRouter` and `LambdaRouter`. ### `PushChannel` - Structurally compatible with cloud's `NotificationChannel` so cloud can register it without casts. - Fans a single notification out to all of a user's tokens, chunks via `expo-server-sdk`, respects the 600 msg/sec project limit with 100ms throttle between chunks. - Embeds `(ticketId, expoToken)` pairs in `providerMessageId` for receipt reconciliation. - Returns `no_tokens` / `invalid_tokens` / `rate_limited` / `all_send_failed` so callers can distinguish. ### `processPushReceipts` - Pure helper to be called by cloud's Vercel cron (companion PR). - Polls Expo receipts in parallel (`Promise.all` across chunks), updates `notification_deliveries` in bulk, prunes `push_tokens` rows flagged `DeviceNotRegistered`. - Configurable lookback window + min-age guard (default: 24h / 15min). ### Dev tooling - `/api/dev/test-push` (404s in production) lets you fire a real push directly to a user's registered tokens, bypassing `NotificationService`. Useful for end-to-end verification before cloud submodule sync. ### Types - `NotificationSettings` gains an optional `push` channel. Tests: 21 added (router 7, PushChannel 7, processPushReceipts 7). Linear: https://linear.app/lobehub/issue/LOBE-8771 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…15233) Send-side machinery for mobile push notifications (LOBE-8771), stacked on top of the schema PR (#15186). ### tRPC - `pushToken.register` / `pushToken.unregister` exposed on both `MobileRouter` and `LambdaRouter`. ### `PushChannel` - Structurally compatible with cloud's `NotificationChannel` so cloud can register it without casts. - Fans a single notification out to all of a user's tokens, chunks via `expo-server-sdk`, respects the 600 msg/sec project limit with 100ms throttle between chunks. - Embeds `(ticketId, expoToken)` pairs in `providerMessageId` for receipt reconciliation. - Returns `no_tokens` / `invalid_tokens` / `rate_limited` / `all_send_failed` so callers can distinguish. ### `processPushReceipts` - Pure helper to be called by cloud's Vercel cron (companion PR). - Polls Expo receipts in parallel (`Promise.all` across chunks), updates `notification_deliveries` in bulk, prunes `push_tokens` rows flagged `DeviceNotRegistered`. - Configurable lookback window + min-age guard (default: 24h / 15min). ### Dev tooling - `/api/dev/test-push` (404s in production) lets you fire a real push directly to a user's registered tokens, bypassing `NotificationService`. Useful for end-to-end verification before cloud submodule sync. ### Types - `NotificationSettings` gains an optional `push` channel. Tests: 21 added (router 7, PushChannel 7, processPushReceipts 7). Linear: https://linear.app/lobehub/issue/LOBE-8771 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Two independent DB schema additions bundled together since both ship as schema-only changes that need to be cherry-picked into the next
release/db-migration-*window:push_tokenstable — data layer + mobile-facing API + send-side machinery for LobeHub Mobile remote push notifications via Expo Push Service. (LOBE-8771)tasks.editor_datacolumn —jsonbcolumn letting the Task instruction editor persist Lexical state (image sizes, custom nodes, etc.). Required by LOBE-8967 task attachment / image upload support (companion feature PR ✨ feat(task): support file & image attachments #15141).Both are additive, nullable / new-table-only, and backwards compatible.
Linear: LOBE-8771, LOBE-8967
Sub-issue: LOBE-9138 — Server 端实现
Companion PRs:
tasks.editor_datacolumn from this PR)Migrations
task_comments.editor_dataalready exists on canary (added in 0095); onlytasksneeded the column.What's included
push_tokens— data layerpush_tokenstable keyed by(user_id, device_id)so a single account can register multiple devices independently. Cascade-deletes with the user.PushTokenModelwith idempotent upsert (onConflictDoUpdate), scopedunregister, and a staticdeletePushTokensByExpoTokenshelper for the receipt-cleanup worker.notification_deliveries.channelwidened to include'push'.NotificationSettingstype gains an optionalpushchannel.push_tokens— tRPCpushToken.register/pushToken.unregisterexposed on bothMobileRouterandLambdaRouter.push_tokens— Send side (PushChannel)NotificationChannel, so cloud can register it without casts.expo-server-sdk, respects the 600 msg/sec project limit with 100ms throttle between chunks.(ticketId, expoToken)pairs inproviderMessageIdfor receipt reconciliation. (TODO marker left for promoting this to a dedicatedpush_ticketstable if cross-delivery joins are ever needed.)push_tokens— Receipt reconciliation (processPushReceipts)Promise.allacross chunks), updatesnotification_deliveriesin bulk, prunespush_tokensrows flaggedDeviceNotRegistered.push_tokens— Dev tooling/api/dev/test-push(development-only — 404s in production) lets you fire a real push directly to a user's registered tokens, bypassingNotificationService. Useful for end-to-end verification before cloud submodule sync.tasks.editor_datacolumnjsonbcolumn ontasks.Architecture (push side)
The setup uses opt-in scenarios. By default no scenario sends push — the cloud-side PR explicitly adds
'push'toimage_generation_completedandvideo_generation_completed'schannelsarrays. User preferences (userSettings.notification.push) sit on top, giving users a per-channel and per-type kill switch.Test plan
Push side:
deletePushTokensByExpoTokenshelperPushChanneltests — no_tokens / invalid_tokens / send-time errors / rate_limited / 429 / non-429 rethrowprocessPushReceiptstests — empty pending, all-ok, mixed, error aggregation, no-yet-receipts, malformed JSON, dedupe cross-deliveriespushToken.registerwritespush_tokensrow, dev/test-pushdelivers to locked iPhone via APNstasks.editor_data:bun run db:generateregenerates a clean migration (one-lineALTER TABLE ADD COLUMN)ADD COLUMN IF NOT EXISTS)🤖 Generated with Claude Code