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