ITADN
tornidomaroc-web/scan-and-action
tornidomaroc-web/scan-and-action · 文件
文件最后提交记录最后更新时间
README.md

Live Launch App Gemini Vision React License

Upload a receipt or invoice. Get structured data and a decision in seconds.

What it does

  1. Upload a receipt or invoice (JPEG/PNG/WebP/PDF, ≤10 MB).
  2. Extract — Gemini Vision pulls merchant, total, date, and currency, in Arabic, French, or English.
  3. Decide — built-in business rules return one of three verdicts:
StatusMeaning
APPROVEDData complete, rules passed
⚠️ NEEDS_REVIEWPartial data or ambiguous fields
🚫 FLAGGEDRule violation (high amount, food expense over limit, possible duplicate)
  1. Act — review queue with a fix panel (correct amount, justify, re-evaluate), natural-language search (EN/FR/AR keywords), CSV export.

The UI is localized in English, French, and Arabic with automatic RTL switching.

Pricing: free tier (10 scans) · PRO at $9/month or $59/year via Paddle.

Stack

LayerTechnology
FrontendReact 18 · Vite · Tailwind CSS · react-router
BackendExpress 5 · Prisma · PostgreSQL
Auth & storageSupabase (JWT auth, private file bucket)
ExtractionGemini Flash (vision, JSON mode)
BillingPaddle (webhook-driven plan upgrades)
HostingVercel (frontend) · Railway (backend)

Structure

apps/
  backend/    Express API — auth, upload pipeline, rule engine, search, webhook
  frontend/   React SPA — dashboard, review queue, document detail, settings

Run locally

# Backend (apps/backend)
npm install
cp ../../.env.example .env   # fill in real values
npx prisma generate          # generates the client only; does NOT touch any database
npm run dev                  # http://localhost:3001

# Frontend (apps/frontend)
npm install                  # set VITE_ vars in .env
npm run dev                  # http://localhost:5173

See .env.example for every required variable.

Do not run migrations from your machine. These steps deliberately stop at prisma generate. DATABASE_URL points at the only reachable database, which is production, so prisma migrate deploy from a fresh clone would apply migrations straight to prod. Migrations are applied by the Railway pre-deploy command and by nothing else — see Deployment — database migrations.

Deployment — database migrations

There is exactly one reachable database: production (Supabase). There is no staging or throwaway database in this project. Every rule below follows from that.

How migrations are applied. Migrations are applied by prisma migrate deploy, which runs as the pre-deploy command on the Railway backend service. Railway runs it to completion before the new container starts serving traffic, so schema application strictly precedes the code that depends on it. A failing migration fails the deploy — the new container is not promoted and the previous one keeps serving — rather than shipping new code onto an old schema.

Where to find the output. Railway does not render a separate "Pre-deploy" row in the stage list; it still reads Initialization / Build / Deploy / Post-deploy. The migrate deploy output appears inside the Deploy logs, above the Stopping Container / Starting Container lines. Look there — there is no stage to click on.

CI does not apply migrations, and must not. .github/workflows/ci.yml only typechecks, tests, and builds. It has no database credentials and must never be given any. Applying schema changes is the deploy path's job, not CI's.

Never run against production:

  • prisma db push — applies the schema with no migration history and will drop columns or tables it considers drift. The db:push npm script was removed for this reason.
  • prisma migrate reset — drops and recreates the database. It destroys all production data.

Before merging any migration. Run prisma migrate diff read-only and confirm it comes back empty — an empty diff means the committed migrations fully describe the schema and nothing will be silently applied or skipped at deploy time. A non-empty diff means the migration set and the schema disagree; resolve that before merging, never at deploy time.

Built by

AboJad — Full Stack AI Engineer, Marrakesh 🇲🇦