Skip to content

Commit 329be24

Browse files
committed
Implement membership rename across schema, ORM, APIs, and tooling
- Added execution order documentation for the membership rename process. - Created summary and detailed plans for schema migrations, ORM entities, services, API usages, and E2E tooling. - Replaced `user_trust_settings` with `user_membership` in the database schema and updated related ORM entities and services. - Updated API middleware and management API to reflect the new membership structure. - Implemented local development seeding for the new membership model and adjusted E2E tests accordingly. - Ensured cumulative verification steps are documented for operator execution. This commit completes the transition from trust settings to a dedicated membership model, ensuring no legacy references remain.
1 parent 65de69b commit 329be24

37 files changed

Lines changed: 691 additions & 150 deletions
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Execution order — membership rename
2+
3+
Phases are **sequential**. Do not start the next phase until the previous phase is complete and verified.
4+
5+
## Phase 1 — Schema (blocking)
6+
7+
- [01-schema-migrations.md](01-schema-migrations.md)
8+
9+
## Phase 2 — ORM entity and relations
10+
11+
- [02-orm-entity-and-relations.md](02-orm-entity-and-relations.md)
12+
13+
Depends on Phase 1 (table name in `@Entity`).
14+
15+
## Phase 3 — ORM services
16+
17+
- [03-orm-services.md](03-orm-services.md)
18+
19+
Depends on Phase 2 (`UserMembership` entity and `User.membership`).
20+
21+
## Phase 4 — API and management-api
22+
23+
- [04-api-and-management-usages.md](04-api-and-management-usages.md)
24+
25+
Depends on Phase 3 (`upsertMembership`, relation name).
26+
27+
## Phase 5 — Seed and E2E tooling
28+
29+
- [05-seed-and-e2e-tooling.md](05-seed-and-e2e-tooling.md)
30+
31+
Depends on Phase 1 (`user_membership` table exists in schema).
32+
33+
## After all phases
34+
35+
Operator verification (cumulative):
36+
37+
```bash
38+
npm run build:packages
39+
npm run lint
40+
npm run test:unit
41+
npm run test:e2e:api
42+
make e2e_test_web_report_spec SPEC=e2e/auth-stale-cookies-redirect-login.spec.ts
43+
```
44+
45+
For existing local Docker DB (table rename without full re-init), re-run `make local_db_init` or apply the one-off in [05-seed-and-e2e-tooling.md](05-seed-and-e2e-tooling.md).
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Membership rename plan set
2+
3+
Hard-break rename of `user_trust_settings``user_membership` and `UserTrustSettings``UserMembership` across schema, ORM, APIs, seeds, and E2E tooling. No fallbacks or legacy names.
4+
5+
## Recorded decisions
6+
7+
| Decision | Choice |
8+
|----------|--------|
9+
| Table / entity | `user_membership` / `UserMembership` (single 1:1 table, inline `membership_tier`) |
10+
| Podverse mirror | Naming domain only (membership); **not** the two-table split (`account_membership` + `account_membership_status`) |
11+
| Scope | Table, entity, relation, and code usages only |
12+
| Out of scope | `packages/helpers/src/trust/` (entitlement capability keys + membership constants); management-web `trustTier*` i18n keys |
13+
| Migration style | Edit original `CREATE TABLE` in `0003`; fold `0004` ALTER columns into `0003`; rename migration files; regenerate `0003a` baseline |
14+
15+
## Plan files
16+
17+
| File | Topic |
18+
|------|--------|
19+
| [01-schema-migrations.md](01-schema-migrations.md) | Linear SQL, kustomization, baseline regen |
20+
| [02-orm-entity-and-relations.md](02-orm-entity-and-relations.md) | Entity, User relation, exports |
21+
| [03-orm-services.md](03-orm-services.md) | UserService, renewal orchestrator, period extension |
22+
| [04-api-and-management-usages.md](04-api-and-management-usages.md) | requireAuth, billing, management users |
23+
| [05-seed-and-e2e-tooling.md](05-seed-and-e2e-tooling.md) | Local dev seed + E2E seed script |
24+
25+
## Dependency map
26+
27+
```mermaid
28+
flowchart LR
29+
schema[01_schema] --> ormEntity[02_orm_entity]
30+
ormEntity --> ormServices[03_orm_services]
31+
ormServices --> api[04_api_mgmt]
32+
schema --> seed[05_seed]
33+
```
34+
35+
## Integrates
36+
37+
- Local dev login fix: `localdev` gets `user_membership` row (premium, long expiry) in [0008_seed_local_user.sql](../../../infra/k8s/base/db/source/bootstrap/0008_seed_local_user.sql).
38+
39+
## Status
40+
41+
Implemented on branch; operator must re-init local DB or run one-off insert for existing Docker volumes.
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# 01 — Schema migrations
2+
3+
## Scope
4+
5+
Replace `user_trust_settings` with `user_membership` in the linear migration chain. Fold billing-renewal columns from `0004` into the `0003` `CREATE TABLE`. No ALTER-rename migration.
6+
7+
## Steps
8+
9+
1. **Replace** `infra/k8s/base/ops/source/database/linear-migrations/app/0003_user_trust_and_entitlement_overrides.sql` with `0003_user_membership.sql`:
10+
- `CREATE TABLE user_membership` with all membership + renewal columns
11+
- Constraints `chk_user_membership_*`
12+
- Trigger `set_updated_at_user_membership`
13+
- Backfill `INSERT INTO user_membership ... SELECT ... FROM "user"`
14+
- Indexes `idx_user_membership_next_renewal_attempt_at`, `idx_user_membership_renewal_retry_backoff_until`
15+
16+
2. **Replace** `0004_billing_catalog_and_trust_renewal.sql` with `0004_billing_catalog_and_membership_renewal.sql`:
17+
- Keep billing catalog tables and seeds
18+
- Remove `ALTER TABLE user_trust_settings` and trust-named indexes
19+
20+
3. **Update** [infra/k8s/base/ops/kustomization.yaml](infra/k8s/base/ops/kustomization.yaml) ConfigMap file list for the two new filenames.
21+
22+
4. **Regenerate baseline** (operator or agent with Nix):
23+
24+
```bash
25+
bash scripts/database/generate-linear-baseline.sh
26+
```
27+
28+
Commit `infra/k8s/base/db/source/bootstrap/0003a_app_linear_baseline.sql.gz`.
29+
30+
## Key files
31+
32+
- `infra/k8s/base/ops/source/database/linear-migrations/app/0003_user_membership.sql`
33+
- `infra/k8s/base/ops/source/database/linear-migrations/app/0004_billing_catalog_and_membership_renewal.sql`
34+
- `infra/k8s/base/ops/kustomization.yaml`
35+
- `infra/k8s/base/db/source/bootstrap/0003a_app_linear_baseline.sql.gz`
36+
37+
## Verification
38+
39+
```bash
40+
bash scripts/database/validate-linear-migrations.sh
41+
bash scripts/database/verify-linear-baseline.sh
42+
gunzip -c infra/k8s/base/db/source/bootstrap/0003a_app_linear_baseline.sql.gz | rg user_membership
43+
```
44+
45+
Expect `user_membership` in baseline; no `user_trust_settings`.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# 02 — ORM entity and relations
2+
3+
## Scope
4+
5+
Rename TypeORM entity and `User` relation from trust settings to membership.
6+
7+
## Steps
8+
9+
1. **Delete** `packages/orm/src/entities/UserTrustSettings.ts`.
10+
11+
2. **Add** `packages/orm/src/entities/UserMembership.ts`:
12+
- `@Entity('user_membership')`
13+
- Class `UserMembership`
14+
- `@OneToOne('User', (u) => u.membership)`
15+
16+
3. **Update** [packages/orm/src/entities/User.ts](packages/orm/src/entities/User.ts):
17+
- Import `UserMembership`
18+
- Relation property `membership!: UserMembership`
19+
20+
4. **Update** [packages/orm/src/types/UserWithRelations.ts](packages/orm/src/types/UserWithRelations.ts):
21+
- `membership: UserMembership | null`
22+
23+
5. **Update** [packages/orm/src/data-source.ts](packages/orm/src/data-source.ts) entity list.
24+
25+
6. **Update** [packages/orm/src/index.ts](packages/orm/src/index.ts):
26+
- Export `UserMembership` (not `UserTrustSettings`)
27+
28+
## Key files
29+
30+
- `packages/orm/src/entities/UserMembership.ts`
31+
- `packages/orm/src/entities/User.ts`
32+
- `packages/orm/src/types/UserWithRelations.ts`
33+
- `packages/orm/src/data-source.ts`
34+
- `packages/orm/src/index.ts`
35+
36+
## Verification
37+
38+
```bash
39+
npm run build -w @metaboost/orm
40+
rg "UserTrustSettings|trustSettings" packages/orm/src
41+
```
42+
43+
Expect no matches.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# 03 — ORM services
2+
3+
## Scope
4+
5+
Update services that read/write membership rows and raw SQL against the table.
6+
7+
## Steps
8+
9+
1. **UserService** ([packages/orm/src/services/UserService.ts](packages/orm/src/services/UserService.ts)):
10+
- `USER_RELATIONS`: `'membership'`
11+
- Repository `UserMembership`
12+
- Rename `upsertTrustSettings``upsertMembership`
13+
14+
2. **BillingRenewalOrchestratorService** ([packages/orm/src/services/billingRenewalOrchestrator.ts](packages/orm/src/services/billingRenewalOrchestrator.ts)):
15+
- `FROM user_membership` in raw SQL
16+
- `getRepository(UserMembership)`
17+
18+
3. **MembershipPeriodExtensionService** ([packages/orm/src/services/membershipPeriodExtension.ts](packages/orm/src/services/membershipPeriodExtension.ts)):
19+
- `getRepository(UserMembership)`
20+
- Local variable `membership` (not `trust`)
21+
- Error messages reference "membership"
22+
23+
## Key files
24+
25+
- `packages/orm/src/services/UserService.ts`
26+
- `packages/orm/src/services/billingRenewalOrchestrator.ts`
27+
- `packages/orm/src/services/membershipPeriodExtension.ts`
28+
29+
## Verification
30+
31+
```bash
32+
npm run build -w @metaboost/orm
33+
rg "user_trust_settings|UserTrustSettings|trustSettings" packages/orm/src
34+
```
35+
36+
Expect no matches.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# 04 — API and management-api usages
2+
3+
## Scope
4+
5+
Update auth middleware, billing read model, management user CRUD, and integration tests.
6+
7+
## Steps
8+
9+
1. **requireAuth** ([apps/api/src/middleware/requireAuth.ts](apps/api/src/middleware/requireAuth.ts)):
10+
- `Reflect.get(value, 'membership')` for `membershipExpiresAt`
11+
12+
2. **billingController** ([apps/api/src/controllers/billingController.ts](apps/api/src/controllers/billingController.ts)):
13+
- `user.membership`
14+
15+
3. **billingReadModelSerialization** ([apps/api/src/lib/billingReadModelSerialization.ts](apps/api/src/lib/billingReadModelSerialization.ts)):
16+
- `params.user.membership`
17+
- Rename `premiumBillingCadenceFromTrust``premiumBillingCadenceFromMembership`
18+
19+
4. **management usersController** ([apps/management-api/src/controllers/usersController.ts](apps/management-api/src/controllers/usersController.ts)):
20+
- `user.membership` in `userToJson`
21+
- `leftJoinAndSelect('user.membership', 'membership')`
22+
- `UserService.upsertMembership`
23+
24+
5. **Integration test** ([apps/api/src/test/billing-renewal-orchestrator.test.ts](apps/api/src/test/billing-renewal-orchestrator.test.ts)):
25+
- SQL `user_membership`
26+
- `refreshed?.membership`
27+
28+
## Key files
29+
30+
- `apps/api/src/middleware/requireAuth.ts`
31+
- `apps/api/src/controllers/billingController.ts`
32+
- `apps/api/src/lib/billingReadModelSerialization.ts`
33+
- `apps/management-api/src/controllers/usersController.ts`
34+
- `apps/api/src/test/billing-renewal-orchestrator.test.ts`
35+
36+
## Verification
37+
38+
```bash
39+
npm run build -w apps/api
40+
npm run build -w apps/management-api
41+
npm run test:e2e:api
42+
```
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# 05 — Seed and E2E tooling
2+
3+
## Scope
4+
5+
Local dev bootstrap user and Playwright E2E seed use `user_membership`. Integrates deferred login fix for `localdev@example.com`.
6+
7+
## Steps
8+
9+
1. **Local Docker bootstrap** [infra/k8s/base/db/source/bootstrap/0008_seed_local_user.sql](infra/k8s/base/db/source/bootstrap/0008_seed_local_user.sql):
10+
- After `user_bio`, insert premium membership with long expiry:
11+
12+
```sql
13+
WITH u AS (SELECT user_id AS id FROM user_credentials WHERE email = 'localdev@example.com')
14+
INSERT INTO user_membership (user_id, membership_tier, membership_expires_at, auto_renew)
15+
SELECT id, 'premium', NOW() + INTERVAL '100 years', false FROM u
16+
ON CONFLICT (user_id) DO NOTHING;
17+
```
18+
19+
- Keep `user_terms_acceptance` insert for terms gate.
20+
21+
2. **E2E seed** [tools/web/seed-e2e.mjs](tools/web/seed-e2e.mjs):
22+
- `INSERT INTO user_membership` (replace `user_trust_settings`)
23+
24+
## Operator — existing local DB (before re-init)
25+
26+
After code deploy, if the volume still has `user_trust_settings`, **re-init** is the clean path:
27+
28+
```bash
29+
make local_db_init
30+
```
31+
32+
Or one-off on fresh schema (`user_membership` table):
33+
34+
```bash
35+
docker exec -i metaboost_local_postgres psql -U metaboost_app_owner -d metaboost_app <<'SQL'
36+
WITH u AS (SELECT user_id AS id FROM user_credentials WHERE email = 'localdev@example.com')
37+
INSERT INTO user_membership (user_id, membership_tier, membership_expires_at, auto_renew)
38+
SELECT id, 'premium', NOW() + INTERVAL '100 years', false FROM u
39+
ON CONFLICT (user_id) DO NOTHING;
40+
SQL
41+
```
42+
43+
Clear cookies for `http://localhost:4002` and log in with `localdev@example.com` / `Test!1Aa`.
44+
45+
## Verification
46+
47+
```bash
48+
make e2e_seed
49+
make e2e_test_web_report_spec SPEC=e2e/auth-stale-cookies-redirect-login.spec.ts
50+
```
51+
52+
Open `.artifacts/e2e-reports/latest/web/index.html`.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# COPY-PASTA — membership rename
2+
3+
Execute prompts in order. Mark `[x]` when complete. Move completed numbered files to `.llm/plans/completed/membership-rename/` per plan-execution-completion-tracking.
4+
5+
---
6+
7+
## Phase 1 — Schema
8+
9+
- [x] **Prompt 1** — Implement [01-schema-migrations.md](01-schema-migrations.md): replace `0003`/`0004` migration files, update kustomization, regenerate `0003a` baseline.
10+
11+
---
12+
13+
## Phase 2 — ORM entity
14+
15+
- [x] **Prompt 2** — Implement [02-orm-entity-and-relations.md](02-orm-entity-and-relations.md): `UserMembership` entity, `User.membership`, exports.
16+
17+
---
18+
19+
## Phase 3 — ORM services
20+
21+
- [x] **Prompt 3** — Implement [03-orm-services.md](03-orm-services.md): `UserService.upsertMembership`, renewal orchestrator, period extension.
22+
23+
---
24+
25+
## Phase 4 — API and management-api
26+
27+
- [x] **Prompt 4** — Implement [04-api-and-management-usages.md](04-api-and-management-usages.md): requireAuth, billing, management users, integration tests.
28+
29+
---
30+
31+
## Phase 5 — Seed and E2E
32+
33+
- [x] **Prompt 5** — Implement [05-seed-and-e2e-tooling.md](05-seed-and-e2e-tooling.md): `0008` local user membership seed, `tools/web/seed-e2e.mjs`.
34+
35+
---
36+
37+
## Cumulative verification (operator)
38+
39+
```bash
40+
npm run build:packages
41+
npm run lint
42+
npm run test:unit
43+
npm run test:e2e:api
44+
make e2e_test_web_report_spec SPEC=e2e/auth-stale-cookies-redirect-login.spec.ts
45+
```
46+
47+
For local dev login after rename:
48+
49+
```bash
50+
make local_db_init
51+
```
52+
53+
Or apply the one-off in [05-seed-and-e2e-tooling.md](05-seed-and-e2e-tooling.md), then restart `npm run dev:all:watch`.

apps/api/src/controllers/billingController.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ export async function membershipSummary(req: Request, res: Response): Promise<vo
1515
res.status(401).json({ message: 'Authentication required' });
1616
return;
1717
}
18-
const trust = user.trustSettings;
19-
if (trust === undefined) {
20-
res.status(404).json({ message: 'Membership settings not found' });
18+
const membership = user.membership;
19+
if (membership === undefined || membership === null) {
20+
res.status(404).json({ message: 'Membership not found' });
2121
return;
2222
}
2323

0 commit comments

Comments
 (0)