Implement private org creation with Stripe integration#7
Implement private org creation with Stripe integration#7gulfaniputra wants to merge 10 commits intosurprisetalk:mainfrom
Conversation
surprisetalk
left a comment
There was a problem hiding this comment.
Excellent work! The architecture is very well designed. I added some small stylistic suggestions.
db.sql
Outdated
| create table org ( | ||
| name citext primary key check (name ~ '^[0-9a-zA-Z_]{4,32}$'), | ||
| created_by citext references usr (name) not null, | ||
| stripe_sub_id text, |
There was a problem hiding this comment.
Let's make this a unique column
There was a problem hiding this comment.
Good call. I’ve added unique to stripe_sub_id. Stripe IDs are globally unique so this should be enforced at the DB level.
org_test.ts
Outdated
| import { PostgresConnection } from "pg-gateway"; | ||
| import dbSql from "./db.sql" with { type: "text" }; | ||
|
|
||
| const pglite = (f: (sql: pg.Sql) => (t: Deno.TestContext) => Promise<void>) => async (t: Deno.TestContext) => { |
There was a problem hiding this comment.
We might just want to append the org tests to server.test.ts so we don't have to copy/paste this
There was a problem hiding this comment.
Makes sense. I’ve moved the org tests into server.test.ts. I’ve also added jane_doe seed and Stripe mock to the shared pglite helper.
org_test.ts
Outdated
| await db.exec(` | ||
| insert into usr (name, email, password, bio, email_verified_at, invited_by, orgs_r, orgs_w) | ||
| values ('john_doe', 'john@example.com', 'hashed:password1!', 'sample bio', now(), 'john_doe', '{secret}', '{secret}') | ||
| on conflict do nothing; | ||
| `); | ||
|
|
||
| await db.exec(` | ||
| insert into usr (name, email, password, bio, email_verified_at, invited_by, orgs_r, orgs_w) | ||
| values ('jane_doe', 'jane@example.com', 'hashed:password1!', 'sample bio', now(), 'john_doe', '{}', '{}') | ||
| on conflict do nothing; | ||
| `); |
There was a problem hiding this comment.
I think you can add these to db.sql and the tests should load them automatically
There was a problem hiding this comment.
Good call. I’ve moved the seed inserts into db.sql so pglite picks them up automatically via the schema load.
server.tsx
Outdated
| tag: tokens.filter(t => t.startsWith("#")).map(t => t.slice(1).toLowerCase()), | ||
| org: tokens.filter(t => t.startsWith("*")).map(t => t.slice(1).toLowerCase()), | ||
| usr: tokens.filter(t => t.startsWith("@")).map(t => t.slice(1)), | ||
| www: tokens.filter(t => t.startsWith("~")).map(t => t.slice(1).toLowerCase()), | ||
| text: tokens.filter(t => !/^[#*@~]/.test(t)).join(" "), |
There was a problem hiding this comment.
I see a lot of formatting changes. All files should be formatted with deno fmt before commit. Double-check that prettier isn't overriding
There was a problem hiding this comment.
Root cause was Prettier overriding deno fmt on save. Added .prettierignore to exclude .ts/.tsx from Prettier and .editorconfig for baseline consistency across editors.
server.tsx
Outdated
| return isImage | ||
| ? \`<a href="\${url}">\${url}</a><br><img src="\${url}" loading="lazy" style="max-width:100%;max-height:400px;">\` | ||
| : \`<a href="\${url}">\${url}</a>\`; |
There was a problem hiding this comment.
It looks like this block broke my deno fmt when I tried to run it 😅 Going to push a fix for that rn
There was a problem hiding this comment.
Pulled in your fix and ran deno fmt. Formatting is clean now.
server.tsx
Outdated
| insert into org (name, created_by, stripe_sub_id) | ||
| values (${orgName}, ${creatorName}, ${subId}) |
There was a problem hiding this comment.
It might be easier to do this:
insert into org ${sql({ name: orgName, created_by: creatorName, stripe_sub_id: subId })}
There was a problem hiding this comment.
Updated to use the sql({}) object syntax for consistency with the rest of the queries in this file.
server.tsx
Outdated
| await sql` | ||
| update usr | ||
| set orgs_r = array_append(orgs_r, ${orgName}), | ||
| orgs_w = array_append(orgs_w, ${orgName}) | ||
| where name = ${creatorName} |
There was a problem hiding this comment.
Instead of using a transaction, you can use a CTE in a single query like this:
with o as (
insert into org ...
)
update usr
set orgs_r = ...
There was a problem hiding this comment.
Replaced the transaction with a single CTE. Cleaner and still atomic.
server.tsx
Outdated
| sql`select name from usr where ${c.req.param("name")} = any(orgs_r)`, | ||
| ]); | ||
| if (!org) return notFound(); | ||
| if (!viewerOrgs.includes(org.name)) throw new HTTPException(403, { message: "Access denied" }); |
There was a problem hiding this comment.
Instead of fetching that viewerOrgs array, you can also do something like
select true
from usr
where true
and name = ${c.get("name") ?? ""}
and ${c.req.param("name")} = any(orgs_r)There was a problem hiding this comment.
Replaced the orgs_r array fetch with a direct select true membership check in the db.
- Add stripe to imports - Seed jane_doe in shared pglite helper - Add stripe mock to shared pglite helper - Append org management test block
- org tests moved to 'server.test.ts' - Seed data moved to 'db.sql' - No logic lost
5cd8dd9 to
693b326
Compare
- Prevent Prettier from overriding 'deno fmt' on .ts/.tsx files - Add baseline editor consistency across editors
- Insert into org - Update usr orgs_r & orgs_w
Summary: Implements private organization creation and management with Stripe integration at $1/member/month.
Demo: Screencast
Changes:
orgtable for subscriptions and ownership.OrgCreateandOrgDashboardcomponents./invite&/remove).Integration Tests: Passed 4/4 steps (4s) using
pgliteand Stripe mocks (terminal output).