@nexttylabs/echo 0.4.0 → 0.6.0

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