@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,155 +0,0 @@
1
- # Permission Check Hook Implementation Plan
2
-
3
- > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
4
-
5
- **Goal:** Add client-side permission hooks (`useCan`, `useHasPermission`) for UI conditional rendering.
6
-
7
- **Architecture:** Create a Better Auth React client wrapper in `lib/auth/client.ts`. Implement hooks in `hooks/use-permissions.ts` that read the current session role via `authClient.useSession()` and reuse permission helpers from `lib/auth/permissions.ts`. Add a small permission helper for “全部满足” logic and test it with Bun’s test runner.
8
-
9
- **Tech Stack:** Next.js App Router, React 19, TypeScript, Better Auth, Bun, Bun Test
10
-
11
- ### Task 1: Add permission helper for “全部满足”
12
-
13
- **Files:**
14
- - Modify: `lib/auth/permissions.ts`
15
- - Test: `tests/lib/permissions.test.ts`
16
-
17
- **Step 1: Write the failing test**
18
-
19
- ```ts
20
- import { describe, expect, it } from "bun:test";
21
- import { PERMISSIONS, hasAllPermissions } from "@/lib/auth/permissions";
22
-
23
- it("checks multiple permissions (all-of)", () => {
24
- expect(
25
- hasAllPermissions("admin", [
26
- PERMISSIONS.CREATE_FEEDBACK,
27
- PERMISSIONS.MANAGE_ORG,
28
- ]),
29
- ).toBe(true);
30
-
31
- expect(
32
- hasAllPermissions("developer", [
33
- PERMISSIONS.CREATE_FEEDBACK,
34
- PERMISSIONS.DELETE_FEEDBACK,
35
- ]),
36
- ).toBe(false);
37
- });
38
- ```
39
-
40
- **Step 2: Run test to verify it fails**
41
-
42
- Run: `bun test tests/lib/permissions.test.ts`
43
- Expected: FAIL with “hasAllPermissions is not defined” (or similar)
44
-
45
- **Step 3: Write minimal implementation**
46
-
47
- ```ts
48
- export function hasAllPermissions(
49
- role: UserRole,
50
- permissions: Permission[],
51
- ): boolean {
52
- return permissions.every((permission) => hasPermission(role, permission));
53
- }
54
- ```
55
-
56
- **Step 4: Run test to verify it passes**
57
-
58
- Run: `bun test tests/lib/permissions.test.ts`
59
- Expected: PASS
60
-
61
- **Step 5: Commit**
62
-
63
- ```bash
64
- git add lib/auth/permissions.ts tests/lib/permissions.test.ts
65
- git commit -m "feat: add all-of permission helper"
66
- ```
67
-
68
- ### Task 2: Add Better Auth client wrapper
69
-
70
- **Files:**
71
- - Create: `lib/auth/client.ts`
72
-
73
- **Step 1: Add client wrapper**
74
-
75
- ```ts
76
- import { createAuthClient } from "better-auth/react";
77
-
78
- export const authClient = createAuthClient();
79
- ```
80
-
81
- **Step 2: Run lint to ensure type safety**
82
-
83
- Run: `bun run lint`
84
- Expected: PASS
85
-
86
- **Step 3: Commit**
87
-
88
- ```bash
89
- git add lib/auth/client.ts
90
- git commit -m "feat: add better-auth client wrapper"
91
- ```
92
-
93
- ### Task 3: Implement permission hooks
94
-
95
- **Files:**
96
- - Create: `hooks/use-permissions.ts`
97
-
98
- **Step 1: Implement hooks**
99
-
100
- ```ts
101
- "use client";
102
-
103
- import { authClient } from "@/lib/auth/client";
104
- import {
105
- hasAllPermissions,
106
- hasPermission,
107
- type Permission,
108
- } from "@/lib/auth/permissions";
109
-
110
- export function useCan(permission: Permission): boolean {
111
- const { data: session } = authClient.useSession();
112
- const role = session?.user?.role;
113
- return role ? hasPermission(role, permission) : false;
114
- }
115
-
116
- export function useHasPermission(permissions: Permission | Permission[]): boolean {
117
- const { data: session } = authClient.useSession();
118
- const role = session?.user?.role;
119
- if (!role) return false;
120
-
121
- const list = Array.isArray(permissions) ? permissions : [permissions];
122
- return hasAllPermissions(role, list);
123
- }
124
- ```
125
-
126
- **Step 2: Run lint to ensure no TS/RSC issues**
127
-
128
- Run: `bun run lint`
129
- Expected: PASS
130
-
131
- **Step 3: Commit**
132
-
133
- ```bash
134
- git add hooks/use-permissions.ts
135
- git commit -m "feat: add permission hooks"
136
- ```
137
-
138
- ### Task 4: Final verification
139
-
140
- **Files:**
141
- - No new files
142
-
143
- **Step 1: Run lint and targeted tests**
144
-
145
- Run: `bun test tests/lib/permissions.test.ts`
146
- Expected: PASS
147
-
148
- Run: `bun run lint`
149
- Expected: PASS
150
-
151
- **Step 2: Commit (if needed)**
152
-
153
- ```bash
154
- git status -sb
155
- ```
@@ -1,231 +0,0 @@
1
- # Resource Ownership Check 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 reusable organization membership check and apply it to existing org-scoped APIs to ensure cross-org data isolation.
6
-
7
- **Architecture:** Create a small `assertOrganizationAccess` helper that checks membership via `organizationMembers` and throws on failure. Update org-scoped handlers to use the helper and keep their existing admin-role enforcement logic.
8
-
9
- **Tech Stack:** Next.js App Router, TypeScript, Bun, Drizzle ORM
10
-
11
- ### Task 1: Add organization access helper + tests
12
-
13
- **Files:**
14
- - Create: `lib/auth/organization.ts`
15
- - Create: `tests/lib/organization-access.test.ts`
16
-
17
- **Step 1: Write the failing test**
18
-
19
- ```typescript
20
- import { describe, expect, it } from "bun:test";
21
- import { assertOrganizationAccess } from "@/lib/auth/organization";
22
-
23
- const makeDb = (member: { role: string } | null) => ({
24
- select: () => ({
25
- from: () => ({
26
- where: () => ({
27
- limit: async () => (member ? [member] : []),
28
- }),
29
- }),
30
- }),
31
- });
32
-
33
- describe("assertOrganizationAccess", () => {
34
- it("returns member when user belongs to organization", async () => {
35
- const db = makeDb({ role: "admin" });
36
- const member = await assertOrganizationAccess(db, "user_1", "org_1");
37
- expect(member.role).toBe("admin");
38
- });
39
-
40
- it("throws when user is not a member", async () => {
41
- const db = makeDb(null);
42
- await expect(
43
- assertOrganizationAccess(db, "user_1", "org_1"),
44
- ).rejects.toThrow("Access denied");
45
- });
46
- });
47
- ```
48
-
49
- **Step 2: Run test to verify it fails**
50
-
51
- Run: `bun test tests/lib/organization-access.test.ts`
52
- Expected: FAIL with module not found or missing export.
53
-
54
- **Step 3: Write minimal implementation**
55
-
56
- ```typescript
57
- import { and, eq } from "drizzle-orm";
58
- import { organizationMembers } from "@/lib/db/schema";
59
- import type { db as database } from "@/lib/db";
60
-
61
- type Database = NonNullable<typeof database>;
62
-
63
- type AccessDb = {
64
- select: Database["select"];
65
- };
66
-
67
- export async function assertOrganizationAccess(
68
- db: AccessDb,
69
- userId: string,
70
- organizationId: string,
71
- ) {
72
- const [member] = await db
73
- .select()
74
- .from(organizationMembers)
75
- .where(
76
- and(
77
- eq(organizationMembers.userId, userId),
78
- eq(organizationMembers.organizationId, organizationId),
79
- ),
80
- )
81
- .limit(1);
82
-
83
- if (!member) {
84
- throw new Error("Access denied");
85
- }
86
-
87
- return member;
88
- }
89
- ```
90
-
91
- **Step 4: Run test to verify it passes**
92
-
93
- Run: `bun test tests/lib/organization-access.test.ts`
94
- Expected: PASS
95
-
96
- **Step 5: Commit**
97
-
98
- ```bash
99
- git add lib/auth/organization.ts tests/lib/organization-access.test.ts
100
- git commit -m "feat: add organization access helper"
101
- ```
102
-
103
- ### Task 2: Apply helper to organization invitations handler
104
-
105
- **Files:**
106
- - Modify: `app/api/organizations/[orgId]/invitations/handler.ts`
107
- - Modify: `tests/api/organization-invitations.test.ts`
108
-
109
- **Step 1: Write the failing test**
110
-
111
- Add a test:
112
-
113
- ```typescript
114
- it("rejects non-members", async () => {
115
- const deps = makeDepsWithRole(null);
116
- const handler = buildCreateInvitationHandler(deps);
117
- const res = await handler(
118
- new Request("http://localhost/api/organizations/org_1/invitations", {
119
- method: "POST",
120
- body: JSON.stringify({ email: "test@example.com", role: "member" }),
121
- }),
122
- { params: { orgId: "org_1" } },
123
- );
124
- expect(res.status).toBe(403);
125
- });
126
- ```
127
-
128
- **Step 2: Run test to verify it fails**
129
-
130
- Run: `bun test tests/api/organization-invitations.test.ts`
131
- Expected: FAIL due to missing helper logic or thrown error not handled.
132
-
133
- **Step 3: Write minimal implementation**
134
-
135
- - Import `assertOrganizationAccess`
136
- - Replace inline member query with:
137
-
138
- ```typescript
139
- let member;
140
- try {
141
- member = await assertOrganizationAccess(deps.db, session.user.id, orgId);
142
- } catch {
143
- return NextResponse.json({ error: "Forbidden" }, { status: 403 });
144
- }
145
- ```
146
-
147
- - Keep existing admin check: `if (member.role !== "admin") return 403`
148
-
149
- **Step 4: Run test to verify it passes**
150
-
151
- Run: `bun test tests/api/organization-invitations.test.ts`
152
- Expected: PASS
153
-
154
- **Step 5: Commit**
155
-
156
- ```bash
157
- git add app/api/organizations/[orgId]/invitations/handler.ts tests/api/organization-invitations.test.ts
158
- git commit -m "feat: enforce organization membership on invitations"
159
- ```
160
-
161
- ### Task 3: Apply helper to organization members handler
162
-
163
- **Files:**
164
- - Modify: `app/api/organizations/[orgId]/members/[memberId]/handler.ts`
165
- - Modify: `tests/api/organization-members.test.ts`
166
-
167
- **Step 1: Write the failing tests**
168
-
169
- Add tests for non-members:
170
-
171
- ```typescript
172
- it("rejects non-members", async () => {
173
- const deps = makeDeps({ requesterRole: null });
174
- const handler = buildRemoveMemberHandler(deps);
175
- const res = await handler(
176
- new Request("http://localhost/api/organizations/org_1/members/user_2", {
177
- method: "DELETE",
178
- }),
179
- { params: { orgId: "org_1", memberId: "user_2" } },
180
- );
181
- expect(res.status).toBe(403);
182
- });
183
- ```
184
-
185
- ```typescript
186
- it("rejects non-members", async () => {
187
- const deps = makeUpdateDeps({ requesterRole: null });
188
- const handler = buildUpdateMemberRoleHandler(deps);
189
- const res = await handler(
190
- new Request("http://localhost/api/organizations/org_1/members/user_2", {
191
- method: "PUT",
192
- body: JSON.stringify({ role: "developer" }),
193
- }),
194
- { params: { orgId: "org_1", memberId: "user_2" } },
195
- );
196
- expect(res.status).toBe(403);
197
- });
198
- ```
199
-
200
- **Step 2: Run tests to verify they fail**
201
-
202
- Run: `bun test tests/api/organization-members.test.ts`
203
- Expected: FAIL due to missing helper logic or thrown error not handled.
204
-
205
- **Step 3: Write minimal implementation**
206
-
207
- - Import `assertOrganizationAccess`
208
- - Replace requester lookup with:
209
-
210
- ```typescript
211
- let requester;
212
- try {
213
- requester = await assertOrganizationAccess(deps.db, session.user.id, orgId);
214
- } catch {
215
- return NextResponse.json({ error: "Forbidden" }, { status: 403 });
216
- }
217
- ```
218
-
219
- - Keep existing admin checks unchanged.
220
-
221
- **Step 4: Run tests to verify they pass**
222
-
223
- Run: `bun test tests/api/organization-members.test.ts`
224
- Expected: PASS
225
-
226
- **Step 5: Commit**
227
-
228
- ```bash
229
- git add app/api/organizations/[orgId]/members/[memberId]/handler.ts tests/api/organization-members.test.ts
230
- git commit -m "feat: enforce organization membership on member actions"
231
- ```