@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,266 +0,0 @@
1
- # Roles and Permissions Implementation Plan
2
-
3
- > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
-
5
- **Goal:** Add a minimal RBAC module with a `customer` role and keep user schema and validation aligned with the role model.
6
-
7
- **Architecture:** Implement a pure RBAC helper module in `lib/auth/permissions.ts`, update Drizzle `user` table schema to include a `role` column with default `customer`, and add a Zod role schema in `lib/validations/auth.ts`. Cover behavior with focused bun tests.
8
-
9
- **Tech Stack:** Next.js, TypeScript, Bun test runner, Drizzle ORM, Zod.
10
-
11
- ### Task 1: Permissions tests
12
-
13
- **Files:**
14
- - Create: `tests/lib/permissions.test.ts`
15
-
16
- **Step 1: Write the failing test**
17
-
18
- ```typescript
19
- import { describe, expect, it } from "bun:test";
20
- import {
21
- PERMISSIONS,
22
- ROLE_PERMISSIONS,
23
- hasPermission,
24
- canSubmitOnBehalf,
25
- } from "@/lib/auth/permissions";
26
-
27
- describe("permissions", () => {
28
- it("maps roles to expected permissions", () => {
29
- expect(ROLE_PERMISSIONS.admin).toEqual(
30
- expect.arrayContaining([
31
- PERMISSIONS.CREATE_FEEDBACK,
32
- PERMISSIONS.SUBMIT_ON_BEHALF,
33
- PERMISSIONS.DELETE_FEEDBACK,
34
- PERMISSIONS.MANAGE_ORG,
35
- ]),
36
- );
37
-
38
- expect(ROLE_PERMISSIONS.product_manager).toEqual(
39
- expect.arrayContaining([
40
- PERMISSIONS.CREATE_FEEDBACK,
41
- PERMISSIONS.SUBMIT_ON_BEHALF,
42
- PERMISSIONS.DELETE_FEEDBACK,
43
- ]),
44
- );
45
-
46
- expect(ROLE_PERMISSIONS.developer).toEqual(
47
- expect.arrayContaining([PERMISSIONS.CREATE_FEEDBACK]),
48
- );
49
-
50
- expect(ROLE_PERMISSIONS.customer_support).toEqual(
51
- expect.arrayContaining([
52
- PERMISSIONS.CREATE_FEEDBACK,
53
- PERMISSIONS.SUBMIT_ON_BEHALF,
54
- ]),
55
- );
56
-
57
- expect(ROLE_PERMISSIONS.customer).toEqual(
58
- expect.arrayContaining([PERMISSIONS.CREATE_FEEDBACK]),
59
- );
60
- });
61
-
62
- it("checks permissions by role", () => {
63
- expect(hasPermission("admin", PERMISSIONS.MANAGE_ORG)).toBe(true);
64
- expect(hasPermission("developer", PERMISSIONS.DELETE_FEEDBACK)).toBe(false);
65
- expect(hasPermission("customer", PERMISSIONS.CREATE_FEEDBACK)).toBe(true);
66
- });
67
-
68
- it("checks submit-on-behalf permission", () => {
69
- expect(canSubmitOnBehalf("customer_support")).toBe(true);
70
- expect(canSubmitOnBehalf("customer")).toBe(false);
71
- });
72
- });
73
- ```
74
-
75
- **Step 2: Run test to verify it fails**
76
-
77
- Run: `bun test tests/lib/permissions.test.ts`
78
- Expected: FAIL because `@/lib/auth/permissions` does not exist.
79
-
80
- **Step 3: Commit**
81
-
82
- ```bash
83
- git add tests/lib/permissions.test.ts
84
- git commit -m "test: add permissions coverage"
85
- ```
86
-
87
- ### Task 2: Implement permissions module
88
-
89
- **Files:**
90
- - Create: `lib/auth/permissions.ts`
91
-
92
- **Step 1: Write minimal implementation**
93
-
94
- ```typescript
95
- export type UserRole =
96
- | "admin"
97
- | "product_manager"
98
- | "developer"
99
- | "customer_support"
100
- | "customer";
101
-
102
- export const PERMISSIONS = {
103
- CREATE_FEEDBACK: "create_feedback",
104
- SUBMIT_ON_BEHALF: "submit_on_behalf",
105
- DELETE_FEEDBACK: "delete_feedback",
106
- MANAGE_ORG: "manage_org",
107
- } as const;
108
-
109
- export type Permission = (typeof PERMISSIONS)[keyof typeof PERMISSIONS];
110
-
111
- export const ROLE_PERMISSIONS: Record<UserRole, Permission[]> = {
112
- admin: [
113
- PERMISSIONS.CREATE_FEEDBACK,
114
- PERMISSIONS.SUBMIT_ON_BEHALF,
115
- PERMISSIONS.DELETE_FEEDBACK,
116
- PERMISSIONS.MANAGE_ORG,
117
- ],
118
- product_manager: [
119
- PERMISSIONS.CREATE_FEEDBACK,
120
- PERMISSIONS.SUBMIT_ON_BEHALF,
121
- PERMISSIONS.DELETE_FEEDBACK,
122
- ],
123
- developer: [PERMISSIONS.CREATE_FEEDBACK],
124
- customer_support: [
125
- PERMISSIONS.CREATE_FEEDBACK,
126
- PERMISSIONS.SUBMIT_ON_BEHALF,
127
- ],
128
- customer: [PERMISSIONS.CREATE_FEEDBACK],
129
- };
130
-
131
- export function hasPermission(role: UserRole, permission: Permission): boolean {
132
- return ROLE_PERMISSIONS[role]?.includes(permission) ?? false;
133
- }
134
-
135
- export function canSubmitOnBehalf(role: UserRole): boolean {
136
- return hasPermission(role, PERMISSIONS.SUBMIT_ON_BEHALF);
137
- }
138
- ```
139
-
140
- **Step 2: Run test to verify it passes**
141
-
142
- Run: `bun test tests/lib/permissions.test.ts`
143
- Expected: PASS
144
-
145
- **Step 3: Commit**
146
-
147
- ```bash
148
- git add lib/auth/permissions.ts
149
- git commit -m "feat: add rbac permissions module"
150
- ```
151
-
152
- ### Task 3: Role validation
153
-
154
- **Files:**
155
- - Create: `tests/lib/user-role-schema.test.ts`
156
- - Modify: `lib/validations/auth.ts`
157
-
158
- **Step 1: Write the failing test**
159
-
160
- ```typescript
161
- import { describe, expect, it } from "bun:test";
162
- import { userRoleSchema } from "@/lib/validations/auth";
163
-
164
- describe("userRoleSchema", () => {
165
- it("accepts known roles", () => {
166
- expect(userRoleSchema.safeParse("customer").success).toBe(true);
167
- expect(userRoleSchema.safeParse("admin").success).toBe(true);
168
- expect(userRoleSchema.safeParse("product_manager").success).toBe(true);
169
- expect(userRoleSchema.safeParse("developer").success).toBe(true);
170
- expect(userRoleSchema.safeParse("customer_support").success).toBe(true);
171
- });
172
-
173
- it("rejects unknown roles", () => {
174
- expect(userRoleSchema.safeParse("guest").success).toBe(false);
175
- });
176
- });
177
- ```
178
-
179
- **Step 2: Run test to verify it fails**
180
-
181
- Run: `bun test tests/lib/user-role-schema.test.ts`
182
- Expected: FAIL because `userRoleSchema` is missing.
183
-
184
- **Step 3: Write minimal implementation**
185
-
186
- ```typescript
187
- import { z } from "zod";
188
-
189
- export const userRoleSchema = z.enum([
190
- "admin",
191
- "product_manager",
192
- "developer",
193
- "customer_support",
194
- "customer",
195
- ]);
196
-
197
- export type UserRoleInput = z.infer<typeof userRoleSchema>;
198
- ```
199
-
200
- (Add the above to `lib/validations/auth.ts` while keeping existing schemas.)
201
-
202
- **Step 4: Run test to verify it passes**
203
-
204
- Run: `bun test tests/lib/user-role-schema.test.ts`
205
- Expected: PASS
206
-
207
- **Step 5: Commit**
208
-
209
- ```bash
210
- git add tests/lib/user-role-schema.test.ts lib/validations/auth.ts
211
- git commit -m "feat: add user role validation"
212
- ```
213
-
214
- ### Task 4: User schema role column
215
-
216
- **Files:**
217
- - Create: `tests/lib/user-schema.test.ts`
218
- - Modify: `lib/db/schema/auth.ts`
219
-
220
- **Step 1: Write the failing test**
221
-
222
- ```typescript
223
- import { describe, expect, it } from "bun:test";
224
- import { user } from "@/lib/db/schema";
225
-
226
- describe("user schema", () => {
227
- it("includes role column", () => {
228
- expect(user.role).toBeDefined();
229
- });
230
- });
231
- ```
232
-
233
- **Step 2: Run test to verify it fails**
234
-
235
- Run: `bun test tests/lib/user-schema.test.ts`
236
- Expected: FAIL because `user.role` is undefined.
237
-
238
- **Step 3: Write minimal implementation**
239
-
240
- ```typescript
241
- role: text("role").notNull().default("customer"),
242
- ```
243
-
244
- (Add to `lib/db/schema/auth.ts` in the `user` table definition.)
245
-
246
- **Step 4: Run test to verify it passes**
247
-
248
- Run: `bun test tests/lib/user-schema.test.ts`
249
- Expected: PASS
250
-
251
- **Step 5: Commit**
252
-
253
- ```bash
254
- git add tests/lib/user-schema.test.ts lib/db/schema/auth.ts
255
- git commit -m "feat: add user role column"
256
- ```
257
-
258
- ---
259
-
260
- **Plan complete and saved to `docs/plans/2026-01-03-roles-permissions.md`. Two execution options:**
261
-
262
- **1. Subagent-Driven (this session)** - I dispatch fresh subagent per task, review between tasks, fast iteration
263
-
264
- **2. Parallel Session (separate)** - Open new session with executing-plans, batch execution with checkpoints
265
-
266
- **Which approach?**
@@ -1,207 +0,0 @@
1
- # Authentication Middleware Implementation Plan
2
-
3
- > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
-
5
- **Goal:** Add session-aware auth guarding in Next.js middleware so `/dashboard` and `/feedback` require login while `/login`, `/register`, `/invite/*`, and `/api/auth/*` stay public, without breaking existing request-id/logging behavior.
6
-
7
- **Architecture:** Keep the current global middleware (for `x-request-id` and request logging) and add a protected-route check inside it. Implement a small `getServerSession` helper that wraps `better-auth`’s `auth.api.getSession({ headers })` so middleware can request session data from `NextRequest` headers. Only protected routes trigger session checks; public routes pass through.
8
-
9
- **Tech Stack:** Next.js App Router middleware, TypeScript, better-auth, Bun test runner.
10
-
11
- ### Task 1: Add middleware auth behavior tests (failing first)
12
-
13
- **Files:**
14
- - Modify: `middleware.test.ts`
15
-
16
- **Step 1: Write the failing tests**
17
-
18
- Add tests that cover:
19
- - Protected route unauthenticated → redirect to `/login`
20
- - Public route unauthenticated → allowed
21
- - Protected route authenticated → allowed
22
-
23
- ```ts
24
- import { describe, it, expect, mock } from "bun:test";
25
- import { NextRequest } from "next/server";
26
-
27
- mock.module("@/lib/auth/session", () => ({
28
- getServerSession: async (req: NextRequest) => {
29
- const isAuthed = req.headers.get("x-test-auth") === "1";
30
- return isAuthed ? { user: { id: "u_test" } } : null;
31
- },
32
- }));
33
-
34
- const { middleware } = await import("./middleware");
35
-
36
- describe("middleware auth", () => {
37
- it("redirects unauthenticated users from protected routes", async () => {
38
- const req = new NextRequest("http://localhost/dashboard");
39
- const res = await middleware(req);
40
- expect(res.headers.get("location")).toBe("http://localhost/login");
41
- });
42
-
43
- it("allows unauthenticated users on public routes", async () => {
44
- const req = new NextRequest("http://localhost/login");
45
- const res = await middleware(req);
46
- expect(res.headers.get("location")).toBeNull();
47
- });
48
-
49
- it("allows authenticated users on protected routes", async () => {
50
- const req = new NextRequest("http://localhost/dashboard", {
51
- headers: { "x-test-auth": "1" },
52
- });
53
- const res = await middleware(req);
54
- expect(res.headers.get("location")).toBeNull();
55
- });
56
- });
57
- ```
58
-
59
- **Step 2: Run test to verify it fails**
60
-
61
- Run: `bun test middleware.test.ts`
62
- Expected: FAIL because middleware does not yet enforce auth redirect.
63
-
64
- **Step 3: Commit**
65
-
66
- ```bash
67
- git add middleware.test.ts
68
- git commit -m "test: cover auth redirect behavior in middleware"
69
- ```
70
-
71
- ### Task 2: Implement getServerSession helper
72
-
73
- **Files:**
74
- - Create: `lib/auth/session.ts`
75
-
76
- **Step 1: Write the failing test (optional if skipping unit test)**
77
-
78
- If adding a unit test, create `tests/auth/session.test.ts` verifying `getServerSession` returns `null` when no session is found. Otherwise, proceed to implementation and rely on middleware tests.
79
-
80
- **Step 2: Run test to verify it fails**
81
-
82
- Run (if test added): `bun test tests/auth/session.test.ts`
83
- Expected: FAIL because helper does not exist.
84
-
85
- **Step 3: Write minimal implementation**
86
-
87
- ```ts
88
- import type { NextRequest } from "next/server";
89
- import { auth } from "@/lib/auth/config";
90
-
91
- export async function getServerSession(req: NextRequest) {
92
- return auth.api.getSession({ headers: req.headers });
93
- }
94
- ```
95
-
96
- **Step 4: Run test to verify it passes**
97
-
98
- Run: `bun test tests/auth/session.test.ts`
99
- Expected: PASS (if test added).
100
-
101
- **Step 5: Commit**
102
-
103
- ```bash
104
- git add lib/auth/session.ts tests/auth/session.test.ts
105
- git commit -m "feat: add getServerSession helper for middleware"
106
- ```
107
-
108
- ### Task 3: Enforce authentication in middleware
109
-
110
- **Files:**
111
- - Modify: `middleware.ts`
112
-
113
- **Step 1: Write the failing test (already in Task 1)**
114
-
115
- Use the tests from Task 1; they should still be failing.
116
-
117
- **Step 2: Run test to verify it fails**
118
-
119
- Run: `bun test middleware.test.ts`
120
- Expected: FAIL because `/dashboard` is not redirected yet.
121
-
122
- **Step 3: Write minimal implementation**
123
-
124
- Update middleware to:
125
- - Define `publicRoutes` and `protectedRoutes` lists
126
- - Skip session check for public routes
127
- - For protected routes, call `getServerSession(req)` and redirect if falsy
128
- - Preserve existing `x-request-id` and logging behavior
129
-
130
- ```ts
131
- import { NextRequest, NextResponse } from "next/server";
132
- import { generateRequestId } from "@/lib/middleware/request-id";
133
- import { logRequest, logResponse } from "@/lib/middleware/request-logger";
134
- import { getServerSession } from "@/lib/auth/session";
135
-
136
- const publicRoutes = ["/login", "/register", "/invite", "/invite/", "/api/auth"];
137
- const protectedRoutes = ["/dashboard", "/feedback"];
138
-
139
- function isRouteMatch(pathname: string, routes: string[]) {
140
- return routes.some((route) => pathname === route || pathname.startsWith(route));
141
- }
142
-
143
- export async function middleware(req: NextRequest) {
144
- const startTime = Date.now();
145
- const reqId = req.headers.get("x-request-id") || generateRequestId();
146
-
147
- const requestHeaders = new Headers(req.headers);
148
- requestHeaders.set("x-request-id", reqId);
149
-
150
- logRequest(new Request(req.url, { method: req.method, headers: requestHeaders }));
151
-
152
- const pathname = req.nextUrl.pathname;
153
- const isPublic = isRouteMatch(pathname, publicRoutes);
154
- const isProtected = isRouteMatch(pathname, protectedRoutes);
155
-
156
- let response = NextResponse.next({
157
- request: {
158
- headers: requestHeaders,
159
- },
160
- });
161
-
162
- if (isProtected && !isPublic) {
163
- const session = await getServerSession(req);
164
- if (!session) {
165
- response = NextResponse.redirect(new URL("/login", req.url));
166
- }
167
- }
168
-
169
- response.headers.set("x-request-id", reqId);
170
- const duration = Date.now() - startTime;
171
- logResponse(reqId, response.status, duration);
172
-
173
- return response;
174
- }
175
- ```
176
-
177
- **Step 4: Run test to verify it passes**
178
-
179
- Run: `bun test middleware.test.ts`
180
- Expected: PASS
181
-
182
- **Step 5: Commit**
183
-
184
- ```bash
185
- git add middleware.ts
186
- git commit -m "feat: enforce auth on protected routes in middleware"
187
- ```
188
-
189
- ### Task 4: Full validation
190
-
191
- **Files:**
192
- - None
193
-
194
- **Step 1: Run lint and tests**
195
-
196
- Run: `bun run lint`
197
- Expected: PASS
198
-
199
- Run: `bun test`
200
- Expected: PASS
201
-
202
- **Step 2: Commit (if any fixes were made)**
203
-
204
- ```bash
205
- git add .
206
- git commit -m "chore: fix lint/test issues"
207
- ```
@@ -1,186 +0,0 @@
1
- # Member Removal Implementation Plan
2
-
3
- > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
-
5
- **Goal:** Allow organization admins to remove members while preventing removal of the last admin or self-removal.
6
-
7
- **Architecture:** Add a DELETE handler using the existing build*Handler pattern and Drizzle queries for role checks and deletion. Render a server-fetched members list in the org settings page, and use a client component with a confirmation dialog to call the DELETE API and update local state.
8
-
9
- **Tech Stack:** Next.js App Router (RSC), TypeScript, Drizzle ORM, Bun test runner, shadcn/ui components.
10
-
11
- ### Task 1: Add member removal API (handler + route)
12
-
13
- **Files:**
14
- - Create: `app/api/organizations/[orgId]/members/[memberId]/handler.ts`
15
- - Create: `app/api/organizations/[orgId]/members/[memberId]/route.ts`
16
- - Test: `tests/api/organization-members.test.ts`
17
-
18
- **Step 1: Write the failing tests**
19
-
20
- ```ts
21
- import { describe, expect, it } from "bun:test";
22
- import { buildRemoveMemberHandler } from "@/app/api/organizations/[orgId]/members/[memberId]/handler";
23
- import { organizationMembers } from "@/lib/db/schema";
24
-
25
- // Tests:
26
- // - 401 when unauthenticated
27
- // - 403 when requester is not admin
28
- // - 404 when target member not found
29
- // - 400 when target is last admin (message: 组织至少需要一个管理员)
30
- // - 400 when requester tries to remove self (message: 不能移除自己)
31
- // - 200 on success and delete called
32
- ```
33
-
34
- **Step 2: Run test to verify it fails**
35
-
36
- Run: `bun test tests/api/organization-members.test.ts`
37
- Expected: FAIL with "buildRemoveMemberHandler is not defined" (or module not found).
38
-
39
- **Step 3: Write minimal implementation**
40
-
41
- ```ts
42
- export function buildRemoveMemberHandler(deps: RemoveMemberDeps) {
43
- return async function DELETE(req: Request, context: { params: { orgId: string; memberId: string } }) {
44
- const session = await deps.auth.api.getSession({ headers: req.headers });
45
- if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
46
-
47
- const { orgId, memberId } = await Promise.resolve(context.params);
48
-
49
- if (memberId === session.user.id) {
50
- return NextResponse.json({ error: "不能移除自己" }, { status: 400 });
51
- }
52
-
53
- const [requester] = await deps.db
54
- .select()
55
- .from(organizationMembers)
56
- .where(and(eq(organizationMembers.organizationId, orgId), eq(organizationMembers.userId, session.user.id)))
57
- .limit(1);
58
-
59
- if (!requester || requester.role !== "admin") {
60
- return NextResponse.json({ error: "Forbidden" }, { status: 403 });
61
- }
62
-
63
- const [target] = await deps.db
64
- .select()
65
- .from(organizationMembers)
66
- .where(and(eq(organizationMembers.organizationId, orgId), eq(organizationMembers.userId, memberId)))
67
- .limit(1);
68
-
69
- if (!target) {
70
- return NextResponse.json({ error: "Member not found" }, { status: 404 });
71
- }
72
-
73
- const [adminCount] = await deps.db
74
- .select({ count: count() })
75
- .from(organizationMembers)
76
- .where(and(eq(organizationMembers.organizationId, orgId), eq(organizationMembers.role, "admin")));
77
-
78
- if (target.role === "admin" && adminCount.count === 1) {
79
- return NextResponse.json({ error: "组织至少需要一个管理员" }, { status: 400 });
80
- }
81
-
82
- await deps.db
83
- .delete(organizationMembers)
84
- .where(and(eq(organizationMembers.organizationId, orgId), eq(organizationMembers.userId, memberId)));
85
-
86
- return NextResponse.json({ message: "成员已移除" }, { status: 200 });
87
- };
88
- }
89
- ```
90
-
91
- **Step 4: Run test to verify it passes**
92
-
93
- Run: `bun test tests/api/organization-members.test.ts`
94
- Expected: PASS
95
-
96
- **Step 5: Commit**
97
-
98
- ```bash
99
- git add app/api/organizations/[orgId]/members/[memberId]/handler.ts app/api/organizations/[orgId]/members/[memberId]/route.ts tests/api/organization-members.test.ts
100
- git commit -m "feat: add member removal API"
101
- ```
102
-
103
- ### Task 2: Render member list and removal UI
104
-
105
- **Files:**
106
- - Modify: `app/(dashboard)/settings/organizations/[orgId]/members/page.tsx`
107
- - Create: `components/settings/organization-members-list.tsx`
108
-
109
- **Step 1: Add a server-side members query**
110
-
111
- ```ts
112
- const members = db
113
- ? await db
114
- .select({
115
- userId: organizationMembers.userId,
116
- role: organizationMembers.role,
117
- name: user.name,
118
- email: user.email,
119
- })
120
- .from(organizationMembers)
121
- .leftJoin(user, eq(user.id, organizationMembers.userId))
122
- .where(eq(organizationMembers.organizationId, params.orgId))
123
- : [];
124
- ```
125
-
126
- **Step 2: Implement client list with removal**
127
-
128
- ```tsx
129
- <AlertDialog>
130
- <AlertDialogTrigger asChild>
131
- <Button variant="destructive">移除</Button>
132
- </AlertDialogTrigger>
133
- <AlertDialogContent>
134
- <AlertDialogTitle>确认移除?</AlertDialogTitle>
135
- <AlertDialogAction onClick={() => removeMember(member.userId)}>确认</AlertDialogAction>
136
- </AlertDialogContent>
137
- </AlertDialog>
138
- ```
139
-
140
- **Step 3: Disable remove for self**
141
-
142
- ```ts
143
- const isSelf = member.userId === currentUserId;
144
- <Button disabled={isSelf}>移除</Button>
145
- ```
146
-
147
- **Step 4: Run UI smoke check**
148
-
149
- Run: `bun dev`
150
- Expected: Members page shows list, remove button opens confirmation, success removes row from UI.
151
-
152
- **Step 5: Commit**
153
-
154
- ```bash
155
- git add app/(dashboard)/settings/organizations/[orgId]/members/page.tsx components/settings/organization-members-list.tsx
156
- git commit -m "feat: render members list with removal UI"
157
- ```
158
-
159
- ### Task 3: Edge cases + validation
160
-
161
- **Files:**
162
- - Modify: `tests/api/organization-members.test.ts`
163
-
164
- **Step 1: Add edge-case tests (self-removal + missing member)**
165
-
166
- ```ts
167
- it("rejects removing self", async () => {
168
- // expect 400 "不能移除自己"
169
- });
170
-
171
- it("returns 404 when target member missing", async () => {
172
- // expect 404
173
- });
174
- ```
175
-
176
- **Step 2: Run test suite**
177
-
178
- Run: `bun test tests/api/organization-members.test.ts`
179
- Expected: PASS
180
-
181
- **Step 3: Commit**
182
-
183
- ```bash
184
- git add tests/api/organization-members.test.ts
185
- git commit -m "test: cover member removal edge cases"
186
- ```