@nexttylabs/echo 0.4.0 → 0.6.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.
Files changed (262) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/app/(dashboard)/admin/feedback/[id]/edit/page.tsx +12 -6
  3. package/app/(dashboard)/admin/feedback/new/page.tsx +19 -17
  4. package/app/(dashboard)/admin/layout.tsx +16 -6
  5. package/app/(dashboard)/layout.tsx +4 -2
  6. package/app/(dashboard)/settings/api-keys/page.tsx +13 -3
  7. package/app/(dashboard)/settings/layout.tsx +25 -2
  8. package/app/(dashboard)/settings/organization/page.tsx +8 -9
  9. package/app/(public)/[organizationSlug]/roadmap/page.tsx +19 -1
  10. package/app/api/admin/backup/route.ts +22 -4
  11. package/app/api/auth/register/handler.ts +1 -2
  12. package/app/api/feedback/[id]/comments/[commentId]/route.ts +13 -4
  13. package/app/api/feedback/[id]/reclassify/route.ts +4 -4
  14. package/app/api/organizations/handler.ts +2 -4
  15. package/components/settings/settings-sidebar.tsx +4 -4
  16. package/hooks/use-organization.tsx +116 -0
  17. package/hooks/use-permissions.ts +24 -11
  18. package/lib/auth/config.ts +0 -7
  19. package/lib/auth/organization.ts +20 -0
  20. package/lib/auth/permissions.ts +10 -0
  21. package/lib/db/migrations/0000_needy_leech.sql +335 -0
  22. package/lib/db/migrations/meta/0000_snapshot.json +2186 -1
  23. package/lib/db/migrations/meta/_journal.json +2 -135
  24. package/lib/db/schema/auth.ts +0 -1
  25. package/lib/db/schema/index.ts +0 -1
  26. package/lib/portal/public-context.tsx +5 -0
  27. package/package.json +20 -1
  28. package/.changeset/README.md +0 -21
  29. package/.changeset/config.json +0 -11
  30. package/.changeset/cozy-ghosts-care.md +0 -5
  31. package/.changeset/sharp-lines-stand.md +0 -5
  32. package/.changeset/sour-doodles-eat.md +0 -5
  33. package/.changeset/tender-moose-shop.md +0 -5
  34. package/.github/pull_request_template.md +0 -13
  35. package/.github/workflows/ci.yml +0 -41
  36. package/.github/workflows/publish.yml +0 -44
  37. package/.github/workflows/release.yml +0 -73
  38. package/AGENTS.md +0 -92
  39. package/Dockerfile +0 -57
  40. package/Makefile +0 -77
  41. package/bun.lock +0 -2503
  42. package/components/portal/project-switcher.tsx +0 -20
  43. package/docker-compose.dev.yml +0 -26
  44. package/docker-compose.yml +0 -98
  45. package/docs/architecture.md +0 -259
  46. package/docs/component-inventory.md +0 -261
  47. package/docs/database-migrations.md +0 -76
  48. package/docs/development-guide.md +0 -209
  49. package/docs/e2e-user-flows.csv +0 -31
  50. package/docs/er-diagram-feedback.mmd +0 -138
  51. package/docs/er-diagram.mmd +0 -281
  52. package/docs/i18n-check-report.md +0 -296
  53. package/docs/index.md +0 -214
  54. package/docs/logic-chain.md +0 -94
  55. package/docs/plans/2026-01-02-database-migration-scripts.md +0 -496
  56. package/docs/plans/2026-01-02-user-login-design.md +0 -37
  57. package/docs/plans/2026-01-02-user-login.md +0 -437
  58. package/docs/plans/2026-01-02-user-registration-design.md +0 -47
  59. package/docs/plans/2026-01-02-user-registration.md +0 -628
  60. package/docs/plans/2026-01-03-roles-permissions-design.md +0 -20
  61. package/docs/plans/2026-01-03-roles-permissions.md +0 -266
  62. package/docs/plans/2026-01-05-authentication-middleware.md +0 -207
  63. package/docs/plans/2026-01-05-member-removal.md +0 -186
  64. package/docs/plans/2026-01-05-organization-creation.md +0 -374
  65. package/docs/plans/2026-01-05-rbac-middleware.md +0 -112
  66. package/docs/plans/2026-01-05-role-configuration.md +0 -441
  67. package/docs/plans/2026-01-06-file-upload-support.md +0 -804
  68. package/docs/plans/2026-01-06-permission-check-hook.md +0 -155
  69. package/docs/plans/2026-01-06-resource-ownership-check.md +0 -231
  70. package/docs/plans/2026-01-07-feedback-tracking-link.md +0 -459
  71. package/docs/plans/2026-01-09-logout-redirect-design.md +0 -52
  72. package/docs/plans/2026-01-09-phase2-3-plan.md +0 -654
  73. package/docs/plans/2026-01-09-portal-execution-plan.md +0 -408
  74. package/docs/plans/2026-01-09-project-delete-feature-design.md +0 -163
  75. package/docs/plans/2026-01-09-project-delete-implementation.md +0 -451
  76. package/docs/plans/2026-01-09-project-edit-delete-design.md +0 -52
  77. package/docs/plans/2026-01-09-settings-center-design.md +0 -114
  78. package/docs/plans/2026-01-09-settings-center.md +0 -948
  79. package/docs/plans/2026-01-10-organization-only-design.md +0 -66
  80. package/docs/plans/2026-01-10-organization-only-implementation.md +0 -433
  81. package/docs/plans/2026-01-10-portal-settings-restructure-plan.md +0 -18
  82. package/docs/plans/2026-01-10-project-settings-tabs-design-implementation.md +0 -296
  83. package/docs/plans/2026-01-14-e2e-playwright-feedback.md +0 -173
  84. package/docs/plans/2026-01-15-feedback-management-org-context-design.md +0 -82
  85. package/docs/plans/2026-01-15-feedback-management-org-context-implementation-plan.md +0 -521
  86. package/docs/plans/2026-01-16-admin-feedback-filters-design.md +0 -75
  87. package/docs/plans/2026-01-16-admin-feedback-filters-implementation.md +0 -293
  88. package/docs/plans/2026-01-16-admin-feedback-route-consolidation.md +0 -180
  89. package/docs/plans/2026-01-16-e2e-test-fixes.md +0 -158
  90. package/docs/plans/2026-01-17-admin-feedback-filters.md +0 -214
  91. package/docs/plans/2026-01-17-admin-feedback-improvements.md +0 -453
  92. package/docs/plans/2026-01-18-changesets-design.md +0 -40
  93. package/docs/product_changes.md +0 -37
  94. package/docs/project-overview.md +0 -159
  95. package/docs/project-scan-report.json +0 -104
  96. package/docs/route-role-visibility.md +0 -51
  97. package/docs/source-tree-analysis.md +0 -150
  98. package/docs/testing/delete-project-manual-tests.md +0 -18
  99. package/docs/user-story-tracking.md +0 -191
  100. package/eslint.config.mjs +0 -19
  101. package/lib/db/migrations/.gitkeep +0 -0
  102. package/lib/db/migrations/0000_cynical_gladiator.sql +0 -53
  103. package/lib/db/migrations/0001_wandering_sunfire.sql +0 -27
  104. package/lib/db/migrations/0002_shallow_speedball.sql +0 -1
  105. package/lib/db/migrations/0003_add_org_description.sql +0 -1
  106. package/lib/db/migrations/0003_boring_wild_pack.sql +0 -13
  107. package/lib/db/migrations/0004_windy_tyrannus.sql +0 -27
  108. package/lib/db/migrations/0005_perpetual_doorman.sql +0 -5
  109. package/lib/db/migrations/0006_aberrant_captain_midlands.sql +0 -13
  110. package/lib/db/migrations/0007_clever_captain_cross.sql +0 -14
  111. package/lib/db/migrations/0008_sparkling_pandemic.sql +0 -2
  112. package/lib/db/migrations/0009_happy_black_tom.sql +0 -29
  113. package/lib/db/migrations/0010_kind_junta.sql +0 -8
  114. package/lib/db/migrations/0011_mute_squadron_supreme.sql +0 -25
  115. package/lib/db/migrations/0012_giant_power_man.sql +0 -24
  116. package/lib/db/migrations/0013_damp_titanium_man.sql +0 -17
  117. package/lib/db/migrations/0014_blue_alice.sql +0 -18
  118. package/lib/db/migrations/0015_webhook_tables.sql +0 -41
  119. package/lib/db/migrations/0016_github_integration.sql +0 -30
  120. package/lib/db/migrations/0016_overjoyed_ghost_rider.sql +0 -22
  121. package/lib/db/migrations/0017_slimy_inhumans.sql +0 -6
  122. package/lib/db/migrations/0018_same_spitfire.sql +0 -1
  123. package/lib/db/migrations/0019_jittery_loners.sql +0 -16
  124. package/lib/db/migrations/0019_remove_projects_add_org_settings.sql +0 -14
  125. package/lib/db/migrations/meta/0001_snapshot.json +0 -553
  126. package/lib/db/migrations/meta/0002_snapshot.json +0 -560
  127. package/lib/db/migrations/meta/0003_snapshot.json +0 -650
  128. package/lib/db/migrations/meta/0004_snapshot.json +0 -852
  129. package/lib/db/migrations/meta/0005_snapshot.json +0 -900
  130. package/lib/db/migrations/meta/0006_snapshot.json +0 -1011
  131. package/lib/db/migrations/meta/0007_snapshot.json +0 -1125
  132. package/lib/db/migrations/meta/0008_snapshot.json +0 -1146
  133. package/lib/db/migrations/meta/0009_snapshot.json +0 -1386
  134. package/lib/db/migrations/meta/0010_snapshot.json +0 -1419
  135. package/lib/db/migrations/meta/0011_snapshot.json +0 -1615
  136. package/lib/db/migrations/meta/0012_snapshot.json +0 -1805
  137. package/lib/db/migrations/meta/0013_snapshot.json +0 -1948
  138. package/lib/db/migrations/meta/0014_snapshot.json +0 -2082
  139. package/lib/db/migrations/meta/0015_snapshot.json +0 -2476
  140. package/lib/db/migrations/meta/0016_snapshot.json +0 -2633
  141. package/lib/db/migrations/meta/0017_snapshot.json +0 -2680
  142. package/lib/db/migrations/meta/0018_snapshot.json +0 -2686
  143. package/lib/db/migrations/meta/0019_snapshot.json +0 -2741
  144. package/lib/db/schema/projects.ts +0 -145
  145. package/lib/db/schema/user-profiles.ts +0 -31
  146. package/lib/validations/projects.ts +0 -49
  147. package/next-env.d.ts +0 -6
  148. package/playwright.config.ts +0 -44
  149. package/proxy.test.ts +0 -131
  150. package/proxy.ts +0 -116
  151. package/scripts/backup-db.sh +0 -57
  152. package/scripts/backup-db.ts +0 -24
  153. package/scripts/generate-openapi.ts +0 -22
  154. package/scripts/migration-helper.ts +0 -39
  155. package/scripts/pre-deploy.ts +0 -75
  156. package/scripts/restore-db.sh +0 -60
  157. package/scripts/rollback.ts +0 -72
  158. package/scripts/seed-tags.ts +0 -48
  159. package/tests/api/feedback-bulk.test.ts +0 -47
  160. package/tests/api/feedback-by-id.test.ts +0 -67
  161. package/tests/api/feedback-comments-route-import.test.ts +0 -26
  162. package/tests/api/feedback-create.test.ts +0 -71
  163. package/tests/api/feedback-delete.test.ts +0 -160
  164. package/tests/api/feedback-filter.test.ts +0 -250
  165. package/tests/api/feedback-list.test.ts +0 -234
  166. package/tests/api/feedback-route-assignee-condition.test.ts +0 -32
  167. package/tests/api/feedback-similar.test.ts +0 -46
  168. package/tests/api/feedback-sort.test.ts +0 -261
  169. package/tests/api/feedback-status-enum.test.ts +0 -49
  170. package/tests/api/feedback-status-filter.test.ts +0 -117
  171. package/tests/api/feedback-submit-on-behalf.test.ts +0 -269
  172. package/tests/api/feedback.test.ts +0 -175
  173. package/tests/api/identify-jwt.test.ts +0 -25
  174. package/tests/api/invitation-accept.test.ts +0 -213
  175. package/tests/api/organization-invitations.test.ts +0 -186
  176. package/tests/api/organization-members-list.test.ts +0 -79
  177. package/tests/api/organization-members.test.ts +0 -340
  178. package/tests/api/organizations.test.ts +0 -149
  179. package/tests/api/register.test.ts +0 -112
  180. package/tests/api/upload.test.ts +0 -103
  181. package/tests/api/vote.test.ts +0 -82
  182. package/tests/app/admin-feedback-detail-page.test.tsx +0 -25
  183. package/tests/app/admin-feedback-list-page.test.tsx +0 -25
  184. package/tests/app/admin-feedback-new-page.test.tsx +0 -25
  185. package/tests/app/health-route-helpers.test.ts +0 -27
  186. package/tests/app/login-page.test.ts +0 -26
  187. package/tests/app/portal-page.test.ts +0 -29
  188. package/tests/app/project-portal-overview.test.tsx +0 -25
  189. package/tests/app/widget-page-import.test.ts +0 -25
  190. package/tests/components/create-post-dialog-defaults.test.ts +0 -43
  191. package/tests/components/feedback/duplicate-suggestions-inline.test.tsx +0 -27
  192. package/tests/components/feedback/embedded-feedback-form.test.tsx +0 -96
  193. package/tests/components/feedback/feedback-detail.test.tsx +0 -25
  194. package/tests/components/feedback/feedback-stats.test.tsx +0 -49
  195. package/tests/components/feedback-bulk-actions.test.tsx +0 -39
  196. package/tests/components/feedback-i18n-keys.test.ts +0 -70
  197. package/tests/components/feedback-list-controls-compile.test.ts +0 -25
  198. package/tests/components/feedback-list-controls.test.tsx +0 -204
  199. package/tests/components/feedback-list-item.test.tsx +0 -67
  200. package/tests/components/landing/hero.test.tsx +0 -46
  201. package/tests/components/layout/language-switcher.test.tsx +0 -25
  202. package/tests/components/layout/sidebar.test.tsx +0 -157
  203. package/tests/components/login-form.test.ts +0 -25
  204. package/tests/components/organization-form.test.ts +0 -32
  205. package/tests/components/organization-switcher.test.ts +0 -25
  206. package/tests/components/pagination.test.tsx +0 -43
  207. package/tests/components/portal-overview.test.tsx +0 -25
  208. package/tests/components/profile-form.test.tsx +0 -139
  209. package/tests/components/role-selector.test.ts +0 -31
  210. package/tests/components/status-chart.test.tsx +0 -90
  211. package/tests/e2e/auth.e2e.ts +0 -323
  212. package/tests/e2e/feedback-actions.e2e.ts +0 -471
  213. package/tests/e2e/feedback-attachment.e2e.ts +0 -168
  214. package/tests/e2e/feedback-customer.e2e.ts +0 -226
  215. package/tests/e2e/feedback-management.e2e.ts +0 -565
  216. package/tests/e2e/feedback-submit.e2e.ts +0 -133
  217. package/tests/e2e/feedback-view.e2e.ts +0 -297
  218. package/tests/e2e/fixtures/test-data.ts +0 -235
  219. package/tests/e2e/health-check.e2e.ts +0 -230
  220. package/tests/e2e/helpers/test-utils-helpers.test.ts +0 -43
  221. package/tests/e2e/helpers/test-utils.ts +0 -298
  222. package/tests/e2e/integration-placeholders.e2e.ts +0 -199
  223. package/tests/e2e/organization.e2e.ts +0 -292
  224. package/tests/e2e/permissions.e2e.ts +0 -424
  225. package/tests/e2e/project-widget.e2e.ts +0 -63
  226. package/tests/feedback/filters.test.ts +0 -29
  227. package/tests/hooks/use-permissions.test.ts +0 -52
  228. package/tests/lib/ai/classifier.test.ts +0 -104
  229. package/tests/lib/ai/duplicate-detector.test.ts +0 -234
  230. package/tests/lib/attachments-schema.test.ts +0 -30
  231. package/tests/lib/auth/session.test.ts +0 -49
  232. package/tests/lib/auth-client.test.ts +0 -37
  233. package/tests/lib/auth-config.test.ts +0 -26
  234. package/tests/lib/feedback-prefill.test.ts +0 -52
  235. package/tests/lib/feedback-processor.test.ts +0 -41
  236. package/tests/lib/feedback-schema.test.ts +0 -33
  237. package/tests/lib/file-validator.test.ts +0 -48
  238. package/tests/lib/get-feedback-by-id.test.ts +0 -37
  239. package/tests/lib/invitations.test.ts +0 -35
  240. package/tests/lib/login-schema.test.ts +0 -36
  241. package/tests/lib/org-context.test.ts +0 -95
  242. package/tests/lib/organization-access.test.ts +0 -44
  243. package/tests/lib/organization-member-role-schema.test.ts +0 -41
  244. package/tests/lib/permissions.test.ts +0 -88
  245. package/tests/lib/portal-analytics.test.ts +0 -25
  246. package/tests/lib/portal-contributors.test.ts +0 -25
  247. package/tests/lib/portal-copy.test.ts +0 -27
  248. package/tests/lib/portal-i18n.test.ts +0 -30
  249. package/tests/lib/portal-leaderboard-settings.test.ts +0 -25
  250. package/tests/lib/portal-modules.test.ts +0 -25
  251. package/tests/lib/portal-seo.test.ts +0 -25
  252. package/tests/lib/portal-sharing.test.ts +0 -25
  253. package/tests/lib/portal-sorting.test.ts +0 -25
  254. package/tests/lib/portal-theme.test.ts +0 -25
  255. package/tests/lib/rate-limit.test.ts +0 -142
  256. package/tests/lib/resolve-locale.test.ts +0 -34
  257. package/tests/lib/services/backup.test.ts +0 -145
  258. package/tests/lib/user-organizations.test.ts +0 -42
  259. package/tests/lib/user-role-schema.test.ts +0 -33
  260. package/tests/lib/user-schema.test.ts +0 -25
  261. package/tests/setup.ts +0 -74
  262. 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` 的默认会话时长与显式配置项。