added migration script
This commit is contained in:
parent
a8ff57cd40
commit
89ab487431
4 changed files with 42 additions and 220 deletions
|
|
@ -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);
|
||||||
|
|
@ -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=
|
||||||
|
|
|
||||||
0
stripe_to_invoice/app/login/page.tsx
Normal file
0
stripe_to_invoice/app/login/page.tsx
Normal 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 you’re 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>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue