create-m5kdev 0.40.3 → 0.41.1
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/package.json +1 -1
- package/templates/minimal-app/.github/workflows/check.yml.tpl +21 -0
- package/templates/minimal-app/AGENTS.md.tpl +22 -31
- package/templates/minimal-app/CLAUDE.md.tpl +15 -1
- package/templates/minimal-app/GLOSSARY.md.tpl +181 -0
- package/templates/minimal-app/README.md.tpl +2 -0
- package/templates/minimal-app/apps/e2e/tests/standard.spec.ts.tpl +11 -22
- package/templates/minimal-app/apps/server/drizzle/0009_empty_starjammers.sql +45 -0
- package/templates/minimal-app/apps/server/drizzle/meta/0009_snapshot.json.tpl +3687 -0
- package/templates/minimal-app/apps/server/drizzle/meta/_journal.json.tpl +76 -69
- package/templates/minimal-app/apps/server/src/app.ts.tpl +1 -0
- package/templates/minimal-app/biome.json.tpl +1 -0
- package/templates/minimal-app/docs/adr/.gitkeep +0 -0
- package/templates/minimal-app/docs/agents/domain.md.tpl +38 -0
- package/templates/minimal-app/docs/agents/issue-tracker.md.tpl +45 -0
- package/templates/minimal-app/docs/agents/triage-labels.md.tpl +15 -0
- package/templates/minimal-app/pnpm-workspace.yaml.tpl +15 -15
- package/templates/minimal-app/template.manifest.json +12 -0
package/package.json
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: check
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
check:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v4
|
|
12
|
+
- uses: pnpm/action-setup@v4
|
|
13
|
+
with:
|
|
14
|
+
version: 10.13.1
|
|
15
|
+
- uses: actions/setup-node@v4
|
|
16
|
+
with:
|
|
17
|
+
node-version: 24
|
|
18
|
+
cache: pnpm
|
|
19
|
+
- run: pnpm install
|
|
20
|
+
- run: pnpm lint
|
|
21
|
+
- run: pnpm check-types
|
|
@@ -22,38 +22,29 @@
|
|
|
22
22
|
// m5k:expo:end
|
|
23
23
|
|
|
24
24
|
// m5k:server:start
|
|
25
|
-
##
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- Register modules in `apps/server/src/app.ts` via `createBackendApp(config, [modules])`.
|
|
29
|
-
- Import Kernel infrastructure (`BaseModule`, Grants, DTOs) from `@m5kdev/backend/base/*`. The `modules/base` path still re-exports.
|
|
30
|
-
- Clay, Docx, Pdf, Social, and Video are Optional Backend Module packages (`@m5kdev/module-<name>`). They are not scaffolded; add them only when the product needs them.
|
|
31
|
-
- Use `BaseModule` subclasses for app modules; the kernel wires repositories, services, and tRPC.
|
|
32
|
-
- Keep tRPC files focused on input/output wiring and delegate logic to services.
|
|
33
|
-
- Do not add service methods that merely call another service. Call the owning service directly unless the method adds a business rule, authorization, validation, orchestration, a transaction boundary, or meaningful domain translation.
|
|
34
|
-
- Renaming a dependency method, repackaging arguments, constructing a prompt, or forwarding actor/context data alone does not justify a service wrapper.
|
|
35
|
-
- Do not create Drizzle migrations by hand. Use the scaffolded config and your project migration workflow later if you need generated migrations.
|
|
36
|
-
- Server event: emit through Auth from Workflow job handlers (and similar background work) when the UI must refresh a change the triggering mutation did not return. See `.cursor/rules/server-event-emit.mdc`.
|
|
37
|
-
// m5k:server:end
|
|
25
|
+
## Modules
|
|
26
|
+
|
|
27
|
+
`posts` is the reference module. Mirror its layout when adding a feature:
|
|
38
28
|
|
|
29
|
+
- `apps/shared/src/modules/posts/`
|
|
30
|
+
- `apps/server/src/modules/posts/`
|
|
39
31
|
// m5k:webapp:start
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
- Keep the app shell shape: `NuqsAdapter` + `BrowserRouter` + `Providers`.
|
|
43
|
-
- Compose global providers only in `apps/webapp/src/Providers.tsx`.
|
|
44
|
-
- Use `AppConfigProvider` and `AppTrpcQueryProvider` from `@m5kdev/frontend`; use `@m5kdev/web-ui` for web-only UI.
|
|
45
|
-
- Define app roles in `apps/shared/src/modules/app/roles.constants.ts` and pass `roles: APP_ROLES_CONFIG` through `AppConfigProvider` and `createBackendApp` metadata.
|
|
46
|
-
- Add role display labels in `apps/webapp/translations/app.json` using keys like `organization.role.editor` (resolved as `app:organization.role.editor`).
|
|
47
|
-
- When adding custom roles, also extend module grants (for example `*.grants.ts`) so permissions match the new role keys.
|
|
48
|
-
- For upgrading existing apps, see the docs site guide `apps/docs/docs/guides/custom-app-roles-migration.md`.
|
|
49
|
-
- Use `nuqs` for URL state, React Router for routing, HeroUI for UI primitives, and Tailwind v4 for styling.
|
|
50
|
-
- Prefer shared framework utilities from `@m5kdev/frontend` and `@m5kdev/web-ui` before adding local duplicates.
|
|
51
|
-
- React 19 idioms: no `forwardRef` (pass `ref` as a prop), no `defaultProps`, use `startTransition`/`useDeferredValue` for low-priority updates.
|
|
52
|
-
- Prefer HeroUI v3 components over hand-rolled HTML + Tailwind whenever an equivalent exists; Tailwind is for layout and styling on top of them.
|
|
53
|
-
- Keep components display-focused: data fetching, mutations, URL state, and effect chains live in custom hooks under `modules/<feature>/hooks/` (small focused hooks composed into one screen-level hook, e.g. `usePostsRoute`). Ephemeral UI state (modal open, selected row) may stay in the component. Extract reusable tRPC actions (delete, publish) into their own hooks.
|
|
54
|
-
- Fetch all server data with TanStack Query over tRPC (`useTRPC()` + `queryOptions`/`mutationOptions`); invalidate with the matching `queryFilter()` after mutations. No `fetch`/axios.
|
|
55
|
-
- Forms are uncontrolled HeroUI `Form` + `TextField`/`TextArea` with native HTML validation (`isRequired`, `type`, `minLength`) and `FieldError`; read values from `FormData` on submit. Never add form libraries (react-hook-form, formik). Validation that HTML cannot express (e.g. confirm-password) happens in the submit handler.
|
|
56
|
-
- All user-facing copy goes through i18next (`useTranslation`); keys live in `apps/webapp/translations/`.
|
|
57
|
-
- The `posts` feature (`apps/webapp/src/modules/posts/`) is the reference implementation for these conventions; see `.cursor/rules/frontend-*.mdc` for the detailed guides.
|
|
32
|
+
- `apps/webapp/src/modules/posts/`
|
|
58
33
|
// m5k:webapp:end
|
|
59
34
|
|
|
35
|
+
Read the matching `.cursor/rules` guide before editing a module file.
|
|
36
|
+
// m5k:server:end
|
|
37
|
+
|
|
38
|
+
## Agent skills
|
|
39
|
+
|
|
40
|
+
### Issue tracker
|
|
41
|
+
|
|
42
|
+
Issues live in GitHub Issues. Use the `gh` CLI. See `docs/agents/issue-tracker.md`.
|
|
43
|
+
|
|
44
|
+
### Triage labels
|
|
45
|
+
|
|
46
|
+
Default role labels: `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`. See `docs/agents/triage-labels.md`.
|
|
47
|
+
|
|
48
|
+
### Domain docs
|
|
49
|
+
|
|
50
|
+
Single-context: one root `GLOSSARY.md` and `docs/adr/`. See `docs/agents/domain.md`.
|
|
@@ -139,6 +139,20 @@ The framework's detailed conventions live in `.cursor/rules/*.mdc`. Cursor appli
|
|
|
139
139
|
| `.cursor/rules/frontend-i18n-guide.mdc` | webapp user-facing copy |
|
|
140
140
|
// m5k:webapp:end
|
|
141
141
|
|
|
142
|
-
`AGENTS.md` (repo root and per-app)
|
|
142
|
+
`AGENTS.md` (repo root and per-app) points at this file and `.cursor/rules`. This file plus the guides above are the authoritative conventions.
|
|
143
143
|
// m5k:server:end
|
|
144
144
|
|
|
145
|
+
## Agent skills
|
|
146
|
+
|
|
147
|
+
### Issue tracker
|
|
148
|
+
|
|
149
|
+
Issues live in GitHub Issues. Use the `gh` CLI. See `docs/agents/issue-tracker.md`.
|
|
150
|
+
|
|
151
|
+
### Triage labels
|
|
152
|
+
|
|
153
|
+
Default role labels: `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`. See `docs/agents/triage-labels.md`.
|
|
154
|
+
|
|
155
|
+
### Domain docs
|
|
156
|
+
|
|
157
|
+
Single-context: one root `GLOSSARY.md` and `docs/adr/`. See `docs/agents/domain.md`.
|
|
158
|
+
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# {{APP_NAME}}
|
|
2
|
+
|
|
3
|
+
{{APP_DESCRIPTION}}
|
|
4
|
+
|
|
5
|
+
## Language
|
|
6
|
+
# m5k:server:start
|
|
7
|
+
|
|
8
|
+
### Tenancy
|
|
9
|
+
|
|
10
|
+
**Organization**:
|
|
11
|
+
The default tenancy unit. Signup creates one; a Closed Organization cannot be used.
|
|
12
|
+
_Avoid_: Workspace, tenant, account, company, Team
|
|
13
|
+
|
|
14
|
+
**Membership**:
|
|
15
|
+
A durable seat in an Organization. Invite can create it before a User exists; leave, cancel, and expiry soft-delete it; rejoin revives the same MemberId. An Organization has exactly one Owner.
|
|
16
|
+
_Avoid_: OrgUser; treating Invitation as the Membership; Seat (that is Seat billing)
|
|
17
|
+
|
|
18
|
+
**Member**:
|
|
19
|
+
The Membership principal used to attribute and authorize org-scoped assets, including invited Memberships that do not yet have a User.
|
|
20
|
+
_Avoid_: User (when you mean the membership), Invitation
|
|
21
|
+
|
|
22
|
+
**MemberId**:
|
|
23
|
+
The Membership id stamped on org-scoped rows. In organization context, `"own"` grants compare this, not UserId.
|
|
24
|
+
_Avoid_: UserId as the org ownership key
|
|
25
|
+
|
|
26
|
+
**User**:
|
|
27
|
+
The global sign-in identity. Owns personal resources that are not org tenancy. A Closed User cannot sign in.
|
|
28
|
+
_Avoid_: Account, Customer, Client, Member
|
|
29
|
+
|
|
30
|
+
**UserId**:
|
|
31
|
+
The User id. Correct key for personal resources. Not the org-scoped ownership key.
|
|
32
|
+
_Avoid_: MemberId
|
|
33
|
+
|
|
34
|
+
**Owner**:
|
|
35
|
+
The Organization Role `owner`. Exactly one per Organization. Invite and org self-service cannot grant or transfer it.
|
|
36
|
+
_Avoid_: multiple Owners as a product feature; inviting Owner
|
|
37
|
+
|
|
38
|
+
**Invitation**:
|
|
39
|
+
The accept token that attaches a User to an invited Membership. Not a person and not assignable.
|
|
40
|
+
_Avoid_: Waitlist code, pending Member
|
|
41
|
+
|
|
42
|
+
**Closed**:
|
|
43
|
+
A User or Organization in the restorable lockout state.
|
|
44
|
+
_Avoid_: soft-delete (that is Membership leave), deactivated, suspended, banned
|
|
45
|
+
|
|
46
|
+
**Restore**:
|
|
47
|
+
Returning a Closed User or Organization to usable state before Purge. Admin panel only.
|
|
48
|
+
_Avoid_: undelete, reactivate
|
|
49
|
+
|
|
50
|
+
**Purge**:
|
|
51
|
+
Permanent removal of a Closed User or Organization after the grace window.
|
|
52
|
+
_Avoid_: hard-delete as the User-facing noun
|
|
53
|
+
|
|
54
|
+
### Identity and access
|
|
55
|
+
|
|
56
|
+
**Actor**:
|
|
57
|
+
Who a server call is made on behalf of: User, Organization, or Admin. Organization scope requires a live Membership and an Organization that is not Closed. Invited Members are not Actors.
|
|
58
|
+
_Avoid_: Session, Principal, Request, TeamActor
|
|
59
|
+
|
|
60
|
+
**Grant**:
|
|
61
|
+
A permission tuple: resource, level (`user` | `organization`), role, Action, Access.
|
|
62
|
+
_Avoid_: Permission, Policy, ACL
|
|
63
|
+
|
|
64
|
+
**Action**:
|
|
65
|
+
Canonical Grant verbs: `read`, `write`, `delete`, `publish`.
|
|
66
|
+
_Avoid_: update, create, patch, remove
|
|
67
|
+
|
|
68
|
+
**Access**:
|
|
69
|
+
How wide a Grant is: `all`, `own`, `org`, `none`.
|
|
70
|
+
_Avoid_: Scope, Role
|
|
71
|
+
|
|
72
|
+
**Role**:
|
|
73
|
+
A named key at User or Organization scope. Defaults: User `user`/`admin`; Organization `member`/`admin`/`owner`.
|
|
74
|
+
_Avoid_: Grant, Access, permission
|
|
75
|
+
|
|
76
|
+
### Product surfaces
|
|
77
|
+
|
|
78
|
+
**Admin panel**:
|
|
79
|
+
The User-role admin UI at `/admin`. Organization Role `admin` does not see it.
|
|
80
|
+
_Avoid_: org Role admin, Billing Portal
|
|
81
|
+
# m5k:files:start
|
|
82
|
+
|
|
83
|
+
**File**:
|
|
84
|
+
An inventoried object in storage: a row plus the bytes. Personal Files stamp UserId; org-scoped Files stamp MemberId.
|
|
85
|
+
_Avoid_: Upload, Asset, Attachment, Blob
|
|
86
|
+
# m5k:files:end
|
|
87
|
+
# m5k:billing:start
|
|
88
|
+
|
|
89
|
+
**Plan**:
|
|
90
|
+
Named commercial offering in app code. Not a Stripe Product.
|
|
91
|
+
_Avoid_: Product, Tier, Subscription
|
|
92
|
+
|
|
93
|
+
**Subscription**:
|
|
94
|
+
The billed relationship for an Organization, not a User.
|
|
95
|
+
_Avoid_: Plan, personal User subscription
|
|
96
|
+
|
|
97
|
+
**Checkout**:
|
|
98
|
+
Hosted start of a Subscription when the Organization has none.
|
|
99
|
+
_Avoid_: Billing Portal
|
|
100
|
+
|
|
101
|
+
**Billing Portal**:
|
|
102
|
+
Hosted management of an existing Subscription.
|
|
103
|
+
_Avoid_: Checkout
|
|
104
|
+
|
|
105
|
+
**Seat billing**:
|
|
106
|
+
When on, billable quantity is the count of Memberships. Off (default): quantity is one.
|
|
107
|
+
_Avoid_: Seat as a synonym for Membership
|
|
108
|
+
# m5k:billing:end
|
|
109
|
+
# m5k:ai:start
|
|
110
|
+
|
|
111
|
+
**Conversation**:
|
|
112
|
+
Ordered UI transcript of AI turns.
|
|
113
|
+
_Avoid_: Chat, Thread (that is the store)
|
|
114
|
+
|
|
115
|
+
**Thread**:
|
|
116
|
+
The persisted AI memory thread behind a Conversation.
|
|
117
|
+
_Avoid_: using Thread for the UI surface
|
|
118
|
+
|
|
119
|
+
**Agent**:
|
|
120
|
+
A named AI agent the app registers. A Conversation selects which Agent answers.
|
|
121
|
+
_Avoid_: Assistant, Bot, MCP client
|
|
122
|
+
# m5k:ai:end
|
|
123
|
+
# m5k:mcp:start
|
|
124
|
+
|
|
125
|
+
**MCP client**:
|
|
126
|
+
A remote Model Context Protocol client that calls the app on behalf of a User.
|
|
127
|
+
_Avoid_: Agent, Connection
|
|
128
|
+
|
|
129
|
+
**MCP call**:
|
|
130
|
+
A tRPC procedure opted into the MCP catalog. Grant checks always run.
|
|
131
|
+
_Avoid_: a second tool catalog, Workflow job
|
|
132
|
+
|
|
133
|
+
**MCP allowlist**:
|
|
134
|
+
The Organizations a given OAuth client may pass as organizationId for a User.
|
|
135
|
+
_Avoid_: treating allowlist as permission to write
|
|
136
|
+
# m5k:mcp:end
|
|
137
|
+
# m5k:notifications:start
|
|
138
|
+
|
|
139
|
+
**Notification**:
|
|
140
|
+
A persisted inbox instance owned by a Member, classified by a Notification kind.
|
|
141
|
+
_Avoid_: UserId as the ownership key
|
|
142
|
+
|
|
143
|
+
**Notification kind**:
|
|
144
|
+
A developer-declared class of Notifications. Mute applies to the kind.
|
|
145
|
+
_Avoid_: Notification (that is the instance)
|
|
146
|
+
|
|
147
|
+
**Channel**:
|
|
148
|
+
A delivery means for a Notification: in-app, web push, mobile push, or email.
|
|
149
|
+
_Avoid_: Server event, Device
|
|
150
|
+
# m5k:notifications:end
|
|
151
|
+
# m5k:workflows:start
|
|
152
|
+
|
|
153
|
+
**Workflow**:
|
|
154
|
+
A persisted background run. Org-scoped runs stamp MemberId.
|
|
155
|
+
_Avoid_: Job, Queue, Task, Recurrence
|
|
156
|
+
# m5k:workflows:end
|
|
157
|
+
# m5k:tags:start
|
|
158
|
+
|
|
159
|
+
**Tag**:
|
|
160
|
+
A polymorphic label. Ownership is UserId, MemberId, or organizationId.
|
|
161
|
+
_Avoid_: Label, Category
|
|
162
|
+
# m5k:tags:end
|
|
163
|
+
# m5k:connect:start
|
|
164
|
+
|
|
165
|
+
**Connection**:
|
|
166
|
+
A linked third-party API account. Personal; keyed by UserId. The module id is `connect`.
|
|
167
|
+
_Avoid_: Connect as the product noun; login OAuth
|
|
168
|
+
# m5k:connect:end
|
|
169
|
+
# m5k:webhook:start
|
|
170
|
+
|
|
171
|
+
**Inbound callback**:
|
|
172
|
+
A one-shot inbound callback with an awaitable payload. Unattributed. The module id is `webhook`.
|
|
173
|
+
_Avoid_: Webhook (when you mean this vs Stripe Subscription sync); Connection
|
|
174
|
+
# m5k:webhook:end
|
|
175
|
+
# m5k:recurrence:start
|
|
176
|
+
|
|
177
|
+
**Recurrence**:
|
|
178
|
+
A repeating calendar pattern stored as a row. Not a Workflow.
|
|
179
|
+
_Avoid_: Workflow, cron
|
|
180
|
+
# m5k:recurrence:end
|
|
181
|
+
# m5k:server:end
|
|
@@ -39,6 +39,8 @@ Database tables are registered by hand in `apps/server/src/schema.ts`; after cha
|
|
|
39
39
|
|
|
40
40
|
- Email: `admin@{{APP_SLUG}}.local`
|
|
41
41
|
- Password: `password1234`
|
|
42
|
+
|
|
43
|
+
After sign-in, the index route (`/`) is the Posts list.
|
|
42
44
|
// m5k:server:end
|
|
43
45
|
|
|
44
46
|
## Typical Commands
|
|
@@ -58,7 +58,7 @@ async function saveMemberEdit(page: Page, row: Locator, changes: { name?: string
|
|
|
58
58
|
await dialog.getByLabel(/^name$/i).fill(changes.name);
|
|
59
59
|
}
|
|
60
60
|
if (changes.role !== undefined) {
|
|
61
|
-
await dialog.getByRole("button", { name:
|
|
61
|
+
await dialog.getByRole("button", { name: /role/i }).click();
|
|
62
62
|
await page.getByRole("option", { name: new RegExp(`^${changes.role}$`) }).click();
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -211,7 +211,10 @@ test("duplicate signup with an existing email is rejected", async ({ page, reque
|
|
|
211
211
|
expect(
|
|
212
212
|
response.ok(),
|
|
213
213
|
`${response.status()} ${response.statusText()}: ${await response.text()}`
|
|
214
|
-
).toBe(
|
|
214
|
+
).toBe(true);
|
|
215
|
+
expect(await response.json()).toMatchObject({ token: null });
|
|
216
|
+
const state = await getUserState(request, profile, email);
|
|
217
|
+
expect(state.user.emailVerified).toBe(true);
|
|
215
218
|
});
|
|
216
219
|
|
|
217
220
|
test("organization invite email lets a new user join without creating a separate org", async ({
|
|
@@ -357,7 +360,7 @@ test("organization members page lets an admin edit a member name", async ({ page
|
|
|
357
360
|
expect(afterName?.id).toBe(invitedMemberId);
|
|
358
361
|
expect(afterName?.name).toBe("Kitchen lead");
|
|
359
362
|
expect(afterName?.userId).toBeNull();
|
|
360
|
-
await expect(
|
|
363
|
+
await expect(page.getByRole("row").filter({ hasText: "Kitchen lead" })).toBeVisible();
|
|
361
364
|
});
|
|
362
365
|
|
|
363
366
|
test("organization members page resends, updates, cancels, and revives an invited seat", async ({
|
|
@@ -491,9 +494,11 @@ test("admin creates, bans, unbans, and authenticates a user", async ({ page, req
|
|
|
491
494
|
await dialog.getByPlaceholder(/enter user's email/i).fill(email);
|
|
492
495
|
await dialog.getByPlaceholder(/enter password/i).fill(password);
|
|
493
496
|
await dialog.getByRole("button", { name: /^create user$/i }).click();
|
|
497
|
+
await expect(dialog).toBeHidden();
|
|
494
498
|
|
|
499
|
+
await page.locator('input[name="search"]').fill(email);
|
|
495
500
|
const createdRow = page.getByRole("row").filter({ hasText: email });
|
|
496
|
-
await expect(createdRow).toBeVisible();
|
|
501
|
+
await expect(createdRow).toBeVisible({ timeout: 10_000 });
|
|
497
502
|
await expect(createdRow.getByText(/^verified$/i)).toBeVisible();
|
|
498
503
|
await createdRow.getByLabel("User actions").click();
|
|
499
504
|
await page.getByRole("menuitem", { name: /^ban$/i }).click();
|
|
@@ -529,21 +534,6 @@ test("admin creates, bans, unbans, and authenticates a user", async ({ page, req
|
|
|
529
534
|
.toBe("admin");
|
|
530
535
|
|
|
531
536
|
await logout(page);
|
|
532
|
-
const verificationResponse = await request.post(
|
|
533
|
-
`${profiles.standard.serverUrl}/api/auth/send-verification-email`,
|
|
534
|
-
{
|
|
535
|
-
data: {
|
|
536
|
-
email,
|
|
537
|
-
callbackURL: `${profiles.standard.webUrl}/login`,
|
|
538
|
-
},
|
|
539
|
-
}
|
|
540
|
-
);
|
|
541
|
-
expect(verificationResponse.ok()).toBe(true);
|
|
542
|
-
const verification = await latestEmail(request, profile, {
|
|
543
|
-
to: email,
|
|
544
|
-
templateId: "verification",
|
|
545
|
-
});
|
|
546
|
-
await request.get(emailUrl(verification));
|
|
547
537
|
await login(page, email, password);
|
|
548
538
|
const adminListResponse = await authFetch(page, profile, "/api/auth/admin/list-users");
|
|
549
539
|
expect(
|
|
@@ -561,6 +551,7 @@ test("admin verifies email, changes user role, and sets password from users list
|
|
|
561
551
|
const newPassword = "password5678";
|
|
562
552
|
|
|
563
553
|
await signUp(page, email, password);
|
|
554
|
+
await expect(page.getByText(/verification email sent/i)).toBeVisible();
|
|
564
555
|
const before = await getUserState(request, profile, email);
|
|
565
556
|
expect(before.user.emailVerified).toBe(false);
|
|
566
557
|
expect(before.user.role).toBe("user");
|
|
@@ -603,9 +594,7 @@ test("admin verifies email, changes user role, and sets password from users list
|
|
|
603
594
|
await page.locator('input[name="search"]').fill(profiles.standard.adminEmail);
|
|
604
595
|
const adminRow = page.getByRole("row").filter({ hasText: profiles.standard.adminEmail });
|
|
605
596
|
await expect(adminRow).toBeVisible();
|
|
606
|
-
await expect(
|
|
607
|
-
adminRow.getByRole("button", { name: `Role for ${profiles.standard.adminEmail}` })
|
|
608
|
-
).toBeDisabled();
|
|
597
|
+
await expect(adminRow.locator('[data-slot="select-trigger"]')).toBeDisabled();
|
|
609
598
|
|
|
610
599
|
await logout(page);
|
|
611
600
|
await login(page, email, newPassword);
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
DROP INDEX `users_stripe_customer_id_unique`;--> statement-breakpoint
|
|
2
|
+
ALTER TABLE `users` ADD `closed_at` integer;--> statement-breakpoint
|
|
3
|
+
ALTER TABLE `users` DROP COLUMN `stripe_customer_id`;--> statement-breakpoint
|
|
4
|
+
PRAGMA foreign_keys=OFF;--> statement-breakpoint
|
|
5
|
+
CREATE TABLE `__new_apikeys` (
|
|
6
|
+
`id` text PRIMARY KEY NOT NULL,
|
|
7
|
+
`name` text,
|
|
8
|
+
`start` text,
|
|
9
|
+
`prefix` text,
|
|
10
|
+
`key` text NOT NULL,
|
|
11
|
+
`config_id` text DEFAULT 'default' NOT NULL,
|
|
12
|
+
`reference_id` text NOT NULL,
|
|
13
|
+
`user_id` text,
|
|
14
|
+
`refill_interval` integer,
|
|
15
|
+
`refill_amount` integer,
|
|
16
|
+
`last_refill_at` integer,
|
|
17
|
+
`enabled` integer NOT NULL,
|
|
18
|
+
`rate_limit_enabled` integer NOT NULL,
|
|
19
|
+
`rate_limit_time_window` integer,
|
|
20
|
+
`rate_limit_max` integer,
|
|
21
|
+
`request_count` integer NOT NULL,
|
|
22
|
+
`remaining` integer,
|
|
23
|
+
`last_request` integer,
|
|
24
|
+
`expires_at` integer,
|
|
25
|
+
`created_at` integer NOT NULL,
|
|
26
|
+
`updated_at` integer NOT NULL,
|
|
27
|
+
`permissions` text,
|
|
28
|
+
`metadata` text,
|
|
29
|
+
FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action
|
|
30
|
+
);
|
|
31
|
+
--> statement-breakpoint
|
|
32
|
+
INSERT INTO `__new_apikeys`("id", "name", "start", "prefix", "key", "config_id", "reference_id", "user_id", "refill_interval", "refill_amount", "last_refill_at", "enabled", "rate_limit_enabled", "rate_limit_time_window", "rate_limit_max", "request_count", "remaining", "last_request", "expires_at", "created_at", "updated_at", "permissions", "metadata") SELECT "id", "name", "start", "prefix", "key", 'default', "user_id", "user_id", "refill_interval", "refill_amount", "last_refill_at", "enabled", "rate_limit_enabled", "rate_limit_time_window", "rate_limit_max", "request_count", "remaining", "last_request", "expires_at", "created_at", "updated_at", "permissions", "metadata" FROM `apikeys`;--> statement-breakpoint
|
|
33
|
+
DROP TABLE `apikeys`;--> statement-breakpoint
|
|
34
|
+
ALTER TABLE `__new_apikeys` RENAME TO `apikeys`;--> statement-breakpoint
|
|
35
|
+
PRAGMA foreign_keys=ON;--> statement-breakpoint
|
|
36
|
+
ALTER TABLE `organizations` ADD `currency` text;--> statement-breakpoint
|
|
37
|
+
ALTER TABLE `organizations` ADD `stripe_customer_id` text;--> statement-breakpoint
|
|
38
|
+
ALTER TABLE `organizations` ADD `stripe_sandbox_customer_id` text;--> statement-breakpoint
|
|
39
|
+
ALTER TABLE `organizations` ADD `billing_exempt` integer DEFAULT false NOT NULL;--> statement-breakpoint
|
|
40
|
+
ALTER TABLE `organizations` ADD `allow_cardless_trial` integer DEFAULT false NOT NULL;--> statement-breakpoint
|
|
41
|
+
ALTER TABLE `organizations` ADD `cardless_trial_consumed` text DEFAULT '{}';--> statement-breakpoint
|
|
42
|
+
ALTER TABLE `organizations` ADD `closed_at` integer;--> statement-breakpoint
|
|
43
|
+
CREATE UNIQUE INDEX `organizations_stripe_customer_id_unique` ON `organizations` (`stripe_customer_id`);--> statement-breakpoint
|
|
44
|
+
CREATE UNIQUE INDEX `organizations_stripe_sandbox_customer_id_unique` ON `organizations` (`stripe_sandbox_customer_id`);--> statement-breakpoint
|
|
45
|
+
ALTER TABLE `files` DROP COLUMN `team_id`;
|