@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.
- package/CHANGELOG.md +27 -0
- package/app/(dashboard)/admin/feedback/[id]/edit/page.tsx +12 -6
- package/app/(dashboard)/admin/feedback/new/page.tsx +19 -17
- package/app/(dashboard)/admin/layout.tsx +16 -6
- package/app/(dashboard)/layout.tsx +4 -2
- package/app/(dashboard)/settings/api-keys/page.tsx +13 -3
- package/app/(dashboard)/settings/layout.tsx +25 -2
- package/app/(dashboard)/settings/organization/page.tsx +8 -9
- package/app/(public)/[organizationSlug]/roadmap/page.tsx +19 -1
- package/app/api/admin/backup/route.ts +22 -4
- package/app/api/auth/register/handler.ts +1 -2
- package/app/api/feedback/[id]/comments/[commentId]/route.ts +13 -4
- package/app/api/feedback/[id]/reclassify/route.ts +4 -4
- package/app/api/organizations/handler.ts +2 -4
- package/components/settings/settings-sidebar.tsx +4 -4
- package/hooks/use-organization.tsx +116 -0
- package/hooks/use-permissions.ts +24 -11
- package/lib/auth/config.ts +0 -7
- package/lib/auth/organization.ts +20 -0
- package/lib/auth/permissions.ts +10 -0
- package/lib/db/migrations/0000_needy_leech.sql +335 -0
- package/lib/db/migrations/meta/0000_snapshot.json +2186 -1
- package/lib/db/migrations/meta/_journal.json +2 -135
- package/lib/db/schema/auth.ts +0 -1
- package/lib/db/schema/index.ts +0 -1
- package/lib/portal/public-context.tsx +5 -0
- package/package.json +20 -1
- package/.changeset/README.md +0 -21
- package/.changeset/config.json +0 -11
- package/.changeset/cozy-ghosts-care.md +0 -5
- package/.changeset/sharp-lines-stand.md +0 -5
- package/.changeset/sour-doodles-eat.md +0 -5
- package/.changeset/tender-moose-shop.md +0 -5
- package/.github/pull_request_template.md +0 -13
- package/.github/workflows/ci.yml +0 -41
- package/.github/workflows/publish.yml +0 -44
- package/.github/workflows/release.yml +0 -73
- package/AGENTS.md +0 -92
- package/Dockerfile +0 -57
- package/Makefile +0 -77
- package/bun.lock +0 -2503
- package/components/portal/project-switcher.tsx +0 -20
- package/docker-compose.dev.yml +0 -26
- package/docker-compose.yml +0 -98
- package/docs/architecture.md +0 -259
- package/docs/component-inventory.md +0 -261
- package/docs/database-migrations.md +0 -76
- package/docs/development-guide.md +0 -209
- package/docs/e2e-user-flows.csv +0 -31
- package/docs/er-diagram-feedback.mmd +0 -138
- package/docs/er-diagram.mmd +0 -281
- package/docs/i18n-check-report.md +0 -296
- package/docs/index.md +0 -214
- package/docs/logic-chain.md +0 -94
- package/docs/plans/2026-01-02-database-migration-scripts.md +0 -496
- package/docs/plans/2026-01-02-user-login-design.md +0 -37
- package/docs/plans/2026-01-02-user-login.md +0 -437
- package/docs/plans/2026-01-02-user-registration-design.md +0 -47
- package/docs/plans/2026-01-02-user-registration.md +0 -628
- package/docs/plans/2026-01-03-roles-permissions-design.md +0 -20
- package/docs/plans/2026-01-03-roles-permissions.md +0 -266
- package/docs/plans/2026-01-05-authentication-middleware.md +0 -207
- package/docs/plans/2026-01-05-member-removal.md +0 -186
- package/docs/plans/2026-01-05-organization-creation.md +0 -374
- package/docs/plans/2026-01-05-rbac-middleware.md +0 -112
- package/docs/plans/2026-01-05-role-configuration.md +0 -441
- package/docs/plans/2026-01-06-file-upload-support.md +0 -804
- package/docs/plans/2026-01-06-permission-check-hook.md +0 -155
- package/docs/plans/2026-01-06-resource-ownership-check.md +0 -231
- package/docs/plans/2026-01-07-feedback-tracking-link.md +0 -459
- package/docs/plans/2026-01-09-logout-redirect-design.md +0 -52
- package/docs/plans/2026-01-09-phase2-3-plan.md +0 -654
- package/docs/plans/2026-01-09-portal-execution-plan.md +0 -408
- package/docs/plans/2026-01-09-project-delete-feature-design.md +0 -163
- package/docs/plans/2026-01-09-project-delete-implementation.md +0 -451
- package/docs/plans/2026-01-09-project-edit-delete-design.md +0 -52
- package/docs/plans/2026-01-09-settings-center-design.md +0 -114
- package/docs/plans/2026-01-09-settings-center.md +0 -948
- package/docs/plans/2026-01-10-organization-only-design.md +0 -66
- package/docs/plans/2026-01-10-organization-only-implementation.md +0 -433
- package/docs/plans/2026-01-10-portal-settings-restructure-plan.md +0 -18
- package/docs/plans/2026-01-10-project-settings-tabs-design-implementation.md +0 -296
- package/docs/plans/2026-01-14-e2e-playwright-feedback.md +0 -173
- package/docs/plans/2026-01-15-feedback-management-org-context-design.md +0 -82
- package/docs/plans/2026-01-15-feedback-management-org-context-implementation-plan.md +0 -521
- package/docs/plans/2026-01-16-admin-feedback-filters-design.md +0 -75
- package/docs/plans/2026-01-16-admin-feedback-filters-implementation.md +0 -293
- package/docs/plans/2026-01-16-admin-feedback-route-consolidation.md +0 -180
- package/docs/plans/2026-01-16-e2e-test-fixes.md +0 -158
- package/docs/plans/2026-01-17-admin-feedback-filters.md +0 -214
- package/docs/plans/2026-01-17-admin-feedback-improvements.md +0 -453
- package/docs/plans/2026-01-18-changesets-design.md +0 -40
- package/docs/product_changes.md +0 -37
- package/docs/project-overview.md +0 -159
- package/docs/project-scan-report.json +0 -104
- package/docs/route-role-visibility.md +0 -51
- package/docs/source-tree-analysis.md +0 -150
- package/docs/testing/delete-project-manual-tests.md +0 -18
- package/docs/user-story-tracking.md +0 -191
- package/eslint.config.mjs +0 -19
- package/lib/db/migrations/.gitkeep +0 -0
- package/lib/db/migrations/0000_cynical_gladiator.sql +0 -53
- package/lib/db/migrations/0001_wandering_sunfire.sql +0 -27
- package/lib/db/migrations/0002_shallow_speedball.sql +0 -1
- package/lib/db/migrations/0003_add_org_description.sql +0 -1
- package/lib/db/migrations/0003_boring_wild_pack.sql +0 -13
- package/lib/db/migrations/0004_windy_tyrannus.sql +0 -27
- package/lib/db/migrations/0005_perpetual_doorman.sql +0 -5
- package/lib/db/migrations/0006_aberrant_captain_midlands.sql +0 -13
- package/lib/db/migrations/0007_clever_captain_cross.sql +0 -14
- package/lib/db/migrations/0008_sparkling_pandemic.sql +0 -2
- package/lib/db/migrations/0009_happy_black_tom.sql +0 -29
- package/lib/db/migrations/0010_kind_junta.sql +0 -8
- package/lib/db/migrations/0011_mute_squadron_supreme.sql +0 -25
- package/lib/db/migrations/0012_giant_power_man.sql +0 -24
- package/lib/db/migrations/0013_damp_titanium_man.sql +0 -17
- package/lib/db/migrations/0014_blue_alice.sql +0 -18
- package/lib/db/migrations/0015_webhook_tables.sql +0 -41
- package/lib/db/migrations/0016_github_integration.sql +0 -30
- package/lib/db/migrations/0016_overjoyed_ghost_rider.sql +0 -22
- package/lib/db/migrations/0017_slimy_inhumans.sql +0 -6
- package/lib/db/migrations/0018_same_spitfire.sql +0 -1
- package/lib/db/migrations/0019_jittery_loners.sql +0 -16
- package/lib/db/migrations/0019_remove_projects_add_org_settings.sql +0 -14
- package/lib/db/migrations/meta/0001_snapshot.json +0 -553
- package/lib/db/migrations/meta/0002_snapshot.json +0 -560
- package/lib/db/migrations/meta/0003_snapshot.json +0 -650
- package/lib/db/migrations/meta/0004_snapshot.json +0 -852
- package/lib/db/migrations/meta/0005_snapshot.json +0 -900
- package/lib/db/migrations/meta/0006_snapshot.json +0 -1011
- package/lib/db/migrations/meta/0007_snapshot.json +0 -1125
- package/lib/db/migrations/meta/0008_snapshot.json +0 -1146
- package/lib/db/migrations/meta/0009_snapshot.json +0 -1386
- package/lib/db/migrations/meta/0010_snapshot.json +0 -1419
- package/lib/db/migrations/meta/0011_snapshot.json +0 -1615
- package/lib/db/migrations/meta/0012_snapshot.json +0 -1805
- package/lib/db/migrations/meta/0013_snapshot.json +0 -1948
- package/lib/db/migrations/meta/0014_snapshot.json +0 -2082
- package/lib/db/migrations/meta/0015_snapshot.json +0 -2476
- package/lib/db/migrations/meta/0016_snapshot.json +0 -2633
- package/lib/db/migrations/meta/0017_snapshot.json +0 -2680
- package/lib/db/migrations/meta/0018_snapshot.json +0 -2686
- package/lib/db/migrations/meta/0019_snapshot.json +0 -2741
- package/lib/db/schema/projects.ts +0 -145
- package/lib/db/schema/user-profiles.ts +0 -31
- package/lib/validations/projects.ts +0 -49
- package/next-env.d.ts +0 -6
- package/playwright.config.ts +0 -44
- package/proxy.test.ts +0 -131
- package/proxy.ts +0 -116
- package/scripts/backup-db.sh +0 -57
- package/scripts/backup-db.ts +0 -24
- package/scripts/generate-openapi.ts +0 -22
- package/scripts/migration-helper.ts +0 -39
- package/scripts/pre-deploy.ts +0 -75
- package/scripts/restore-db.sh +0 -60
- package/scripts/rollback.ts +0 -72
- package/scripts/seed-tags.ts +0 -48
- package/tests/api/feedback-bulk.test.ts +0 -47
- package/tests/api/feedback-by-id.test.ts +0 -67
- package/tests/api/feedback-comments-route-import.test.ts +0 -26
- package/tests/api/feedback-create.test.ts +0 -71
- package/tests/api/feedback-delete.test.ts +0 -160
- package/tests/api/feedback-filter.test.ts +0 -250
- package/tests/api/feedback-list.test.ts +0 -234
- package/tests/api/feedback-route-assignee-condition.test.ts +0 -32
- package/tests/api/feedback-similar.test.ts +0 -46
- package/tests/api/feedback-sort.test.ts +0 -261
- package/tests/api/feedback-status-enum.test.ts +0 -49
- package/tests/api/feedback-status-filter.test.ts +0 -117
- package/tests/api/feedback-submit-on-behalf.test.ts +0 -269
- package/tests/api/feedback.test.ts +0 -175
- package/tests/api/identify-jwt.test.ts +0 -25
- package/tests/api/invitation-accept.test.ts +0 -213
- package/tests/api/organization-invitations.test.ts +0 -186
- package/tests/api/organization-members-list.test.ts +0 -79
- package/tests/api/organization-members.test.ts +0 -340
- package/tests/api/organizations.test.ts +0 -149
- package/tests/api/register.test.ts +0 -112
- package/tests/api/upload.test.ts +0 -103
- package/tests/api/vote.test.ts +0 -82
- package/tests/app/admin-feedback-detail-page.test.tsx +0 -25
- package/tests/app/admin-feedback-list-page.test.tsx +0 -25
- package/tests/app/admin-feedback-new-page.test.tsx +0 -25
- package/tests/app/health-route-helpers.test.ts +0 -27
- package/tests/app/login-page.test.ts +0 -26
- package/tests/app/portal-page.test.ts +0 -29
- package/tests/app/project-portal-overview.test.tsx +0 -25
- package/tests/app/widget-page-import.test.ts +0 -25
- package/tests/components/create-post-dialog-defaults.test.ts +0 -43
- package/tests/components/feedback/duplicate-suggestions-inline.test.tsx +0 -27
- package/tests/components/feedback/embedded-feedback-form.test.tsx +0 -96
- package/tests/components/feedback/feedback-detail.test.tsx +0 -25
- package/tests/components/feedback/feedback-stats.test.tsx +0 -49
- package/tests/components/feedback-bulk-actions.test.tsx +0 -39
- package/tests/components/feedback-i18n-keys.test.ts +0 -70
- package/tests/components/feedback-list-controls-compile.test.ts +0 -25
- package/tests/components/feedback-list-controls.test.tsx +0 -204
- package/tests/components/feedback-list-item.test.tsx +0 -67
- package/tests/components/landing/hero.test.tsx +0 -46
- package/tests/components/layout/language-switcher.test.tsx +0 -25
- package/tests/components/layout/sidebar.test.tsx +0 -157
- package/tests/components/login-form.test.ts +0 -25
- package/tests/components/organization-form.test.ts +0 -32
- package/tests/components/organization-switcher.test.ts +0 -25
- package/tests/components/pagination.test.tsx +0 -43
- package/tests/components/portal-overview.test.tsx +0 -25
- package/tests/components/profile-form.test.tsx +0 -139
- package/tests/components/role-selector.test.ts +0 -31
- package/tests/components/status-chart.test.tsx +0 -90
- package/tests/e2e/auth.e2e.ts +0 -323
- package/tests/e2e/feedback-actions.e2e.ts +0 -471
- package/tests/e2e/feedback-attachment.e2e.ts +0 -168
- package/tests/e2e/feedback-customer.e2e.ts +0 -226
- package/tests/e2e/feedback-management.e2e.ts +0 -565
- package/tests/e2e/feedback-submit.e2e.ts +0 -133
- package/tests/e2e/feedback-view.e2e.ts +0 -297
- package/tests/e2e/fixtures/test-data.ts +0 -235
- package/tests/e2e/health-check.e2e.ts +0 -230
- package/tests/e2e/helpers/test-utils-helpers.test.ts +0 -43
- package/tests/e2e/helpers/test-utils.ts +0 -298
- package/tests/e2e/integration-placeholders.e2e.ts +0 -199
- package/tests/e2e/organization.e2e.ts +0 -292
- package/tests/e2e/permissions.e2e.ts +0 -424
- package/tests/e2e/project-widget.e2e.ts +0 -63
- package/tests/feedback/filters.test.ts +0 -29
- package/tests/hooks/use-permissions.test.ts +0 -52
- package/tests/lib/ai/classifier.test.ts +0 -104
- package/tests/lib/ai/duplicate-detector.test.ts +0 -234
- package/tests/lib/attachments-schema.test.ts +0 -30
- package/tests/lib/auth/session.test.ts +0 -49
- package/tests/lib/auth-client.test.ts +0 -37
- package/tests/lib/auth-config.test.ts +0 -26
- package/tests/lib/feedback-prefill.test.ts +0 -52
- package/tests/lib/feedback-processor.test.ts +0 -41
- package/tests/lib/feedback-schema.test.ts +0 -33
- package/tests/lib/file-validator.test.ts +0 -48
- package/tests/lib/get-feedback-by-id.test.ts +0 -37
- package/tests/lib/invitations.test.ts +0 -35
- package/tests/lib/login-schema.test.ts +0 -36
- package/tests/lib/org-context.test.ts +0 -95
- package/tests/lib/organization-access.test.ts +0 -44
- package/tests/lib/organization-member-role-schema.test.ts +0 -41
- package/tests/lib/permissions.test.ts +0 -88
- package/tests/lib/portal-analytics.test.ts +0 -25
- package/tests/lib/portal-contributors.test.ts +0 -25
- package/tests/lib/portal-copy.test.ts +0 -27
- package/tests/lib/portal-i18n.test.ts +0 -30
- package/tests/lib/portal-leaderboard-settings.test.ts +0 -25
- package/tests/lib/portal-modules.test.ts +0 -25
- package/tests/lib/portal-seo.test.ts +0 -25
- package/tests/lib/portal-sharing.test.ts +0 -25
- package/tests/lib/portal-sorting.test.ts +0 -25
- package/tests/lib/portal-theme.test.ts +0 -25
- package/tests/lib/rate-limit.test.ts +0 -142
- package/tests/lib/resolve-locale.test.ts +0 -34
- package/tests/lib/services/backup.test.ts +0 -145
- package/tests/lib/user-organizations.test.ts +0 -42
- package/tests/lib/user-role-schema.test.ts +0 -33
- package/tests/lib/user-schema.test.ts +0 -25
- package/tests/setup.ts +0 -74
- 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
|
-
```
|