Skip to content

Migrate entire backend to Convex (drop Prisma / Inngest / Redis / server routes) - #127

Merged
Melvynx merged 50 commits into
mainfrom
feat/convex-migration
Jul 7, 2026
Merged

Migrate entire backend to Convex (drop Prisma / Inngest / Redis / server routes)#127
Melvynx merged 50 commits into
mainfrom
feat/convex-migration

Conversation

@Melvynx

@Melvynx Melvynx commented Jun 9, 2026

Copy link
Copy Markdown
Owner

Summary

Complete migration of SaveIt onto a single Convex backend (packages/backend, @workspace/backend) as the source of truth for data, auth, jobs, AI, billing, email, files and the public API.

Gone entirely: Prisma/Postgres (@workspace/database), Inngest, Upstash Redis, the Cloudflare worker (apps/worker), and all TanStack server routes that held backend logic. Verified: zero @prisma / @workspace/database / inngest / @upstash/redis / next imports across every application.

Backend (packages/backend/convex) — deploys clean ✅

  • Auth: Better Auth on Convex via a local @convex-dev/better-auth component (user-centric; SaveIt custom user fields on the auth user table → userId === BA user id). Plugins: emailOTP (+ App Store test bypass), magicLink, admin, apiKey, expo, crossDomain, convex. Banned-user enforcement + typed ConvexError helpers.
  • Schema: bookmarks (single combined 1536-d searchEmbedding vector index), tags + bookmarkTags, bookmarkOpens, processing runs, chatConversations + chatMessages (child table), chatUsages, subscriptions, userCounters, changelogDismissals.
  • Domain: bookmarks/tags CRUD + plan limits · processing pipeline (Inngest → Convex actions + ctx.scheduler + reactive processingStep, all 7 type handlers, Gemini, Cloudflare Browser Rendering, R2) · vector search (pgvector → ctx.vectorSearch) · AI chat (HTTP streaming action) · Stripe billing + webhook · Resend email + marketing drips + crons · public /api/v1 + extension + tools httpActions · admin/* (adminQuery/Mutation/Action).
  • Jobs: 100% Convex crons + ctx.scheduler. sharp → pure-JS image-size for serverless.

Web (apps/web) ✅ pnpm ts + pnpm lint clean

Full data-layer swap up-fetch/Prisma → Convex (convexQuery / useConvexMutation / convexAction). ConvexBetterAuthProvider + SSR auth via convexBetterAuthReactStart. Deleted ~30 Prisma api.*.ts routes (kept only the api.auth.$ proxy that @convex-dev/better-auth requires). Removed all server-only deps + db:* turbo tasks.

Mobile (apps/mobile) ✅ typechecks clean

Wired to the shared Convex backend with the nowstack-mobile auth pattern (expoClient + SecureStore bearer tokens + crossDomain + convex).

Extensions (chrome/firefox)

Session-cookie auth against Convex + new extension save/upload httpActions.

Notes / follow-ups

  • The remaining createServerFn usages in apps/web are Convex-only SSR loaders (they call fetchAuthQuery → Convex, no backend logic). Can be converted to pure client useQuery if literally-zero server functions is required.
  • Data migration (Postgres → Convex re-embed) is an ops step that needs prod credentials; not run here. Set backend secrets via npx convex env set (Stripe/Gemini/R2/Resend/OAuth) before production cutover.
  • Detailed phase plan + porting specs live in .agents/plan/convex-refactor/.

🤖 Generated with Claude Code

…server routes)

Complete migration of SaveIt onto a single Convex backend
(packages/backend, @workspace/backend) as the source of truth for data,
auth, jobs, AI, billing, email, files and the public API. No more Prisma /
Postgres, Inngest, Upstash Redis, the Cloudflare worker, or TanStack server
routes for backend logic.

Backend (packages/backend/convex) — pushes clean to the dev deployment:
- Better Auth on Convex via a LOCAL @convex-dev/better-auth component
  (user-centric, SaveIt custom user fields on the auth `user` table so
  userId === BA user id). Plugins: emailOTP (+ App Store test bypass),
  magicLink, admin, apiKey (@better-auth/api-key), expo, crossDomain, convex.
  Banned-user enforcement + typed ConvexError helpers (utils/errors).
- Domain schema: bookmarks (single combined 1536-d searchEmbedding vector
  index), tags + bookmarkTags join, bookmarkOpens, processingRuns,
  chatConversations + chatMessages (child table), chatUsages, subscriptions,
  userCounters, changelogDismissals.
- Bookmarks/tags CRUD + plan limits; processing pipeline (Inngest -> Convex
  actions + scheduler + reactive processingStep) with all 7 type handlers,
  Gemini summaries/embeddings, Cloudflare Browser Rendering screenshots, R2;
  vector search (pgvector -> ctx.vectorSearch); AI chat (HTTP streaming
  action); Stripe billing + webhook; Resend email + marketing drips +
  crons; public /api/v1 + extension + tools httpActions; admin/* (adminQuery).
- All background jobs run via Convex crons + ctx.scheduler. sharp replaced by
  pure-JS image-size for serverless compatibility.

Web (apps/web): full data-layer swap up-fetch/Prisma -> Convex
(convexQuery / useConvexMutation / convexAction); ConvexBetterAuthProvider;
SSR auth via convexBetterAuthReactStart. Deleted ~30 Prisma api.*.ts routes
(kept only the api.auth.$ proxy that @convex-dev/better-auth requires).
Removed @workspace/database, Prisma, inngest, @upstash/redis, up-fetch and
server-only deps. `pnpm ts` + `pnpm lint` clean.

Mobile (apps/mobile): wired to the shared Convex backend with the
nowstack-mobile auth pattern (expoClient + SecureStore bearer tokens +
crossDomain + convex). Typechecks clean.

Extensions (chrome/firefox): session-cookie auth against Convex + new
extension save/upload httpActions.

Removed: packages/database (Prisma), apps/worker (folded into Convex),
all Inngest/Redis/Prisma imports across every application.
@vercel

vercel Bot commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
saveit-now-web Ready Ready Preview, Comment Jul 7, 2026 7:01am

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 34efd1730c

ℹ️ 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".

Comment thread packages/backend/convex/api/helpers.ts Outdated
Comment on lines +160 to +164
// Check user counters for limit
const counters = await ctx.db
.query("userCounters")
.withIndex("by_user", (q) => q.eq("userId", args.userId))
.first();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Enforce limits before helper creates bookmarks

This helper is now used by both POST /api/v1/bookmarks and the browser-extension /api/bookmarks endpoint, but it only reads userCounters and never calls assertCanCreateBookmark before inserting. In those contexts a free user who has already reached the bookmark or monthly-run limit can keep creating bookmarks through the extension/API path, while the normal bookmarks.mutations.create path still enforces the limit.

Useful? React with 👍 / 👎.

Comment thread packages/backend/convex/api/helpers.ts Outdated
Comment on lines +84 to +88
const result: { page: Array<Record<string, unknown>>; isDone: boolean; continueCursor?: string } = await ctx.runQuery(
internal.bookmarks.queries.listDefault,
{
userId: args.userId,
paginationOpts,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Route public text searches through the search action

When /api/v1/bookmarks?query=... is called without tags, this branch ignores args.query and runs listDefault, so SDK/API clients get an unfiltered bookmark list instead of search results. The previous route passed query to cachedAdvancedSearch, so any public API text search or public-slug search that relies on this helper regresses.

Useful? React with 👍 / 👎.

Comment thread packages/backend/convex/api/v1.ts Outdated
const tagsList = p.tags
? p.tags.split(",").map((t) => t.trim()).filter(Boolean)
: [];
const specialFilters: string[] = p.special ? [p.special.toLowerCase()] : [];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve special filters in uppercase

For requests such as /api/v1/bookmarks?tags=work&special=STAR, this lowercases the value to star before forwarding it to searchByTags, whose validator only accepts READ, UNREAD, or STAR. That makes valid API requests with both tags and special fail validation instead of returning the filtered results.

Useful? React with 👍 / 👎.

- Drop @tanstack/react-query and @convex-dev/react-query; use useQuery
  from convex/react and a new use-async-task helper for mutations/actions
- Simplify Providers to a single ConvexReactClient
- Update backend convex functions, migration scripts, and plan docs
- Fix landing header border to span full viewport width
…xport ordering

- Prisma BookmarkTag has no userId column; resolve it via the bookmark's owner
- Tag and subscription tables have no createdAt; order exports by id
- Document CONVEX_DEPLOY_KEY handling in AGENTS.md
Melvynx added 2 commits June 10, 2026 11:10
- vercel-build.mjs: when CONVEX_DEPLOY_KEY present, run `convex deploy
  --cmd-url-env-var-name VITE_CONVEX_URL --cmd 'node scripts/build-web.mjs'`
  so the deployed Convex URL is injected into the build automatically.
- build-web.mjs: derive VITE_CONVEX_SITE_URL (.site) from VITE_CONVEX_URL.
- migrate: idempotent imports (tags/bookmarks/bookmarkTags dedup) + withRetry
  on transient network errors so a crashed import resumes safely.
- mobile: bump to 3.0.0, add EXPO_PUBLIC_CONVEX_URL/SITE_URL + expo-network.
Lets the branch preview point at an existing deployment (e.g. dev with
migrated data) instead of deploying functions to a fresh empty preview
deployment. Production (no manual VITE_CONVEX_URL) still auto-deploys via
the deploy key.
- importConversations: idempotent (dedup by user+updatedAt+title) and skip
  individual chat messages over the 1 MiB Convex doc limit.
- rebuildUserCounters: take precomputed per-user counts instead of paginating
  inside the mutation (Convex forbids multiple paginated queries / blows read
  limits for power users); counts are computed client-side from the export.
- import-convex.ts: ONLY_STAGES gating + per-row conversation batches
  (CONVERSATION_CHUNK_SIZE) so a failed stage can be re-run in isolation;
  summary uses hoisted counters so skipped stages don't throw.
Melvynx added 5 commits June 10, 2026 20:00
Replace the monolithic pipeline action with a journaled workflow:
named retryable steps (limits, dedupe, route, type handler, finish),
onComplete error/cancel handling with run-ownership guard, workflowId
stored on bookmarks so deletion cancels in-flight runs.

Note: pnpm-lock.yaml also picks up regeneration from earlier
uncommitted dependency changes on this branch.
Browse mode now subscribes via usePaginatedQuery so new PENDING
bookmarks and status flips stream in without refetch plumbing; the
pending card animates processingStep again (parity with the old
Inngest realtime UI). Search action: tags-only requests reach
searchByTags (Route 1 swallowed them); unreachable Route 2 removed.
Better Auth resolves the client session before ConvexBetterAuthProvider
installs the JWT; authQueries fired in that window threw UNAUTHORIZED
into the error boundary and crashed /app. New useAuthedQuery hook skips
until useConvexAuth is ready; all 16 authed call sites migrated.

Also: error card reads processingError (now in BookmarkDTO) instead of
crashing on metadata.error when metadata is absent.
URLs pasted from prose (e.g. "…/page:") 404ed on fetch and produced
empty READY bookmarks. cleanUrl now strips trailing punctuation and
unbalanced closing brackets (Wikipedia-style "_(theory)" preserved).
…ence)

Thumbfast-style rule: dev-browser sign-in via OTP read from the Better
Auth verification table, plus processing-evidence checks. AGENTS.md and
CLAUDE.md also pick up the branch's pending Prisma-to-Convex doc updates.
convex codegen requires CONVEX_DEPLOYMENT, which CI doesn't have.
Run convex dev --once in anonymous local mode instead, then diff
convex/_generated to keep the consistency check without secrets.
@cursor

cursor Bot commented Jul 5, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

Consolidate the DNS-resolving SSRF guard (previously local to
files/actions.ts) into a shared lib/safe_fetch.ts and route the
bookmark-processing fetches (steps analyzeUrl/fetchHtml, image + PDF
downloads, storage.uploadFromURL) through it. Close the DNS gap on the
public V8-runtime tool endpoints via a Node internalAction
(tools/node_fetch.ts) called from fetchHtml/validateFavicon.
@cursor

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

The chat HTTP action only validated Better Auth session cookies, which
never reach convex.site cross-origin, so every /chat request returned
401 and the agent never replied. Resolve the user from the Convex JWT
(safeGetAuthUser) first, keeping the cookie session as fallback.
@cursor

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

…re compliance

- Native Sign in with Apple (expo-apple-authentication -> Better Auth idToken), iOS entitlement + plugin
- RevenueCat paywall (purchase/restore, backend-confirmed activation), iOS upgrade path off web checkout
- Convex /revenuecat/webhook with shared secret, replay guard, Stripe-ownership protection, TRANSFER revocation
- subscriptions schema: provider, revenuecatProductId, revenuecatLastEventAt
- Apple client secret generator script (Guidelines 4.8 + 3.1.1)
@cursor

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@cursor

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

- expo-iap (StoreKit 2) purchase/restore on mobile, no third-party SDK
- Convex verifies via App Store Server API (ES256 JWT, prod->sandbox fallback)
- App Store Server Notifications V2 endpoint: trigger-only, re-verifies with Apple
- subscriptions: provider appstore + originalTransactionId index, ownership guard
@cursor

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

…aunch recovery for unsynced transactions, app-scoped IAP connection
@cursor

cursor Bot commented Jul 6, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

…iled delivery); add ASC submit key to eas.json
@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@cursor

cursor Bot commented Jul 7, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@Melvynx
Melvynx merged commit b819d59 into main Jul 7, 2026
6 checks passed
@Melvynx
Melvynx deleted the feat/convex-migration branch July 7, 2026 16:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant