@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,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
- ```