added migration script

This commit is contained in:
Jun-te Kim 2025-12-30 15:45:20 +00:00
parent a8ff57cd40
commit 89ab487431
4 changed files with 42 additions and 220 deletions

View file

@ -0,0 +1,10 @@
-- 0005_harden_login_tokens.sql
ALTER TABLE login_tokens
RENAME COLUMN token TO token_hash;
ALTER TABLE login_tokens
ADD COLUMN used_at TIMESTAMPTZ;
-- optional but recommended
CREATE INDEX ON login_tokens (token_hash);

View file

@ -1,6 +1,7 @@
h1:dTHZRXvfJ8E0dSqq2PAuMLfFFRSDvt3OzgJKEGeXz2g= h1:C2cfl5WnZmikvrVEqNmhQJhGCoVllvcs4vsgA1oN410=
0001_init.sql h1:gzb02ZbjrrJkXOC+2qIZsngnj7A+29O2/b4awScPlPs= 0001_init.sql h1:gzb02ZbjrrJkXOC+2qIZsngnj7A+29O2/b4awScPlPs=
0002_auth.sql h1:4NhBu26dIBMy9gxMxM3tf6Z2CS2kfKlGjFBj07T/aBw= 0002_auth.sql h1:4NhBu26dIBMy9gxMxM3tf6Z2CS2kfKlGjFBj07T/aBw=
0003_stripe_xero.sql h1:E2bcdUDnondsXwbdIwVlZqR4DQwzcoDiyeRFJwVxXwg= 0003_stripe_xero.sql h1:E2bcdUDnondsXwbdIwVlZqR4DQwzcoDiyeRFJwVxXwg=
0004_login_tokens.sql h1:rj1KcWu/0znh2YvtI7JV8Z2nwtL5rZzONbPwX1P+/PI= 0004_login_tokens.sql h1:rj1KcWu/0znh2YvtI7JV8Z2nwtL5rZzONbPwX1P+/PI=
20251228182659_add_used_at_to_login_tokens.sql h1:/0puYQvwBFzpfSKjiZj2XR/7Mui39lS/IbFZW1TPQOc= 20251228182659_add_used_at_to_login_tokens.sql h1:/0puYQvwBFzpfSKjiZj2XR/7Mui39lS/IbFZW1TPQOc=
20251230154354_add_used_at_to_login_tokens.sql h1:7CRBsyxNBk5Ffxx8wvmEy314yHgQ0QUgyB0GhxPMO6o=

View file

View file

