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