@nexttylabs/echo 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/app/(public)/[organizationSlug]/roadmap/page.tsx +19 -1
- package/app/api/admin/backup/route.ts +22 -4
- package/app/api/auth/register/handler.ts +1 -2
- package/lib/auth/config.ts +0 -7
- package/lib/db/migrations/0000_needy_leech.sql +335 -0
- package/lib/db/migrations/meta/0000_snapshot.json +2186 -1
- package/lib/db/migrations/meta/_journal.json +2 -135
- package/lib/db/schema/auth.ts +0 -1
- package/lib/db/schema/index.ts +0 -1
- package/lib/portal/public-context.tsx +5 -0
- package/package.json +20 -1
- package/.changeset/README.md +0 -21
- package/.changeset/config.json +0 -11
- package/.changeset/cozy-ghosts-care.md +0 -5
- package/.changeset/sharp-lines-stand.md +0 -5
- package/.changeset/sour-doodles-eat.md +0 -5
- package/.changeset/tender-moose-shop.md +0 -5
- package/.github/pull_request_template.md +0 -13
- package/.github/workflows/ci.yml +0 -41
- package/.github/workflows/publish.yml +0 -44
- package/.github/workflows/release.yml +0 -73
- package/AGENTS.md +0 -92
- package/Dockerfile +0 -57
- package/Makefile +0 -77
- package/app/api/internal/domain-lookup/route.ts +0 -67
- package/bun.lock +0 -2503
- package/components/portal/project-switcher.tsx +0 -20
- package/docker-compose.dev.yml +0 -26
- package/docker-compose.yml +0 -98
- package/docs/architecture.md +0 -259
- package/docs/component-inventory.md +0 -261
- package/docs/database-migrations.md +0 -76
- package/docs/development-guide.md +0 -209
- package/docs/e2e-user-flows.csv +0 -31
- package/docs/er-diagram-feedback.mmd +0 -138
- package/docs/er-diagram.mmd +0 -281
- package/docs/i18n-check-report.md +0 -296
- package/docs/index.md +0 -214
- package/docs/logic-chain.md +0 -94
- package/docs/plans/2026-01-02-database-migration-scripts.md +0 -496
- package/docs/plans/2026-01-02-user-login-design.md +0 -37
- package/docs/plans/2026-01-02-user-login.md +0 -437
- package/docs/plans/2026-01-02-user-registration-design.md +0 -47
- package/docs/plans/2026-01-02-user-registration.md +0 -628
- package/docs/plans/2026-01-03-roles-permissions-design.md +0 -20
- package/docs/plans/2026-01-03-roles-permissions.md +0 -266
- package/docs/plans/2026-01-05-authentication-middleware.md +0 -207
- package/docs/plans/2026-01-05-member-removal.md +0 -186
- package/docs/plans/2026-01-05-organization-creation.md +0 -374
- package/docs/plans/2026-01-05-rbac-middleware.md +0 -112
- package/docs/plans/2026-01-05-role-configuration.md +0 -441
- package/docs/plans/2026-01-06-file-upload-support.md +0 -804
- package/docs/plans/2026-01-06-permission-check-hook.md +0 -155
- package/docs/plans/2026-01-06-resource-ownership-check.md +0 -231
- package/docs/plans/2026-01-07-feedback-tracking-link.md +0 -459
- package/docs/plans/2026-01-09-logout-redirect-design.md +0 -52
- package/docs/plans/2026-01-09-phase2-3-plan.md +0 -654
- package/docs/plans/2026-01-09-portal-execution-plan.md +0 -408
- package/docs/plans/2026-01-09-project-delete-feature-design.md +0 -163
- package/docs/plans/2026-01-09-project-delete-implementation.md +0 -451
- package/docs/plans/2026-01-09-project-edit-delete-design.md +0 -52
- package/docs/plans/2026-01-09-settings-center-design.md +0 -114
- package/docs/plans/2026-01-09-settings-center.md +0 -948
- package/docs/plans/2026-01-10-organization-only-design.md +0 -66
- package/docs/plans/2026-01-10-organization-only-implementation.md +0 -433
- package/docs/plans/2026-01-10-portal-settings-restructure-plan.md +0 -18
- package/docs/plans/2026-01-10-project-settings-tabs-design-implementation.md +0 -296
- package/docs/plans/2026-01-14-e2e-playwright-feedback.md +0 -173
- package/docs/plans/2026-01-15-feedback-management-org-context-design.md +0 -82
- package/docs/plans/2026-01-15-feedback-management-org-context-implementation-plan.md +0 -521
- package/docs/plans/2026-01-16-admin-feedback-filters-design.md +0 -75
- package/docs/plans/2026-01-16-admin-feedback-filters-implementation.md +0 -293
- package/docs/plans/2026-01-16-admin-feedback-route-consolidation.md +0 -180
- package/docs/plans/2026-01-16-e2e-test-fixes.md +0 -158
- package/docs/plans/2026-01-17-admin-feedback-filters.md +0 -214
- package/docs/plans/2026-01-17-admin-feedback-improvements.md +0 -453
- package/docs/plans/2026-01-18-changesets-design.md +0 -40
- package/docs/product_changes.md +0 -37
- package/docs/project-overview.md +0 -159
- package/docs/project-scan-report.json +0 -104
- package/docs/route-role-visibility.md +0 -51
- package/docs/source-tree-analysis.md +0 -150
- package/docs/testing/delete-project-manual-tests.md +0 -18
- package/docs/user-story-tracking.md +0 -191
- package/eslint.config.mjs +0 -19
- package/lib/db/migrations/.gitkeep +0 -0
- package/lib/db/migrations/0000_cynical_gladiator.sql +0 -53
- package/lib/db/migrations/0001_wandering_sunfire.sql +0 -27
- package/lib/db/migrations/0002_shallow_speedball.sql +0 -1
- package/lib/db/migrations/0003_add_org_description.sql +0 -1
- package/lib/db/migrations/0003_boring_wild_pack.sql +0 -13
- package/lib/db/migrations/0004_windy_tyrannus.sql +0 -27
- package/lib/db/migrations/0005_perpetual_doorman.sql +0 -5
- package/lib/db/migrations/0006_aberrant_captain_midlands.sql +0 -13
- package/lib/db/migrations/0007_clever_captain_cross.sql +0 -14
- package/lib/db/migrations/0008_sparkling_pandemic.sql +0 -2
- package/lib/db/migrations/0009_happy_black_tom.sql +0 -29
- package/lib/db/migrations/0010_kind_junta.sql +0 -8
- package/lib/db/migrations/0011_mute_squadron_supreme.sql +0 -25
- package/lib/db/migrations/0012_giant_power_man.sql +0 -24
- package/lib/db/migrations/0013_damp_titanium_man.sql +0 -17
- package/lib/db/migrations/0014_blue_alice.sql +0 -18
- package/lib/db/migrations/0015_webhook_tables.sql +0 -41
- package/lib/db/migrations/0016_github_integration.sql +0 -30
- package/lib/db/migrations/0016_overjoyed_ghost_rider.sql +0 -22
- package/lib/db/migrations/0017_slimy_inhumans.sql +0 -6
- package/lib/db/migrations/0018_same_spitfire.sql +0 -1
- package/lib/db/migrations/0019_jittery_loners.sql +0 -16
- package/lib/db/migrations/0019_remove_projects_add_org_settings.sql +0 -14
- package/lib/db/migrations/meta/0001_snapshot.json +0 -553
- package/lib/db/migrations/meta/0002_snapshot.json +0 -560
- package/lib/db/migrations/meta/0003_snapshot.json +0 -650
- package/lib/db/migrations/meta/0004_snapshot.json +0 -852
- package/lib/db/migrations/meta/0005_snapshot.json +0 -900
- package/lib/db/migrations/meta/0006_snapshot.json +0 -1011
- package/lib/db/migrations/meta/0007_snapshot.json +0 -1125
- package/lib/db/migrations/meta/0008_snapshot.json +0 -1146
- package/lib/db/migrations/meta/0009_snapshot.json +0 -1386
- package/lib/db/migrations/meta/0010_snapshot.json +0 -1419
- package/lib/db/migrations/meta/0011_snapshot.json +0 -1615
- package/lib/db/migrations/meta/0012_snapshot.json +0 -1805
- package/lib/db/migrations/meta/0013_snapshot.json +0 -1948
- package/lib/db/migrations/meta/0014_snapshot.json +0 -2082
- package/lib/db/migrations/meta/0015_snapshot.json +0 -2476
- package/lib/db/migrations/meta/0016_snapshot.json +0 -2633
- package/lib/db/migrations/meta/0017_snapshot.json +0 -2680
- package/lib/db/migrations/meta/0018_snapshot.json +0 -2686
- package/lib/db/migrations/meta/0019_snapshot.json +0 -2741
- package/lib/db/schema/projects.ts +0 -145
- package/lib/db/schema/user-profiles.ts +0 -31
- package/lib/validations/projects.ts +0 -49
- package/next-env.d.ts +0 -6
- package/playwright.config.ts +0 -44
- package/proxy.test.ts +0 -131
- package/proxy.ts +0 -190
- package/scripts/backup-db.sh +0 -57
- package/scripts/backup-db.ts +0 -24
- package/scripts/generate-openapi.ts +0 -22
- package/scripts/migration-helper.ts +0 -39
- package/scripts/pre-deploy.ts +0 -75
- package/scripts/restore-db.sh +0 -60
- package/scripts/rollback.ts +0 -72
- package/scripts/seed-tags.ts +0 -48
- package/tests/api/feedback-bulk.test.ts +0 -47
- package/tests/api/feedback-by-id.test.ts +0 -67
- package/tests/api/feedback-comments-route-import.test.ts +0 -26
- package/tests/api/feedback-create.test.ts +0 -71
- package/tests/api/feedback-delete.test.ts +0 -160
- package/tests/api/feedback-filter.test.ts +0 -250
- package/tests/api/feedback-list.test.ts +0 -234
- package/tests/api/feedback-route-assignee-condition.test.ts +0 -32
- package/tests/api/feedback-similar.test.ts +0 -46
- package/tests/api/feedback-sort.test.ts +0 -261
- package/tests/api/feedback-status-enum.test.ts +0 -49
- package/tests/api/feedback-status-filter.test.ts +0 -117
- package/tests/api/feedback-submit-on-behalf.test.ts +0 -269
- package/tests/api/feedback.test.ts +0 -175
- package/tests/api/identify-jwt.test.ts +0 -25
- package/tests/api/invitation-accept.test.ts +0 -213
- package/tests/api/organization-invitations.test.ts +0 -186
- package/tests/api/organization-members-list.test.ts +0 -79
- package/tests/api/organization-members.test.ts +0 -340
- package/tests/api/organizations.test.ts +0 -149
- package/tests/api/register.test.ts +0 -112
- package/tests/api/upload.test.ts +0 -103
- package/tests/api/vote.test.ts +0 -82
- package/tests/app/admin-feedback-detail-page.test.tsx +0 -25
- package/tests/app/admin-feedback-list-page.test.tsx +0 -25
- package/tests/app/admin-feedback-new-page.test.tsx +0 -25
- package/tests/app/health-route-helpers.test.ts +0 -27
- package/tests/app/login-page.test.ts +0 -26
- package/tests/app/portal-page.test.ts +0 -29
- package/tests/app/project-portal-overview.test.tsx +0 -25
- package/tests/app/widget-page-import.test.ts +0 -25
- package/tests/components/create-post-dialog-defaults.test.ts +0 -43
- package/tests/components/feedback/duplicate-suggestions-inline.test.tsx +0 -27
- package/tests/components/feedback/embedded-feedback-form.test.tsx +0 -96
- package/tests/components/feedback/feedback-detail.test.tsx +0 -25
- package/tests/components/feedback/feedback-stats.test.tsx +0 -49
- package/tests/components/feedback-bulk-actions.test.tsx +0 -39
- package/tests/components/feedback-i18n-keys.test.ts +0 -70
- package/tests/components/feedback-list-controls-compile.test.ts +0 -25
- package/tests/components/feedback-list-controls.test.tsx +0 -204
- package/tests/components/feedback-list-item.test.tsx +0 -67
- package/tests/components/landing/hero.test.tsx +0 -46
- package/tests/components/layout/language-switcher.test.tsx +0 -25
- package/tests/components/layout/sidebar.test.tsx +0 -157
- package/tests/components/login-form.test.ts +0 -25
- package/tests/components/organization-form.test.ts +0 -32
- package/tests/components/organization-switcher.test.ts +0 -25
- package/tests/components/pagination.test.tsx +0 -43
- package/tests/components/portal-overview.test.tsx +0 -25
- package/tests/components/profile-form.test.tsx +0 -139
- package/tests/components/role-selector.test.ts +0 -31
- package/tests/components/status-chart.test.tsx +0 -90
- package/tests/e2e/auth.e2e.ts +0 -323
- package/tests/e2e/feedback-actions.e2e.ts +0 -471
- package/tests/e2e/feedback-attachment.e2e.ts +0 -168
- package/tests/e2e/feedback-customer.e2e.ts +0 -226
- package/tests/e2e/feedback-management.e2e.ts +0 -565
- package/tests/e2e/feedback-submit.e2e.ts +0 -133
- package/tests/e2e/feedback-view.e2e.ts +0 -297
- package/tests/e2e/fixtures/test-data.ts +0 -235
- package/tests/e2e/health-check.e2e.ts +0 -230
- package/tests/e2e/helpers/test-utils-helpers.test.ts +0 -43
- package/tests/e2e/helpers/test-utils.ts +0 -298
- package/tests/e2e/integration-placeholders.e2e.ts +0 -199
- package/tests/e2e/organization.e2e.ts +0 -292
- package/tests/e2e/permissions.e2e.ts +0 -424
- package/tests/e2e/project-widget.e2e.ts +0 -63
- package/tests/feedback/filters.test.ts +0 -29
- package/tests/hooks/use-permissions.test.ts +0 -52
- package/tests/lib/ai/classifier.test.ts +0 -104
- package/tests/lib/ai/duplicate-detector.test.ts +0 -234
- package/tests/lib/attachments-schema.test.ts +0 -30
- package/tests/lib/auth/session.test.ts +0 -49
- package/tests/lib/auth-client.test.ts +0 -37
- package/tests/lib/auth-config.test.ts +0 -26
- package/tests/lib/feedback-prefill.test.ts +0 -52
- package/tests/lib/feedback-processor.test.ts +0 -41
- package/tests/lib/feedback-schema.test.ts +0 -33
- package/tests/lib/file-validator.test.ts +0 -48
- package/tests/lib/get-feedback-by-id.test.ts +0 -37
- package/tests/lib/invitations.test.ts +0 -35
- package/tests/lib/login-schema.test.ts +0 -36
- package/tests/lib/org-context.test.ts +0 -95
- package/tests/lib/organization-access.test.ts +0 -44
- package/tests/lib/organization-member-role-schema.test.ts +0 -41
- package/tests/lib/permissions.test.ts +0 -88
- package/tests/lib/portal-analytics.test.ts +0 -25
- package/tests/lib/portal-contributors.test.ts +0 -25
- package/tests/lib/portal-copy.test.ts +0 -27
- package/tests/lib/portal-i18n.test.ts +0 -30
- package/tests/lib/portal-leaderboard-settings.test.ts +0 -25
- package/tests/lib/portal-modules.test.ts +0 -25
- package/tests/lib/portal-seo.test.ts +0 -25
- package/tests/lib/portal-sharing.test.ts +0 -25
- package/tests/lib/portal-sorting.test.ts +0 -25
- package/tests/lib/portal-theme.test.ts +0 -25
- package/tests/lib/rate-limit.test.ts +0 -142
- package/tests/lib/resolve-locale.test.ts +0 -34
- package/tests/lib/services/backup.test.ts +0 -145
- package/tests/lib/user-organizations.test.ts +0 -42
- package/tests/lib/user-role-schema.test.ts +0 -33
- package/tests/lib/user-schema.test.ts +0 -25
- package/tests/setup.ts +0 -74
- package/vercel.json +0 -4
|
@@ -1,496 +0,0 @@
|
|
|
1
|
-
# Database Migration Scripts Implementation Plan
|
|
2
|
-
|
|
3
|
-
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
|
4
|
-
|
|
5
|
-
**Goal:** Add Drizzle Kit migration tooling, runnable scripts, docs, CI workflow, and a docker-compose migration service.
|
|
6
|
-
|
|
7
|
-
**Architecture:** Move the database module into `lib/db/` so we can add `schema/` and `migrations/` subfolders, configure Drizzle Kit at repo root, and provide a typed migration runner plus CLI scripts that reuse the logger. Migrations run via `drizzle-orm/bun-sql/migrator` against Bun SQL, with optional pre-deploy checks and manual rollback hooks.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** Bun, Drizzle ORM/Kit, Next.js App Router, GitHub Actions, Docker Compose
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
### Task 1: Restructure DB module + schema layout
|
|
14
|
-
|
|
15
|
-
**Files:**
|
|
16
|
-
- Move: `lib/db.ts` → `lib/db/index.ts`
|
|
17
|
-
- Create: `lib/db/schema/index.ts`
|
|
18
|
-
- Create: `lib/db/migrations/.gitkeep`
|
|
19
|
-
|
|
20
|
-
**Step 1: Move the DB module**
|
|
21
|
-
- Move file to `lib/db/index.ts` without changing exports.
|
|
22
|
-
|
|
23
|
-
**Step 2: Create empty schema entrypoint**
|
|
24
|
-
```ts
|
|
25
|
-
// lib/db/schema/index.ts
|
|
26
|
-
// Placeholder schema module for Drizzle Kit. Add tables here in future stories.
|
|
27
|
-
export {};
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
**Step 3: Add migrations directory placeholder**
|
|
31
|
-
- Create `lib/db/migrations/.gitkeep` (empty file).
|
|
32
|
-
|
|
33
|
-
**Step 4: Run lint**
|
|
34
|
-
Run: `bun run lint`
|
|
35
|
-
Expected: Same existing warning about `components/component-example.tsx` and no new errors.
|
|
36
|
-
|
|
37
|
-
**Step 5: Commit**
|
|
38
|
-
```bash
|
|
39
|
-
git add lib/db/index.ts lib/db/schema/index.ts lib/db/migrations/.gitkeep
|
|
40
|
-
# include rename info automatically
|
|
41
|
-
git commit -m "chore: restructure db module for migrations"
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
### Task 2: Add Drizzle Kit config + package scripts
|
|
47
|
-
|
|
48
|
-
**Files:**
|
|
49
|
-
- Create: `drizzle.config.ts`
|
|
50
|
-
- Modify: `package.json`
|
|
51
|
-
|
|
52
|
-
**Step 1: Add dev dependencies**
|
|
53
|
-
Run: `bun add -d drizzle-kit dotenv`
|
|
54
|
-
Expected: `drizzle-kit` and `dotenv` added to `devDependencies`.
|
|
55
|
-
|
|
56
|
-
**Step 2: Create Drizzle config**
|
|
57
|
-
```ts
|
|
58
|
-
// drizzle.config.ts
|
|
59
|
-
import { defineConfig } from "drizzle-kit";
|
|
60
|
-
import { config } from "dotenv";
|
|
61
|
-
|
|
62
|
-
config({ path: ".env.local" });
|
|
63
|
-
|
|
64
|
-
export default defineConfig({
|
|
65
|
-
schema: "./lib/db/schema",
|
|
66
|
-
out: "./lib/db/migrations",
|
|
67
|
-
dialect: "postgresql",
|
|
68
|
-
dbCredentials: {
|
|
69
|
-
url: process.env.DATABASE_URL!,
|
|
70
|
-
},
|
|
71
|
-
verbose: true,
|
|
72
|
-
strict: true,
|
|
73
|
-
});
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
**Step 3: Add db scripts**
|
|
77
|
-
Update `package.json` scripts to include:
|
|
78
|
-
```json
|
|
79
|
-
"db:generate": "drizzle-kit generate:pg",
|
|
80
|
-
"db:migrate": "drizzle-kit migrate",
|
|
81
|
-
"db:push": "drizzle-kit push:pg",
|
|
82
|
-
"db:studio": "drizzle-kit studio",
|
|
83
|
-
"db:introspect": "drizzle-kit introspect:pg",
|
|
84
|
-
"db:check": "drizzle-kit check"
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
**Step 4: Run lint**
|
|
88
|
-
Run: `bun run lint`
|
|
89
|
-
Expected: Same existing warning about `components/component-example.tsx` and no new errors.
|
|
90
|
-
|
|
91
|
-
**Step 5: Commit**
|
|
92
|
-
```bash
|
|
93
|
-
git add drizzle.config.ts package.json bun.lock
|
|
94
|
-
|
|
95
|
-
git commit -m "chore: add drizzle kit config and scripts"
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
### Task 3: Migration runner with tests (@superpowers:test-driven-development)
|
|
101
|
-
|
|
102
|
-
**Files:**
|
|
103
|
-
- Create: `lib/db/migrate.ts`
|
|
104
|
-
- Create: `lib/db/migrate.test.ts`
|
|
105
|
-
|
|
106
|
-
**Step 1: Write failing tests**
|
|
107
|
-
```ts
|
|
108
|
-
// lib/db/migrate.test.ts
|
|
109
|
-
import { describe, expect, it } from "bun:test";
|
|
110
|
-
import { runMigrations } from "./migrate";
|
|
111
|
-
|
|
112
|
-
describe("runMigrations", () => {
|
|
113
|
-
it("throws when database is missing", async () => {
|
|
114
|
-
await expect(runMigrations({ database: null })).rejects.toThrow(
|
|
115
|
-
"Database connection not configured"
|
|
116
|
-
);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it("calls migrator with default migrations folder", async () => {
|
|
120
|
-
let called = false;
|
|
121
|
-
const migrateFn = async (_db: unknown, config: { migrationsFolder: string }) => {
|
|
122
|
-
called = true;
|
|
123
|
-
expect(config.migrationsFolder).toBe("./lib/db/migrations");
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
await runMigrations({
|
|
127
|
-
database: {} as unknown,
|
|
128
|
-
migrateFn,
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
expect(called).toBe(true);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
**Step 2: Run tests to see failure**
|
|
137
|
-
Run: `bun test lib/db/migrate.test.ts`
|
|
138
|
-
Expected: FAIL (module not found or `runMigrations` not implemented).
|
|
139
|
-
|
|
140
|
-
**Step 3: Implement migration runner**
|
|
141
|
-
```ts
|
|
142
|
-
// lib/db/migrate.ts
|
|
143
|
-
import { migrate } from "drizzle-orm/bun-sql/migrator";
|
|
144
|
-
import { db } from "@/lib/db";
|
|
145
|
-
import { logger } from "@/lib/logger";
|
|
146
|
-
|
|
147
|
-
type Database = NonNullable<typeof db>;
|
|
148
|
-
|
|
149
|
-
type RunMigrationsOptions = {
|
|
150
|
-
database?: Database | null;
|
|
151
|
-
migrateFn?: typeof migrate;
|
|
152
|
-
migrationsFolder?: string;
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
export async function runMigrations(options: RunMigrationsOptions = {}) {
|
|
156
|
-
const database = options.database ?? db;
|
|
157
|
-
const migrateFn = options.migrateFn ?? migrate;
|
|
158
|
-
const migrationsFolder = options.migrationsFolder ?? "./lib/db/migrations";
|
|
159
|
-
|
|
160
|
-
if (!database || !process.env.DATABASE_URL) {
|
|
161
|
-
logger.error("Database connection not configured");
|
|
162
|
-
throw new Error("Database connection not configured");
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
logger.info("Running database migrations...");
|
|
166
|
-
|
|
167
|
-
try {
|
|
168
|
-
await migrateFn(database, { migrationsFolder });
|
|
169
|
-
logger.info("Migrations completed successfully");
|
|
170
|
-
} catch (error) {
|
|
171
|
-
logger.error({ err: error }, "Migration failed");
|
|
172
|
-
throw error;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (import.meta.main) {
|
|
177
|
-
runMigrations()
|
|
178
|
-
.then(() => {
|
|
179
|
-
console.log("✅ Migrations completed");
|
|
180
|
-
process.exit(0);
|
|
181
|
-
})
|
|
182
|
-
.catch((error) => {
|
|
183
|
-
console.error("❌ Migration failed:", error);
|
|
184
|
-
process.exit(1);
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
**Step 4: Run tests to confirm pass**
|
|
190
|
-
Run: `bun test lib/db/migrate.test.ts`
|
|
191
|
-
Expected: PASS (2 tests).
|
|
192
|
-
|
|
193
|
-
**Step 5: Commit**
|
|
194
|
-
```bash
|
|
195
|
-
git add lib/db/migrate.ts lib/db/migrate.test.ts
|
|
196
|
-
|
|
197
|
-
git commit -m "feat: add migration runner"
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
---
|
|
201
|
-
|
|
202
|
-
### Task 4: Helper scripts (migration, pre-deploy, rollback)
|
|
203
|
-
|
|
204
|
-
**Files:**
|
|
205
|
-
- Create: `scripts/migration-helper.ts`
|
|
206
|
-
- Create: `scripts/pre-deploy.ts`
|
|
207
|
-
- Create: `scripts/rollback.ts`
|
|
208
|
-
|
|
209
|
-
**Step 1: Create migration helper**
|
|
210
|
-
```ts
|
|
211
|
-
// scripts/migration-helper.ts
|
|
212
|
-
import { execSync } from "child_process";
|
|
213
|
-
import { logger } from "@/lib/logger";
|
|
214
|
-
|
|
215
|
-
export function generateMigration(name: string) {
|
|
216
|
-
logger.info({ name }, "Generating migration");
|
|
217
|
-
execSync("bun run db:generate", { stdio: "inherit" });
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
export function applyMigrations() {
|
|
221
|
-
logger.info("Applying migrations...");
|
|
222
|
-
execSync("bun run db:migrate", { stdio: "inherit" });
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
export function pushSchema() {
|
|
226
|
-
logger.info("Pushing schema to database...");
|
|
227
|
-
execSync("bun run db:push", { stdio: "inherit" });
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
export function checkSchema() {
|
|
231
|
-
logger.info("Checking schema consistency...");
|
|
232
|
-
execSync("bun run db:check", { stdio: "inherit" });
|
|
233
|
-
}
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
**Step 2: Create pre-deploy script**
|
|
237
|
-
```ts
|
|
238
|
-
// scripts/pre-deploy.ts
|
|
239
|
-
import { sql } from "drizzle-orm";
|
|
240
|
-
import { db } from "@/lib/db";
|
|
241
|
-
import { runMigrations } from "@/lib/db/migrate";
|
|
242
|
-
import { logger } from "@/lib/logger";
|
|
243
|
-
|
|
244
|
-
async function verifyMigrations() {
|
|
245
|
-
if (!db) {
|
|
246
|
-
throw new Error("Database connection not configured");
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
const tables = await db.execute(sql`
|
|
250
|
-
SELECT table_name
|
|
251
|
-
FROM information_schema.tables
|
|
252
|
-
WHERE table_schema = 'public'
|
|
253
|
-
ORDER BY table_name;
|
|
254
|
-
`);
|
|
255
|
-
|
|
256
|
-
logger.info({ tables: (tables as { rows?: unknown[] }).rows ?? [] }, "Database tables");
|
|
257
|
-
|
|
258
|
-
const rowCount = Array.isArray((tables as { rows?: unknown[] }).rows)
|
|
259
|
-
? (tables as { rows?: unknown[] }).rows!.length
|
|
260
|
-
: 0;
|
|
261
|
-
|
|
262
|
-
if (rowCount < 1) {
|
|
263
|
-
throw new Error("Unexpected number of tables, migration may have failed");
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
async function createBackup() {
|
|
268
|
-
// Implemented in Story 8.5
|
|
269
|
-
logger.warn("DB backup step skipped (Story 8.5)");
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
async function preDeploy() {
|
|
273
|
-
logger.info("Starting pre-deployment migration...");
|
|
274
|
-
|
|
275
|
-
if (!db) {
|
|
276
|
-
throw new Error("Database connection not configured");
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
await db.execute(sql`SELECT 1`);
|
|
280
|
-
|
|
281
|
-
if (process.env.DB_BACKUP_BEFORE_MIGRATE === "true") {
|
|
282
|
-
await createBackup();
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
await runMigrations();
|
|
286
|
-
await verifyMigrations();
|
|
287
|
-
|
|
288
|
-
logger.info("Pre-deployment completed successfully");
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
preDeploy()
|
|
292
|
-
.then(() => process.exit(0))
|
|
293
|
-
.catch((error) => {
|
|
294
|
-
logger.error({ err: error }, "Pre-deployment failed");
|
|
295
|
-
process.exit(1);
|
|
296
|
-
});
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
**Step 3: Create rollback script**
|
|
300
|
-
```ts
|
|
301
|
-
// scripts/rollback.ts
|
|
302
|
-
import { readFileSync } from "fs";
|
|
303
|
-
import { join } from "path";
|
|
304
|
-
import { sql } from "drizzle-orm";
|
|
305
|
-
import { db } from "@/lib/db";
|
|
306
|
-
import { logger } from "@/lib/logger";
|
|
307
|
-
|
|
308
|
-
async function executeRollback(migration: { name: string; hash: string }) {
|
|
309
|
-
const rollbackFile = join(
|
|
310
|
-
process.cwd(),
|
|
311
|
-
"lib/db/migrations",
|
|
312
|
-
`${migration.name}-rollback.sql`
|
|
313
|
-
);
|
|
314
|
-
|
|
315
|
-
const rawSql = readFileSync(rollbackFile, "utf8");
|
|
316
|
-
await db!.execute(sql.raw(rawSql));
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
async function rollback(steps = 1) {
|
|
320
|
-
if (!db) {
|
|
321
|
-
throw new Error("Database connection not configured");
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
logger.info({ steps }, "Rolling back migrations...");
|
|
325
|
-
|
|
326
|
-
const appliedMigrations = await db.execute(sql`
|
|
327
|
-
SELECT * FROM drizzle_migrations
|
|
328
|
-
ORDER BY created_at DESC
|
|
329
|
-
LIMIT ${steps}
|
|
330
|
-
`);
|
|
331
|
-
|
|
332
|
-
const rows = (appliedMigrations as { rows?: Array<{ name: string; hash: string }> }).rows ?? [];
|
|
333
|
-
|
|
334
|
-
for (const migration of rows) {
|
|
335
|
-
logger.info({ migration }, "Rolling back migration");
|
|
336
|
-
await executeRollback(migration);
|
|
337
|
-
await db.execute(sql`
|
|
338
|
-
DELETE FROM drizzle_migrations
|
|
339
|
-
WHERE hash = ${migration.hash}
|
|
340
|
-
`);
|
|
341
|
-
logger.info({ migration: migration.name }, "Rolled back");
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
logger.info("Rollback completed");
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const stepsArg = Number.parseInt(process.argv[2] ?? "1", 10);
|
|
348
|
-
rollback(Number.isNaN(stepsArg) ? 1 : stepsArg)
|
|
349
|
-
.then(() => process.exit(0))
|
|
350
|
-
.catch((error) => {
|
|
351
|
-
logger.error({ err: error }, "Rollback failed");
|
|
352
|
-
process.exit(1);
|
|
353
|
-
});
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
**Step 4: Run lint**
|
|
357
|
-
Run: `bun run lint`
|
|
358
|
-
Expected: Same existing warning about `components/component-example.tsx` and no new errors.
|
|
359
|
-
|
|
360
|
-
**Step 5: Commit**
|
|
361
|
-
```bash
|
|
362
|
-
git add scripts/migration-helper.ts scripts/pre-deploy.ts scripts/rollback.ts
|
|
363
|
-
|
|
364
|
-
git commit -m "feat: add migration helper scripts"
|
|
365
|
-
```
|
|
366
|
-
|
|
367
|
-
---
|
|
368
|
-
|
|
369
|
-
### Task 5: Migration documentation
|
|
370
|
-
|
|
371
|
-
**Files:**
|
|
372
|
-
- Create: `docs/database-migrations.md`
|
|
373
|
-
|
|
374
|
-
**Step 1: Write docs**
|
|
375
|
-
Include sections: generating migrations, applying migrations (dev vs prod), schema checks, Drizzle Studio, rollback convention, and best practices.
|
|
376
|
-
|
|
377
|
-
**Step 2: Commit**
|
|
378
|
-
```bash
|
|
379
|
-
git add docs/database-migrations.md
|
|
380
|
-
|
|
381
|
-
git commit -m "docs: add database migration guide"
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
---
|
|
385
|
-
|
|
386
|
-
### Task 6: CI workflow for migrations
|
|
387
|
-
|
|
388
|
-
**Files:**
|
|
389
|
-
- Create: `.github/workflows/migrate.yml`
|
|
390
|
-
|
|
391
|
-
**Step 1: Add workflow**
|
|
392
|
-
```yaml
|
|
393
|
-
name: Database Migration
|
|
394
|
-
|
|
395
|
-
on:
|
|
396
|
-
workflow_dispatch:
|
|
397
|
-
deployment:
|
|
398
|
-
environment: production
|
|
399
|
-
|
|
400
|
-
jobs:
|
|
401
|
-
migrate:
|
|
402
|
-
runs-on: ubuntu-latest
|
|
403
|
-
steps:
|
|
404
|
-
- uses: actions/checkout@v4
|
|
405
|
-
- name: Setup Bun
|
|
406
|
-
uses: oven-sh/setup-bun@v1
|
|
407
|
-
- name: Install dependencies
|
|
408
|
-
run: bun install
|
|
409
|
-
- name: Run migrations
|
|
410
|
-
env:
|
|
411
|
-
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
|
412
|
-
run: bun run db:migrate
|
|
413
|
-
- name: Verify migrations
|
|
414
|
-
env:
|
|
415
|
-
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
|
416
|
-
run: bun run db:check
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
**Step 2: Commit**
|
|
420
|
-
```bash
|
|
421
|
-
git add .github/workflows/migrate.yml
|
|
422
|
-
|
|
423
|
-
git commit -m "ci: add migration workflow"
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
---
|
|
427
|
-
|
|
428
|
-
### Task 7: Docker compose migration service
|
|
429
|
-
|
|
430
|
-
**Files:**
|
|
431
|
-
- Create or Modify: `docker-compose.yml`
|
|
432
|
-
|
|
433
|
-
**Step 1: Add migrate service**
|
|
434
|
-
If `docker-compose.yml` does not exist, create a minimal file with `postgres` + `migrate`:
|
|
435
|
-
```yaml
|
|
436
|
-
version: "3.9"
|
|
437
|
-
|
|
438
|
-
services:
|
|
439
|
-
postgres:
|
|
440
|
-
image: postgres:16-alpine
|
|
441
|
-
environment:
|
|
442
|
-
POSTGRES_USER: ${POSTGRES_USER:-echo}
|
|
443
|
-
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-changeme}
|
|
444
|
-
POSTGRES_DB: ${POSTGRES_DB:-echo}
|
|
445
|
-
ports:
|
|
446
|
-
- "${POSTGRES_PORT:-5432}:5432"
|
|
447
|
-
healthcheck:
|
|
448
|
-
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-echo} -d ${POSTGRES_DB:-echo}"]
|
|
449
|
-
interval: 10s
|
|
450
|
-
timeout: 5s
|
|
451
|
-
retries: 5
|
|
452
|
-
volumes:
|
|
453
|
-
- postgres_data:/var/lib/postgresql/data
|
|
454
|
-
networks:
|
|
455
|
-
- echo-network
|
|
456
|
-
|
|
457
|
-
migrate:
|
|
458
|
-
build: .
|
|
459
|
-
command: ["bun", "run", "db:migrate"]
|
|
460
|
-
environment:
|
|
461
|
-
DATABASE_URL: postgresql://${POSTGRES_USER:-echo}:${POSTGRES_PASSWORD:-changeme}@postgres:5432/${POSTGRES_DB:-echo}
|
|
462
|
-
depends_on:
|
|
463
|
-
postgres:
|
|
464
|
-
condition: service_healthy
|
|
465
|
-
networks:
|
|
466
|
-
- echo-network
|
|
467
|
-
profiles:
|
|
468
|
-
- migrate
|
|
469
|
-
|
|
470
|
-
volumes:
|
|
471
|
-
postgres_data:
|
|
472
|
-
|
|
473
|
-
networks:
|
|
474
|
-
echo-network:
|
|
475
|
-
driver: bridge
|
|
476
|
-
```
|
|
477
|
-
If `docker-compose.yml` exists, add just the `migrate` service block to match the above.
|
|
478
|
-
|
|
479
|
-
**Step 2: Commit**
|
|
480
|
-
```bash
|
|
481
|
-
git add docker-compose.yml
|
|
482
|
-
|
|
483
|
-
git commit -m "chore: add docker migration service"
|
|
484
|
-
```
|
|
485
|
-
|
|
486
|
-
---
|
|
487
|
-
|
|
488
|
-
### Final verification (@superpowers:verification-before-completion)
|
|
489
|
-
|
|
490
|
-
**Step 1: Lint**
|
|
491
|
-
Run: `bun run lint`
|
|
492
|
-
Expected: same existing warning, no errors.
|
|
493
|
-
|
|
494
|
-
**Step 2: Targeted tests**
|
|
495
|
-
Run: `bun test lib/db/migrate.test.ts`
|
|
496
|
-
Expected: PASS (2 tests).
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# 用户登录设计说明
|
|
2
|
-
|
|
3
|
-
**日期:** 2026-01-02
|
|
4
|
-
**范围:** Story 4.2 用户登录(认证域)
|
|
5
|
-
|
|
6
|
-
## 目标
|
|
7
|
-
为已注册用户提供登录入口,使用 better-auth 内置凭据登录能力建立会话,并在已登录状态下自动重定向至仪表板。
|
|
8
|
-
|
|
9
|
-
## 架构与边界
|
|
10
|
-
- **认证域(better-auth)**:登录、会话与 Cookie 由 better-auth 内置端点处理。
|
|
11
|
-
- **业务域**:登录成功后仅负责前端跳转与展示,不参与会话生成。
|
|
12
|
-
|
|
13
|
-
## 登录流程
|
|
14
|
-
1. 访问 `/login` 时,服务端调用 `auth.api.getSession`,若已有会话则重定向 `/dashboard`。
|
|
15
|
-
2. 前端表单提交至 `/api/auth/sign-in/email`,携带 `email`、`password`、`rememberMe`。
|
|
16
|
-
3. better-auth 验证凭据并建立会话,设置 HTTP-only Cookie。
|
|
17
|
-
4. 前端收到成功响应后跳转 `/dashboard`。
|
|
18
|
-
|
|
19
|
-
## 记住我
|
|
20
|
-
- 表单提供“记住我”选项,勾选时发送 `rememberMe: true`。
|
|
21
|
-
- 会话有效期目标为 30 天;如需显式配置,按 better-auth 配置项调整。
|
|
22
|
-
|
|
23
|
-
## 错误处理
|
|
24
|
-
- 登录失败统一提示“邮箱或密码错误”,避免泄露账号存在性。
|
|
25
|
-
- 客户端仅做基础校验(必填、邮箱格式),服务端由 better-auth 处理最终校验。
|
|
26
|
-
|
|
27
|
-
## 前端
|
|
28
|
-
- 页面:`app/(auth)/login/page.tsx`
|
|
29
|
-
- 表单组件:`components/auth/login-form.tsx`
|
|
30
|
-
- 交互:提交中禁用按钮,失败提示通用错误;成功跳转仪表板。
|
|
31
|
-
|
|
32
|
-
## 测试范围
|
|
33
|
-
- UI:成功登录后跳转;错误登录显示通用错误提示。
|
|
34
|
-
- 登录页访问:已登录用户自动重定向。
|
|
35
|
-
|
|
36
|
-
## 待确认
|
|
37
|
-
- better-auth 对 `rememberMe` 的默认会话时长与显式配置项。
|