Files
buzz/.claude/skills/buzz-database/SKILL.md
T
dmgnr 5d9b75aa9b
Build and Push Image / docker (push) Successful in 5m49s
feat: rubgram manual submission creation
2026-06-02 23:43:00 +07:00

285 lines
7.5 KiB
Markdown

---
name: buzz-database
description: Drizzle ORM workflows for the Buzz PostgreSQL database
metadata:
author: dmgnr
version: 1.0.0
---
# Buzz — Database Workflows (Drizzle ORM)
This skill covers working with the PostgreSQL database via Drizzle ORM.
## Schema Overview
All tables are defined in `lib/db/schema.ts`. The database uses **three named schemas** + the public schema:
### Public schema
| Table | Purpose |
| -------------------------------------------- | --------------------------------------------------------- |
| `cdn` | File storage (bytea), used for images, cards, slips |
| `auditLog` | Admin action audit trail |
| `characters` | Genshin characters (linked to versions, CDN images) |
| `versions` | Game version tracking (self-referencing FK for `from`) |
| `settings` | Global app settings (singleton row, PK is boolean `true`) |
| `guides` | Character build guide links |
| `user`, `session`, `account`, `verification` | Better-auth tables |
### `artifact` schema
| Table | Purpose |
| ------------- | ------------------------------------------ |
| `submissions` | Artifact review queue submissions |
| `cards` | Generated character cards for submissions |
| `settings` | Artifact-specific settings (locked, limit) |
### `endgame` schema
| Table | Purpose |
| ------------- | ----------------------------------- |
| `submissions` | Rubgram (endgame) queue submissions |
| `sarchive` | Archived completed submissions |
| `expired` | Expired unpaid submissions |
| `slips` | SlipOK payment slip images and data |
| `settings` | Rubgram-specific settings |
| `discord` | Discord user mapping |
| `types` | Service types with pricing |
### `tierlist` schema
| Table | Purpose |
| ---------- | ----------------------------------------------------- |
| `types` | Tierlist categories (Spiral Abyss, Stygian Onslaught) |
| `tiers` | Tier definitions (S, A, B, etc.) |
| `columns` | Column layout definitions |
| `badges` | Badge icons for tiers |
| `versions` | Tierlist versions per type |
| `states` | Character placements per version (JSONB badges) |
### Key schema patterns
**UUIDv7 primary keys:**
```ts
import { uuidv7 } from "uuidv7";
id: uuid("id").primaryKey().$defaultFn(uuidv7),
```
**Named schemas with `pgSchema`:**
```ts
import { pgSchema } from "drizzle-orm/pg-core";
const artifact = pgSchema("artifact");
export const submissions = artifact.table("submissions", { ... });
```
**Custom `bytea` column** (for binary data like images):
```ts
import { bytea } from "$/db/custom";
// used in: cdn, cards, slips tables
```
**Generated columns:**
```ts
paid: boolean().generatedAlwaysAs(
sql`(price <= (0)::numeric) OR (slip IS NOT NULL)`
),
```
**Serial sequences** for queue numbering per schema:
```ts
queue: integer().notNull().default(sql`(nextval('artifact.submissions_queue_seq'::regclass))`),
```
**Enums:**
```ts
export const characterElement = pgEnum("character_element", [
"anemo",
"geo",
"dendro",
"hydro",
"pyro",
"cryo",
"electro",
]);
```
**Foreign keys with cascade:**
```ts
char: uuid("char").notNull().references(() => characters.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
```
## Migration Workflow
### Important: database boundary
Schema changes must be pushed to the database **before** the new code deploys. Otherwise the running app will crash on startup when it queries tables or columns that don't exist yet.
| Environment | Command | Timing |
| -------------- | ---------------- | ------------------------------------------------------------------------------------- |
| **Dev** | `bun ds dr push` | Run inside app container (via `bun ds`) against dev DB |
| **Production** | `bun dr push` | Run locally with production env vars against prod DB. **Must run before `git push`.** |
### Full migration workflow
1. **Edit schema** in `lib/db/schema.ts`
2. **Generate migration files** (optional, for tracking):
```bash
bun dr generate
```
3. **Push to dev** to verify:
```bash
bun ds dr push
```
4. **If using migrate instead of push** for production:
```bash
bun dr migrate
```
5. **Push to production DB** (with production credentials):
```bash
bun dr push
```
6. **Deploy code** via `git push` (Gitea Actions handles the rest)
When using `bun dr push`, Drizzle automatically detects differences between the schema and the database, and applies the necessary ALTER statements. For production, always verify the SQL Drizzle generates first.
### Drizzle Studio
Accessible at `http://localhost:4983` when dev containers are running. The `drizzle` service in `dev/compose.yml` provides this:
```bash
bun ds bun dr studio --host $(hostname)
```
## Common Schema Operations
### Adding a column
```ts
// In the table definition, add:
newColumn: varchar("new_column", { length: 255 }).default(""),
```
Generate + push.
### Adding a new table
```ts
export const myTable = pgSchema("myschema").table("my_table", {
id: uuid("id").primaryKey().$defaultFn(uuidv7),
name: varchar("name", { length: 255 }).notNull(),
// ...
});
```
Export and use in queries. Generate + push.
### Adding a new enum
```ts
export const myEnum = pgEnum("my_enum", ["value1", "value2"]);
export const myTable = pgSchema("myschema").table("my_table", {
status: myEnum("status").default("value1"),
});
```
### Adding foreign key
```ts
refId: uuid("ref_id")
.notNull()
.references(() => otherTable.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
```
## Query patterns
### Basic select
```ts
import { db } from "$/db";
import { submissions } from "$/db/schema";
import { eq } from "drizzle-orm";
const items = await db
.select()
.from(submissions)
.where(eq(submissions.checked, false));
```
### Select with join
```ts
const result = await db
.select({
name: characters.name,
queue: submissions.queue,
})
.from(submissions)
.innerJoin(characters, eq(submissions.char, characters.id))
.orderBy(submissions.queue);
```
### Insert
```ts
const [inserted] = await db
.insert(submissions)
.values({ name: "Test", uid: "123456789", char: charId })
.returning({ id: submissions.id, queue: submissions.queue });
```
### Update
```ts
await db
.update(submissions)
.set({ checked: true })
.where(eq(submissions.id, id));
```
### Delete with cascade check (CDN)
```ts
// Use checkCdnRefs() from $api before deleting CDN files
```
### Raw SQL
```ts
import { sql } from "drizzle-orm";
await db.execute(
sql`ALTER SEQUENCE artifact.submissions_queue_seq RESTART WITH 1`,
);
```
### Transactions
```ts
await db.transaction(async (tx) => {
await tx.insert(...);
await tx.update(...);
});
```
### Counting
```ts
const count = await db.$count(submissions);
```