Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,33 @@ concurrency:
cancel-in-progress: true

jobs:
unit:
name: Unit and storage tests
runs-on: ubuntu-latest
services:
postgres:
image: postgres:17
env:
POSTGRES_USER: bracket
POSTGRES_PASSWORD: bracket
POSTGRES_DB: bracket_test
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U bracket -d bracket_test"
--health-interval 5s --health-timeout 5s --health-retries 10
env:
DATABASE_URL_UNPOOLED: postgres://bracket:bracket@localhost:5432/bracket_test
TEST_DATABASE_URL: postgres://bracket:bracket@localhost:5432/bracket_test
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- run: bun install --frozen-lockfile
- run: bun run db:migrate
- run: bun run test:unit

lint:
name: Lint
runs-on: ubuntu-latest
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ yarn-error.log*

# vercel
.vercel
.neon

# typescript
*.tsbuildinfo
Expand Down
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Gitops

- MUST: Use graphite/gt, and prefer stacks for changes. Structure all plans & code changes around this.
- MUST: Use GitHub native stacked pull requests with the official `gh stack` extension. Use ordinary `git` commits; do not use Graphite or `gt`.
- SHOULD: Split dependent changes into small layers using `gh stack init`, `gh stack add`, and `gh stack submit`. Use `gh stack rebase` / `gh stack sync` to keep layers current and `gh stack merge` for authorized merges.
- MUST: Validate each layer and inspect the complete stack before merging. Keep stack guidance aligned with [GitHub's documentation](https://docs.github.com/en/pull-requests/how-tos/stacked-pull-requests).

# Concise rules for building accessible, fast, delightful UIs. Use MUST/SHOULD/NEVER to guide decisions.

Expand Down
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,27 @@ a Clerk production instance and its production keys before deploying authenticat
- [Radix UI](https://www.radix-ui.com/) primitives
- [html-to-image](https://github.com/bubkoo/html-to-image) for bracket export

## Account storage

Account brackets use Postgres, Drizzle migrations, and Clerk user IDs. Set `DATABASE_URL`
to a pooled connection for the app and `DATABASE_URL_UNPOOLED` to a direct connection
for migrations. Keep both in your environment manager; never commit connection strings.

```bash
bun run db:migrate
bun run test:unit
```

Test schema changes on an isolated Neon development branch before applying them to production.
CI runs migrations and ownership/conflict/sharing tests against an ephemeral Postgres service.
Local storage integration tests run when `TEST_DATABASE_URL` points to a migrated test database.

## Contributing

Use GitHub native stacks via the official `gh stack` extension and ordinary Git commits.
Start with `gh stack init`, add dependent layers with `gh stack add`, and publish with
`gh stack submit`. See [GitHub's stacked PR guide](https://docs.github.com/en/pull-requests/how-tos/stacked-pull-requests).

## License

Licensed under the Apache License 2.0. See [LICENSE](./LICENSE) for details.
247 changes: 247 additions & 0 deletions bun.lock

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from "drizzle-kit";

export default defineConfig({
dialect: "postgresql",
schema: "./src/lib/db/schema.ts",
out: "./drizzle",
dbCredentials: { url: process.env.DATABASE_URL_UNPOOLED ?? "" },
});
15 changes: 15 additions & 0 deletions drizzle/0000_panoramic_korg.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
CREATE TABLE "account_brackets" (
"owner_id" text NOT NULL,
"id" text NOT NULL,
"document" jsonb NOT NULL,
"revision" integer DEFAULT 1 NOT NULL,
"share_token" text,
"shared_document" jsonb,
"shared_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "account_brackets_owner_id_id_pk" PRIMARY KEY("owner_id","id")
);
--> statement-breakpoint
CREATE UNIQUE INDEX "account_brackets_share_token_idx" ON "account_brackets" USING btree ("share_token");--> statement-breakpoint
CREATE INDEX "account_brackets_owner_updated_idx" ON "account_brackets" USING btree ("owner_id","updated_at","id");
140 changes: 140 additions & 0 deletions drizzle/meta/0000_snapshot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
{
"id": "2a0cca02-7226-4960-80c8-3406c461b6d4",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.account_brackets": {
"name": "account_brackets",
"schema": "",
"columns": {
"owner_id": {
"name": "owner_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"id": {
"name": "id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"document": {
"name": "document",
"type": "jsonb",
"primaryKey": false,
"notNull": true
},
"revision": {
"name": "revision",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 1
},
"share_token": {
"name": "share_token",
"type": "text",
"primaryKey": false,
"notNull": false
},
"shared_document": {
"name": "shared_document",
"type": "jsonb",
"primaryKey": false,
"notNull": false
},
"shared_at": {
"name": "shared_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"account_brackets_share_token_idx": {
"name": "account_brackets_share_token_idx",
"columns": [
{
"expression": "share_token",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
},
"account_brackets_owner_updated_idx": {
"name": "account_brackets_owner_updated_idx",
"columns": [
{
"expression": "owner_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "updated_at",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {
"account_brackets_owner_id_id_pk": {
"name": "account_brackets_owner_id_id_pk",
"columns": [
"owner_id",
"id"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
13 changes: 13 additions & 0 deletions drizzle/meta/_journal.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1789343728706,
"tag": "0000_panoramic_korg",
"breakpoints": true
}
]
}
Loading
Loading