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
11 changes: 11 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,17 @@ pnpm exec prisma generate
pnpm dev
```

6. (Optional) Seed deterministic local data — three users with wallets and
creator profiles, sufficient to exercise list, read, and ownership-gated
write flows:

```bash
pnpm exec ts-node prisma/seed.ts
```

See [docs/contributor-seed.md](./docs/contributor-seed.md) for the full
fixture catalogue, reset workflow, and example requests.

## Verification commands

```bash
Expand Down
91 changes: 91 additions & 0 deletions docs/contributor-seed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Local seed and fixture guide

A clean clone needs three users plus their wallets and creator profiles to
exercise most flows (creator list, profile read, ownership-gated update).
The repo ships an idempotent seed script that creates exactly that state so
contributors do not need to copy production-like data.

## What gets seeded

The script creates three deterministic users:

| Email | Wallet address | Creator handle | Notes |
| ---------------------------- | ---------------- | -------------- | ---------------------------------------------------------------------------------------------- |
| `alice.creator@example.test` | `GA7XLM…ALICE` | `alice` | Verified creator. Use for happy-path creator profile tests. |
| `bob.creator@example.test` | `GA7XLM…BOB00` | `bob` | Unverified creator. Useful for permission and verification flows. |
| `charlie.fan@example.test` | `GA7XLM…CHARLIE` | `charlie` | Fan account (still has a creator profile). Use as the "wrong wallet" in ownership-gated tests. |

All three share the password `localdev-password-1` (use this in any auth
flow that requires a password). The wallet addresses are obviously fake
placeholders — they keep the database happy without colliding with real
Stellar accounts.

## Running the seed

The seed file is at [`prisma/seed.ts`](../prisma/seed.ts) and is **idempotent**:
re-running it updates existing rows instead of failing on the unique
constraints.

```sh
# Bring the local Postgres container up
pnpm db:up

# Apply migrations
pnpm migrate

# Generate the Prisma client
pnpm generate

# Run the seed
pnpm exec ts-node prisma/seed.ts
```

If you want Prisma to call the seed automatically on `prisma migrate reset`,
add this to `package.json`:

```jsonc
"prisma": {
"schema": "./prisma/schema",
"seed": "ts-node prisma/seed.ts"
}
```

## Resetting and re-seeding

The fastest way to a known-good state is `prisma migrate reset`, which drops
the schema, re-applies migrations, and runs the seed (when the hook above is
in place):

```sh
pnpm exec prisma migrate reset --force
```

Without the hook, do it in two steps:

```sh
pnpm exec prisma migrate reset --force --skip-seed
pnpm exec ts-node prisma/seed.ts
```

## Adding fixtures for a new flow

When you ship a feature that needs new fixture data:

1. Add the row(s) to `SEED_USERS` (or a new typed array if the shape
differs) in [`prisma/seed.ts`](../prisma/seed.ts).
2. Use `upsert` with a stable unique key so re-runs stay idempotent.
3. Document any new account in this file's table.
4. Avoid real PII — `*.test` emails and synthetic wallet addresses are fine.

## Common scenarios

- **Test the creator list endpoint:**
`GET /api/v1/creators` returns Alice and Bob (Charlie's profile is also
included since the seed gives every user a creator profile).
- **Test ownership-gated profile update:**
`PUT /api/v1/creators/alice/profile` with header
`x-wallet-address: GA7XLM…ALICE` succeeds; the same request with
`x-wallet-address: GA7XLM…CHARLIE` returns `403 FORBIDDEN`.
- **Test wallet-not-mapped path:**
Send any request with a wallet address that is not in `SEED_USERS` —
ownership middleware returns `401 UNAUTHORIZED`.
90 changes: 90 additions & 0 deletions docs/release-checklist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Backend release checklist

A short, operationally actionable checklist for shipping a backend release
safely. Run through it for every deploy that touches production traffic. Each
item is fast — the goal is fewer broken deploys, not more process.

> **Tip:** copy the markdown into the release PR description, tick boxes as
> you go, and link the resulting PR from the release announcement.

## Pre-deploy

### Repo state

- [ ] `pnpm install` is clean on a fresh clone.
- [ ] `pnpm lint` passes.
- [ ] `pnpm build` passes (catches typos in `tsconfig.json` paths and Prisma
type drift before runtime).
- [ ] `pnpm exec jest` passes (or the failing suites are documented as
pre-existing in the release notes).
- [ ] No new `console.log` left over from debugging in production code.

### Configuration

- [ ] `src/config.ts` schema matches the env vars actually set in production.
- [ ] Any new required env var has a placeholder in `.env.example`.
- [ ] Secrets rotation is **not** part of this release (or, if it is, the
rotation runbook is linked in the release notes).
- [ ] Feature flags or kill switches needed for rollout are wired up and
default to the safe value.

### Database migrations

- [ ] `pnpm exec prisma migrate diff --from-migrations ./prisma/schema/migrations --to-schema-datamodel ./prisma/schema --script` produces only the diff you intend.
- [ ] No destructive operations (DROP TABLE / DROP COLUMN / ALTER COLUMN
type-narrow) in the migration. If unavoidable, schedule a separate
maintenance window and link the runbook here.
- [ ] Migrations are **forward-compatible with the previous app version**
so a partial rollout does not break the older replicas. Add columns
as nullable; backfill in a follow-up.
- [ ] Rollback path is documented: which migration to revert and which app
version to redeploy.