@ -1,249 +1,60 @@
// app/page.tsx // app/page.tsx
// This page doubles as:
// 1. A landing page
// 2. A product spec
// 3. A reminder to future-me what the hell I was building
// //
// If youre reading this months later: hi 👋 // CORE MVP PAGE
// The product is the automation, not the UI. // Purpose:
// 1. Explain the automation
// 2. Point the user to the next action
//
// Everything else lives elsewhere.
export default function Home() { export default function Home() {
return ( return (
<main className="max-w-3xl mx-auto p-8 space-y-16"> <main className="max-w-2xl mx-auto p-8 space-y-10">
{/* -------------------------------------------------- {/* --------------------------------------------------
Intro What this is
-------------------------------------------------- */} -------------------------------------------------- */}
<section> <section>
<h1 className="text-2xl font-semibold"> <h1 className="text-2xl font-semibold">
Stripe Xero automation Stripe Xero automation
</h1> </h1>
<p className="mt-2 text-gray-600"> <p className="mt-3 text-gray-700">
Automatically create and mark Xero invoices as paid when a Stripe payment succeeds. When a Stripe payment succeeds, a Xero invoice is
<br /> automatically created and marked as paid.
Built for people who value time more than pressing buttons.
</p> </p>
</section> </section>
{/* -------------------------------------------------- {/* --------------------------------------------------
High-level flow (human readable) What the user does
-------------------------------------------------- */} -------------------------------------------------- */}
<section> <section>
<h2 className="text-xl font-medium">How it works (high level)</h2> <h2 className="text-lg font-medium">
How it works
</h2>
<ol className="mt-4 space-y-3 list-decimal list-inside text-gray-700"> <ol className="mt-3 space-y-2 list-decimal list-inside text-gray-700">
<li>Log in via magic link (passwordless)</li> <li>Log in with your email</li>
<li>Connect your Stripe account</li> <li>Connect Stripe</li>
<li>Connect your Xero organisation</li> <li>Connect Xero</li>
<li>A Stripe payment succeeds</li> <li>Invoices handle themselves. You focus on the business.</li>
<li>An invoice appears in Xero as paid</li>
</ol> </ol>
</section> </section>
{/* -------------------------------------------------- {/* --------------------------------------------------
Magic link auth detailed flow Next action
-------------------------------------------------- */} -------------------------------------------------- */}
<section> <section className="pt-4 border-t">
<h2 className="text-xl font-medium">Login flow (magic link)</h2> <p className="text-gray-700">
Start by logging in.
<p className="mt-2 text-gray-600">
Authentication is passwordless. We only store intent and proof of login.
</p> </p>
{/* Text-based flow diagram (easy to read + copy) */}
<pre className="mt-4 p-4 bg-gray-50 border rounded text-sm overflow-x-auto">
{`Browser
|
| POST /auth/login (email)
v
Backend
- find or create user
- generate token
- hash token
- store login_tokens row
- send email (SES)
|
v
Email (magic link)
|
| GET /auth/callback?token=XYZ
v
Backend
- hash token
- validate token (unused + not expired)
- mark token as used
- create session
|
v
Set session cookie
`}
</pre>
{/* Step-by-step breakdown */}
<ol className="mt-6 space-y-4 list-decimal list-inside text-gray-700">
<li>
User enters their email address.
</li>
<li>
Backend creates (or finds) a user record and stores a one-time login token
in <code className="px-1 bg-gray-100 rounded">login_tokens</code>.
</li>
<li>
An email is sent containing a short-lived magic link.
</li>
<li>
When the link is clicked, the token is validated, marked as used,
and a session is created.
</li>
<li>
A secure session cookie is set. No passwords. No OAuth popups.
</li>
</ol>
</section>
{/* --------------------------------------------------
Stripe Xero automation flow
-------------------------------------------------- */}
<section>
<h2 className="text-xl font-medium">Stripe Xero automation flow</h2>
<pre className="mt-4 p-4 bg-gray-50 border rounded text-sm overflow-x-auto">
{`Stripe payment succeeds
|
| Webhook
v
Backend
- verify Stripe event
- map payment to customer
- create Xero invoice
- mark invoice as paid
|
v
Xero (reconciled automatically)
`}
</pre>
<p className="mt-4 text-gray-600">
Once connected, everything runs automatically.
No manual reconciliation. No awaiting payment state.
</p>
</section>
{/* --------------------------------------------------
Proof
-------------------------------------------------- */}
<section>
<h2 className="text-xl font-medium">Proof, not promises</h2>
<p className="mt-2 text-gray-600">
Your next Stripe payment will automatically reconcile in Xero.
<br />
No manual matching. No bookkeeping busywork.
</p>
</section>
{/* --------------------------------------------------
Pricing
-------------------------------------------------- */}
<section>
<h2 className="text-xl font-medium">Pricing</h2>
<p className="mt-2 text-gray-700">
£200 / month unlimited invoices.
</p>
</section>
{/* --------------------------------------------------
Footer / reminder
-------------------------------------------------- */}
<section className="pt-8 border-t">
<p className="text-gray-500 text-sm">
This page is intentionally simple.
<br />
The product is the automation, not the UI.
</p>
</section>
<section>
<h2 className="text-xl font-medium">Implementation notes (for future me)</h2>
<p className="mt-2 text-gray-600">
These are the only docs needed to implement magic-link auth with Next.js + AWS SES.
</p>
<ul className="mt-4 space-y-2 list-disc list-inside text-gray-700">
<li>
Next.js Route Handlers (auth endpoints):{" "}
<a <a
href="https://nextjs.org/docs/app/building-your-application/routing/route-handlers" href="/login"
className="text-blue-600 underline" className="inline-block mt-4 px-6 py-3 bg-black text-white rounded text-sm"
target="_blank"
> >
nextjs.org/docs/app/.../route-handlers Log in
</a> </a>
</li>
<li>
Next.js Server Actions (optional):{" "}
<a
href="https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions"
className="text-blue-600 underline"
target="_blank"
>
nextjs.org/docs/app/.../server-actions
</a>
</li>
<li>
AWS SES sending email (Node.js):{" "}
<a
href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/client/sesv2/"
className="text-blue-600 underline"
target="_blank"
>
AWS SDK SESv2
</a>
</li>
<li>
AWS SES sandbox production access:{" "}
<a
href="https://docs.aws.amazon.com/ses/latest/dg/request-production-access.html"
className="text-blue-600 underline"
target="_blank"
>
Request production access
</a>
</li>
<li>
Node.js crypto (token generation + hashing):{" "}
<a
href="https://nodejs.org/api/crypto.html"
className="text-blue-600 underline"
target="_blank"
>
nodejs.org/api/crypto
</a>
</li>
<li>
Cookies & sessions:{" "}
<a
href="https://nextjs.org/docs/app/api-reference/functions/cookies"
className="text-blue-600 underline"
target="_blank"
>
Next.js cookies API
</a>
</li>
</ul>
</section> </section>
</main> </main>