尚未检测到更新。GitHub 发布版本后会显示在这里。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Invoices, payments and reminders for small businesses: Next.js, Microsoft Entra ID/Graph, Stripe, Twilio, AI
翻译准备中
取自仓库 README
正在加载完整 README。
查看完整 README ↗Invoices, payments and reminders for small businesses: a portfolio project by Jason McElvain, built in public one weekend at a time.
Live demo: https://macplussupport-office.vercel.app (sign in with any Microsoft account; you get a private sandbox business with fictional clients)
| Sign in | New invoice | Invoice |
|---|---|---|
| Weekend | Feature | Status |
|---|---|---|
| 1 | Microsoft sign-in (Entra ID, MSAL, PKCE), per-user demo business, race-safe invoice numbers | ✅ |
| 2 | PDF invoices, searchable history, send through Outlook with Microsoft Graph | ⏳ |
| 3 | Stripe "Pay this invoice", webhooks, care-plan subscriptions | ⏳ |
| 4 | Twilio SMS / WhatsApp reminders with delivery status | ⏳ |
| 5 | AI assistant that answers only from the business's own documents | ⏳ |
| 6 | Hardening: rate limits, tests in CI, demo reset | ⏳ |
| 7 | Google Wallet membership pass | ⏳ |
| 8 | Shopify app on a dev store | ⏳ |
Invoice numbers look like INV-2026-0042, restart each year, and must never repeat or skip, even when two people save at the same moment.
The common approach, select max(seq) + 1, fails under load: two requests read the same max and both try to use it. This repo includes a test that fires 50 invoice creations at once:
$ npm run test:race -- 50 --naive
50 concurrent creates (NAIVE max+1) in 1348 ms (pool size 5)
succeeded: 16
failed: 34 ← duplicate key errors
FAIL
$ npm run test:race -- 300
300 concurrent creates (locked counter) in 24805 ms (pool size 5)
succeeded: 300
duplicates: 0
gaps: 0
range: INV-2026-0001 … INV-2026-0300
PASS: every number used exactly once
Real output from a run against Neon Postgres (AWS us-east-1, pooled connection): docs/race-test-output.txt. The locked run is slower because requests deliberately wait their turn for the counter row; correctness, not speed, is the point.
The fix is one statement (src/lib/invoices.ts): an upsert on a per-business, per-year counter row, run in the same transaction as the invoice insert. Postgres locks that row until the transaction commits, so requests take turns, and a failed insert rolls the counter back, so there are no gaps. A unique (business_id, number) constraint backs it up.
@azure/msal-node (no wrapper library), plus a state check against CSRF.User.Read; Mail.Send is requested later through incremental consent, the first time a user sends an invoice./common authority).business_id in the signed session cookie, never by an id from a form or URL. Another business's invoice id returns a plain 404.Next.js 16 (App Router, server actions) · TypeScript · Postgres on Neon · pg · @azure/msal-node · iron-session · zod · Tailwind · Vercel
Prerequisites: Node 20.6+, a free Neon database, and a Microsoft Entra app registration (steps below).
git clone https://github.com/MacSupportPlus/macplussupport-office.git
cd macplussupport-office
npm install
cp .env.example .env.local # then fill in the values
npm run db:migrate
npm run test:race # optional: prove the numbering
npm run dev # http://localhost:3000
http://localhost:3000/api/auth/callback. Add your Vercel URL + /api/auth/callback later.AZURE_CLIENT_SECRET.User.Read (already there by default). No admin consent needed.Personal Microsoft account with no directory? Signing in to Entra fails with "Selected user account does not exist in tenant 'Microsoft Services'". Personal accounts need their own tenant to register apps: create a free Azure account with the same login, which gives you a "Default Directory", then register the app there.
Hosted on Vercel and connected to this repo, so every push to main deploys to production.
.env.example under Vercel → Project → Settings → Environment Variables, with APP_URL set to the production URL. Generate fresh SESSION_SECRET and TOKEN_CACHE_KEY values for production.https://<your-project>.vercel.app/api/auth/callback.APP_URL (for example localhost, not 127.0.0.1), or the sign-in cookie won't match and you'll see "Sign-in expired".INV-<year>-0001 and 0002.0001.npm run test:race -- 50 --naive failing and npm run test:race -- 300 passing..env.local is git-ignored; .env.example documents every variable.MIT