### API / contract changes

- [ ] No breaking changes to existing endpoint shapes. New fields go in
additively; field removals require a deprecation cycle.
- [ ] Public endpoints have explicit cache-control settings (cf.
`src/constants/creator-public-cache.constants.ts`).
- [ ] Routes that should be authenticated have a guard middleware applied
(e.g. `requireCreatorProfileOwnership`, `adminGuard`).
- [ ] OpenAPI / `tspec` definitions still validate (`pnpm validate-api`).

## Rollout

- [ ] Deploy to staging first; smoke-test:
- `GET /api/v1/health` returns `200`.
- `GET /api/v1/health/ready` returns `200` (DB reachable, indexer in sync).
- `GET /api/v1/health/detailed` shows no degraded checks.
- Hit at least one read path (`GET /api/v1/creators`) and one write path
(`PUT /api/v1/creators/:creatorId/profile` with a wallet you own).
- [ ] Deploy production with rolling restart; **do not** fast-fail the old
pods until the new ones report ready.
- [ ] Watch error rate and p95 latency for the first 5 minutes after
rollout completes. If either rises sharply, roll back without trying
to diagnose live.

## Post-deploy

- [ ] Verify migrations actually ran: `pnpm exec prisma migrate status`
in the deployed environment shows no pending migrations.
- [ ] Check `GET /api/v1/health/detailed` shows every dependency healthy.
- [ ] Confirm a representative sample of recent requests in the access log
look normal (no 5xx spike, no auth blowback).
- [ ] Update the release notes / changelog with the deployed SHA.

## Rollback

- [ ] Re-deploy the previous app version from the last green release tag.
- [ ] If the rollback uncovers a forward-only migration:
1. Disable the affected code path with the feature flag (if any).
2. Open a hotfix branch that adapts the new code to the live schema.
3. **Do not** revert migrations against production data unless it is
genuinely safe (no writes against the new columns yet).
- [ ] Open a follow-up issue describing what failed and how to prevent it.

## When this checklist needs an update

If you hit something during a deploy that this checklist did not catch,
add it here in the same release PR. The checklist is the documentation of
the lessons we have already learned the hard way.
126 changes: 126 additions & 0 deletions prisma/seed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// prisma/seed.ts
//
// Idempotent seed script for local development. Run from the repo root:
//
// pnpm exec tsx prisma/seed.ts # tsx, if installed
// pnpm exec ts-node prisma/seed.ts # ts-node fallback
//
// Or wire it into Prisma's seeding hook by adding to package.json:
//
// "prisma": {
// "schema": "./prisma/schema",
// "seed": "ts-node prisma/seed.ts"
// }
//
// See `docs/contributor-seed.md` for the full setup workflow.

import { PrismaClient } from '@prisma/client';
import bcrypt from 'bcrypt';

const prisma = new PrismaClient();

interface SeedUser {
email: string;
firstName: string;
lastName: string;
handle: string;
displayName: string;
bio: string;
walletAddress: string;
isVerified: boolean;
}

const SEED_USERS: SeedUser[] = [
{
email: 'alice.creator@example.test',
firstName: 'Alice',
lastName: 'Example',
handle: 'alice',
displayName: 'Alice Example',
bio: 'Verified creator for local development.',
walletAddress:
'GA7XLM00000000000000000000000000000000000000000000000ALICE',
isVerified: true,
},
{
email: 'bob.creator@example.test',
firstName: 'Bob',
lastName: 'Example',
handle: 'bob',
displayName: 'Bob Example',
bio: 'Unverified creator for local development.',
walletAddress:
'GA7XLM000000000000000000000000000000000000000000000000BOB00',
isVerified: false,
},
{
email: 'charlie.fan@example.test',
firstName: 'Charlie',
lastName: 'Fan',
handle: 'charlie',
displayName: 'Charlie Fan',
bio: 'Fan/holder account for ownership-check testing.',
walletAddress:
'GA7XLM0000000000000000000000000000000000000000000000CHARLIE',
isVerified: false,
},
];

async function seed() {
const passwordHash = await bcrypt.hash('localdev-password-1', 10);

for (const user of SEED_USERS) {
const upsertedUser = await prisma.user.upsert({
where: { email: user.email },
create: {
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
passwordHash,
emailVerified: true,
emailVerifiedAt: new Date(),
},
update: {
firstName: user.firstName,
lastName: user.lastName,
},
});

await prisma.stellarWallet.upsert({
where: { userId: upsertedUser.id },
create: {
userId: upsertedUser.id,
address: user.walletAddress,
},
update: {
address: user.walletAddress,
},
});

await prisma.creatorProfile.upsert({
where: { userId: upsertedUser.id },
create: {
userId: upsertedUser.id,
handle: user.handle,
displayName: user.displayName,
bio: user.bio,
isVerified: user.isVerified,
},
update: {
handle: user.handle,
displayName: user.displayName,
bio: user.bio,
isVerified: user.isVerified,
},
});

console.log(`✓ seeded ${user.email} (${user.handle})`);
}
}

seed()
.catch(error => {
console.error('seed failed:', error);
process.exit(1);
})
.finally(() => prisma.$disconnect());
Loading
Loading