@objectstack/platform-objects 4.0.5 → 4.1.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/dist/apps/index.d.mts +16 -48
- package/dist/apps/index.d.ts +16 -48
- package/dist/apps/index.js +139 -217
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/index.mjs +140 -212
- package/dist/apps/index.mjs.map +1 -1
- package/dist/audit/index.d.mts +38990 -51
- package/dist/audit/index.d.ts +38990 -51
- package/dist/audit/index.js +1428 -0
- package/dist/audit/index.js.map +1 -1
- package/dist/audit/index.mjs +1417 -1
- package/dist/audit/index.mjs.map +1 -1
- package/dist/identity/index.d.mts +14869 -2802
- package/dist/identity/index.d.ts +14869 -2802
- package/dist/identity/index.js +1090 -6
- package/dist/identity/index.js.map +1 -1
- package/dist/identity/index.mjs +1089 -7
- package/dist/identity/index.mjs.map +1 -1
- package/dist/index.d.mts +8 -7
- package/dist/index.d.ts +8 -7
- package/dist/index.js +3652 -1482
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3633 -1465
- package/dist/index.mjs.map +1 -1
- package/dist/integration/index.d.mts +2905 -0
- package/dist/integration/index.d.ts +2905 -0
- package/dist/integration/index.js +140 -0
- package/dist/integration/index.js.map +1 -0
- package/dist/integration/index.mjs +138 -0
- package/dist/integration/index.mjs.map +1 -0
- package/dist/metadata/index.d.mts +577 -21181
- package/dist/metadata/index.d.ts +577 -21181
- package/dist/metadata/index.js +29 -619
- package/dist/metadata/index.js.map +1 -1
- package/dist/metadata/index.mjs +30 -615
- package/dist/metadata/index.mjs.map +1 -1
- package/dist/security/index.d.mts +7278 -46
- package/dist/security/index.d.ts +7278 -46
- package/dist/security/index.js +540 -0
- package/dist/security/index.js.map +1 -1
- package/dist/security/index.mjs +539 -1
- package/dist/security/index.mjs.map +1 -1
- package/dist/system/index.d.mts +8409 -0
- package/dist/system/index.d.ts +8409 -0
- package/dist/system/index.js +395 -0
- package/dist/system/index.js.map +1 -0
- package/dist/system/index.mjs +391 -0
- package/dist/system/index.mjs.map +1 -0
- package/package.json +13 -8
- package/dist/tenant/index.d.mts +0 -18464
- package/dist/tenant/index.d.ts +0 -18464
- package/dist/tenant/index.js +0 -741
- package/dist/tenant/index.js.map +0 -1
- package/dist/tenant/index.mjs +0 -733
- package/dist/tenant/index.mjs.map +0 -1
- /package/dist/{state-machine.zod-BFg-VE0M.d-Ek3_yo9P.d.mts → state-machine.zod-BNanU03M.d-Ek3_yo9P.d.mts} +0 -0
- /package/dist/{state-machine.zod-BFg-VE0M.d-Ek3_yo9P.d.ts → state-machine.zod-BNanU03M.d-Ek3_yo9P.d.ts} +0 -0
package/dist/identity/index.mjs
CHANGED
|
@@ -12,6 +12,149 @@ var SysUser = ObjectSchema.create({
|
|
|
12
12
|
displayNameField: "name",
|
|
13
13
|
titleFormat: "{name}",
|
|
14
14
|
compactLayout: ["name", "email", "email_verified"],
|
|
15
|
+
// Custom actions — generic CRUD is suppressed because user accounts are
|
|
16
|
+
// managed by better-auth, but we still need first-class affordances for
|
|
17
|
+
// common operations. Each action delegates to a Console-side named script
|
|
18
|
+
// registered on the ActionRunner (see objectui `AppContent.tsx`). Adding
|
|
19
|
+
// new affordances (reset password, revoke session, …) is now a pure
|
|
20
|
+
// schema + script-registration change — no per-view code.
|
|
21
|
+
actions: [
|
|
22
|
+
{
|
|
23
|
+
name: "invite_user",
|
|
24
|
+
label: "Invite User",
|
|
25
|
+
icon: "user-plus",
|
|
26
|
+
variant: "primary",
|
|
27
|
+
locations: ["list_toolbar"],
|
|
28
|
+
type: "api",
|
|
29
|
+
target: "/api/v1/auth/organization/invite-member",
|
|
30
|
+
successMessage: "Invitation sent",
|
|
31
|
+
refreshAfter: true,
|
|
32
|
+
params: [
|
|
33
|
+
{ field: "email", required: true },
|
|
34
|
+
{ field: "role", objectOverride: "sys_member", required: true }
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
// ── Platform admin operations (require better-auth `admin` plugin) ─
|
|
38
|
+
//
|
|
39
|
+
// These actions hit /api/v1/auth/admin/* endpoints that are only
|
|
40
|
+
// wired when `auth.plugins.admin` is enabled. When the plugin is
|
|
41
|
+
// disabled the actions still render (schema is static) but server
|
|
42
|
+
// returns 404. UI surfaces them under the row menu so platform
|
|
43
|
+
// admins can manage accounts without dropping to SQL or
|
|
44
|
+
// a custom Setup wizard.
|
|
45
|
+
{
|
|
46
|
+
name: "ban_user",
|
|
47
|
+
label: "Ban User",
|
|
48
|
+
icon: "ban",
|
|
49
|
+
variant: "danger",
|
|
50
|
+
locations: ["list_item"],
|
|
51
|
+
type: "api",
|
|
52
|
+
target: "/api/v1/auth/admin/ban-user",
|
|
53
|
+
recordIdParam: "userId",
|
|
54
|
+
successMessage: "User banned",
|
|
55
|
+
refreshAfter: true,
|
|
56
|
+
confirmText: "Ban this user? They will be signed out and unable to sign in until unbanned.",
|
|
57
|
+
params: [
|
|
58
|
+
{ name: "banReason", label: "Ban Reason", type: "text", required: false }
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "unban_user",
|
|
63
|
+
label: "Unban User",
|
|
64
|
+
icon: "check-circle-2",
|
|
65
|
+
variant: "secondary",
|
|
66
|
+
locations: ["list_item"],
|
|
67
|
+
type: "api",
|
|
68
|
+
target: "/api/v1/auth/admin/unban-user",
|
|
69
|
+
recordIdParam: "userId",
|
|
70
|
+
successMessage: "User unbanned",
|
|
71
|
+
refreshAfter: true
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "set_user_password",
|
|
75
|
+
label: "Set Password",
|
|
76
|
+
icon: "key-round",
|
|
77
|
+
variant: "secondary",
|
|
78
|
+
locations: ["list_item"],
|
|
79
|
+
type: "api",
|
|
80
|
+
target: "/api/v1/auth/admin/set-user-password",
|
|
81
|
+
recordIdParam: "userId",
|
|
82
|
+
successMessage: "Password updated",
|
|
83
|
+
refreshAfter: false,
|
|
84
|
+
params: [
|
|
85
|
+
{ name: "newPassword", label: "New Password", type: "text", required: true }
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "set_user_role",
|
|
90
|
+
label: "Set Platform Role",
|
|
91
|
+
icon: "shield-check",
|
|
92
|
+
variant: "secondary",
|
|
93
|
+
locations: ["list_item"],
|
|
94
|
+
type: "api",
|
|
95
|
+
target: "/api/v1/auth/admin/set-role",
|
|
96
|
+
recordIdParam: "userId",
|
|
97
|
+
successMessage: "Role updated",
|
|
98
|
+
refreshAfter: true,
|
|
99
|
+
params: [
|
|
100
|
+
{ name: "role", label: "Platform Role", type: "text", required: true }
|
|
101
|
+
]
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: "impersonate_user",
|
|
105
|
+
label: "Impersonate User",
|
|
106
|
+
icon: "user-cog",
|
|
107
|
+
variant: "secondary",
|
|
108
|
+
locations: ["list_item"],
|
|
109
|
+
type: "api",
|
|
110
|
+
target: "/api/v1/auth/admin/impersonate-user",
|
|
111
|
+
recordIdParam: "userId",
|
|
112
|
+
successMessage: "Now impersonating user",
|
|
113
|
+
refreshAfter: true,
|
|
114
|
+
confirmText: "Start an impersonation session for this user? Use only for legitimate support cases \u2014 actions will be logged."
|
|
115
|
+
}
|
|
116
|
+
],
|
|
117
|
+
listViews: {
|
|
118
|
+
all_users: {
|
|
119
|
+
type: "grid",
|
|
120
|
+
name: "all_users",
|
|
121
|
+
label: "All Users",
|
|
122
|
+
data: { provider: "object", object: "sys_user" },
|
|
123
|
+
columns: ["name", "email", "email_verified", "two_factor_enabled", "created_at"],
|
|
124
|
+
sort: [{ field: "name", order: "asc" }],
|
|
125
|
+
pagination: { pageSize: 50 }
|
|
126
|
+
},
|
|
127
|
+
unverified: {
|
|
128
|
+
type: "grid",
|
|
129
|
+
name: "unverified",
|
|
130
|
+
label: "Unverified",
|
|
131
|
+
data: { provider: "object", object: "sys_user" },
|
|
132
|
+
columns: ["name", "email", "created_at"],
|
|
133
|
+
filter: [{ field: "email_verified", operator: "equals", value: false }],
|
|
134
|
+
sort: [{ field: "created_at", order: "desc" }],
|
|
135
|
+
pagination: { pageSize: 50 }
|
|
136
|
+
},
|
|
137
|
+
two_factor: {
|
|
138
|
+
type: "grid",
|
|
139
|
+
name: "two_factor",
|
|
140
|
+
label: "2FA Enabled",
|
|
141
|
+
data: { provider: "object", object: "sys_user" },
|
|
142
|
+
columns: ["name", "email", "two_factor_enabled", "updated_at"],
|
|
143
|
+
filter: [{ field: "two_factor_enabled", operator: "equals", value: true }],
|
|
144
|
+
sort: [{ field: "name", order: "asc" }],
|
|
145
|
+
pagination: { pageSize: 50 }
|
|
146
|
+
},
|
|
147
|
+
banned: {
|
|
148
|
+
type: "grid",
|
|
149
|
+
name: "banned",
|
|
150
|
+
label: "Banned",
|
|
151
|
+
data: { provider: "object", object: "sys_user" },
|
|
152
|
+
columns: ["name", "email", "banned", "ban_reason", "ban_expires"],
|
|
153
|
+
filter: [{ field: "banned", operator: "equals", value: true }],
|
|
154
|
+
sort: [{ field: "updated_at", order: "desc" }],
|
|
155
|
+
pagination: { pageSize: 50 }
|
|
156
|
+
}
|
|
157
|
+
},
|
|
15
158
|
fields: {
|
|
16
159
|
// ── Identity (primary business fields) ───────────────────────
|
|
17
160
|
name: Field.text({
|
|
@@ -38,6 +181,32 @@ var SysUser = ObjectSchema.create({
|
|
|
38
181
|
group: "Identity",
|
|
39
182
|
description: "Whether two-factor authentication is enabled for this user. Maintained by the better-auth `twoFactor` plugin."
|
|
40
183
|
}),
|
|
184
|
+
// ── Admin (managed by better-auth `admin` plugin when enabled) ───
|
|
185
|
+
role: Field.text({
|
|
186
|
+
label: "Platform Role",
|
|
187
|
+
required: false,
|
|
188
|
+
maxLength: 64,
|
|
189
|
+
group: "Admin",
|
|
190
|
+
description: "Platform-level role (admin, user, \u2026). Set via the Set Platform Role action."
|
|
191
|
+
}),
|
|
192
|
+
banned: Field.boolean({
|
|
193
|
+
label: "Banned",
|
|
194
|
+
defaultValue: false,
|
|
195
|
+
group: "Admin",
|
|
196
|
+
description: "When true, the user cannot sign in. Toggle via Ban User / Unban User actions."
|
|
197
|
+
}),
|
|
198
|
+
ban_reason: Field.text({
|
|
199
|
+
label: "Ban Reason",
|
|
200
|
+
required: false,
|
|
201
|
+
maxLength: 255,
|
|
202
|
+
group: "Admin"
|
|
203
|
+
}),
|
|
204
|
+
ban_expires: Field.datetime({
|
|
205
|
+
label: "Ban Expires",
|
|
206
|
+
required: false,
|
|
207
|
+
group: "Admin",
|
|
208
|
+
description: "When set, the ban auto-clears at this time."
|
|
209
|
+
}),
|
|
41
210
|
// ── Profile ──────────────────────────────────────────────────
|
|
42
211
|
image: Field.url({
|
|
43
212
|
label: "Profile Image",
|
|
@@ -98,6 +267,62 @@ var SysSession = ObjectSchema.create({
|
|
|
98
267
|
displayNameField: "user_id",
|
|
99
268
|
titleFormat: "Session \u2014 {user_id}",
|
|
100
269
|
compactLayout: ["user_id", "ip_address", "expires_at"],
|
|
270
|
+
// Custom actions — sessions are managed by better-auth (generic CRUD
|
|
271
|
+
// suppressed). "Sign out other devices" is the high-value self-service
|
|
272
|
+
// affordance every IdP exposes. Maps to better-auth's
|
|
273
|
+
// `revoke-other-sessions` endpoint which terminates every session for
|
|
274
|
+
// the current user except the one making the request.
|
|
275
|
+
actions: [
|
|
276
|
+
{
|
|
277
|
+
name: "revoke_my_other_sessions",
|
|
278
|
+
label: "Sign out other devices",
|
|
279
|
+
icon: "log-out",
|
|
280
|
+
variant: "danger",
|
|
281
|
+
locations: ["list_toolbar"],
|
|
282
|
+
type: "api",
|
|
283
|
+
target: "/api/v1/auth/revoke-other-sessions",
|
|
284
|
+
confirmText: "Sign out of every other device where you're currently logged in? Your current session will remain active.",
|
|
285
|
+
successMessage: "All other sessions revoked",
|
|
286
|
+
refreshAfter: true
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
name: "revoke_session",
|
|
290
|
+
label: "Revoke Session",
|
|
291
|
+
icon: "log-out",
|
|
292
|
+
variant: "danger",
|
|
293
|
+
mode: "delete",
|
|
294
|
+
locations: ["list_item"],
|
|
295
|
+
type: "api",
|
|
296
|
+
target: "/api/v1/auth/revoke-session",
|
|
297
|
+
// better-auth `revoke-session` keys off the session token, not the id.
|
|
298
|
+
recordIdParam: "token",
|
|
299
|
+
recordIdField: "token",
|
|
300
|
+
confirmText: "Revoke this session? The user will be signed out from that device.",
|
|
301
|
+
successMessage: "Session revoked",
|
|
302
|
+
refreshAfter: true
|
|
303
|
+
}
|
|
304
|
+
],
|
|
305
|
+
listViews: {
|
|
306
|
+
mine: {
|
|
307
|
+
type: "grid",
|
|
308
|
+
name: "mine",
|
|
309
|
+
label: "My Sessions",
|
|
310
|
+
data: { provider: "object", object: "sys_session" },
|
|
311
|
+
columns: ["ip_address", "active_organization_id", "created_at", "expires_at"],
|
|
312
|
+
filter: [{ field: "user_id", operator: "equals", value: "{current_user_id}" }],
|
|
313
|
+
sort: [{ field: "created_at", order: "desc" }],
|
|
314
|
+
pagination: { pageSize: 50 }
|
|
315
|
+
},
|
|
316
|
+
all_sessions: {
|
|
317
|
+
type: "grid",
|
|
318
|
+
name: "all_sessions",
|
|
319
|
+
label: "All",
|
|
320
|
+
data: { provider: "object", object: "sys_session" },
|
|
321
|
+
columns: ["user_id", "ip_address", "active_organization_id", "created_at", "expires_at"],
|
|
322
|
+
sort: [{ field: "created_at", order: "desc" }],
|
|
323
|
+
pagination: { pageSize: 50 }
|
|
324
|
+
}
|
|
325
|
+
},
|
|
101
326
|
fields: {
|
|
102
327
|
// ── Session owner & expiry ──────────────────────────────────
|
|
103
328
|
user_id: Field.lookup("sys_user", {
|
|
@@ -135,6 +360,13 @@ var SysSession = ObjectSchema.create({
|
|
|
135
360
|
required: false,
|
|
136
361
|
group: "Client"
|
|
137
362
|
}),
|
|
363
|
+
// ── Admin (managed by better-auth `admin` plugin) ────────────
|
|
364
|
+
impersonated_by: Field.lookup("sys_user", {
|
|
365
|
+
label: "Impersonated By",
|
|
366
|
+
required: false,
|
|
367
|
+
group: "Admin",
|
|
368
|
+
description: "User id of the admin that started this impersonation session, if any."
|
|
369
|
+
}),
|
|
138
370
|
// ── Secret (hidden by default) ──────────────────────────────
|
|
139
371
|
token: Field.text({
|
|
140
372
|
label: "Session Token",
|
|
@@ -189,6 +421,37 @@ var SysAccount = ObjectSchema.create({
|
|
|
189
421
|
description: "OAuth and authentication provider accounts",
|
|
190
422
|
titleFormat: "{provider_id} - {account_id}",
|
|
191
423
|
compactLayout: ["provider_id", "user_id", "account_id"],
|
|
424
|
+
listViews: {
|
|
425
|
+
mine: {
|
|
426
|
+
type: "grid",
|
|
427
|
+
name: "mine",
|
|
428
|
+
label: "My Links",
|
|
429
|
+
data: { provider: "object", object: "sys_account" },
|
|
430
|
+
columns: ["provider_id", "account_id", "created_at", "updated_at"],
|
|
431
|
+
filter: [{ field: "user_id", operator: "equals", value: "{current_user_id}" }],
|
|
432
|
+
sort: [{ field: "provider_id", order: "asc" }],
|
|
433
|
+
pagination: { pageSize: 50 }
|
|
434
|
+
},
|
|
435
|
+
by_provider: {
|
|
436
|
+
type: "grid",
|
|
437
|
+
name: "by_provider",
|
|
438
|
+
label: "By Provider",
|
|
439
|
+
data: { provider: "object", object: "sys_account" },
|
|
440
|
+
columns: ["provider_id", "user_id", "account_id", "created_at"],
|
|
441
|
+
sort: [{ field: "provider_id", order: "asc" }, { field: "created_at", order: "desc" }],
|
|
442
|
+
grouping: { fields: [{ field: "provider_id", order: "asc", collapsed: false }] },
|
|
443
|
+
pagination: { pageSize: 100 }
|
|
444
|
+
},
|
|
445
|
+
all_links: {
|
|
446
|
+
type: "grid",
|
|
447
|
+
name: "all_links",
|
|
448
|
+
label: "All",
|
|
449
|
+
data: { provider: "object", object: "sys_account" },
|
|
450
|
+
columns: ["provider_id", "user_id", "account_id", "created_at", "updated_at"],
|
|
451
|
+
sort: [{ field: "created_at", order: "desc" }],
|
|
452
|
+
pagination: { pageSize: 100 }
|
|
453
|
+
}
|
|
454
|
+
},
|
|
192
455
|
fields: {
|
|
193
456
|
id: Field.text({
|
|
194
457
|
label: "Account ID",
|
|
@@ -329,6 +592,104 @@ var SysOrganization = ObjectSchema.create({
|
|
|
329
592
|
displayNameField: "name",
|
|
330
593
|
titleFormat: "{name}",
|
|
331
594
|
compactLayout: ["name", "slug"],
|
|
595
|
+
// Custom actions — generic CRUD is suppressed (better-auth-managed),
|
|
596
|
+
// but admins still need to create new orgs from the Setup app.
|
|
597
|
+
actions: [
|
|
598
|
+
{
|
|
599
|
+
name: "create_organization",
|
|
600
|
+
label: "Create Organization",
|
|
601
|
+
icon: "plus",
|
|
602
|
+
variant: "primary",
|
|
603
|
+
locations: ["list_toolbar"],
|
|
604
|
+
type: "api",
|
|
605
|
+
target: "/api/v1/auth/organization/create",
|
|
606
|
+
successMessage: "Organization created",
|
|
607
|
+
refreshAfter: true,
|
|
608
|
+
params: [
|
|
609
|
+
{ field: "name", required: true },
|
|
610
|
+
{ field: "slug", required: true },
|
|
611
|
+
{ field: "logo" }
|
|
612
|
+
]
|
|
613
|
+
},
|
|
614
|
+
{
|
|
615
|
+
name: "update_organization",
|
|
616
|
+
label: "Edit Organization",
|
|
617
|
+
icon: "pencil",
|
|
618
|
+
mode: "edit",
|
|
619
|
+
locations: ["list_item"],
|
|
620
|
+
type: "api",
|
|
621
|
+
target: "/api/v1/auth/organization/update",
|
|
622
|
+
recordIdParam: "organizationId",
|
|
623
|
+
// better-auth `organization/update` nests editable fields under `data`.
|
|
624
|
+
bodyShape: { wrap: "data" },
|
|
625
|
+
successMessage: "Organization updated",
|
|
626
|
+
refreshAfter: true,
|
|
627
|
+
params: [
|
|
628
|
+
{ field: "name", required: true, defaultFromRow: true },
|
|
629
|
+
{ field: "slug", required: true, defaultFromRow: true },
|
|
630
|
+
{ field: "logo", defaultFromRow: true }
|
|
631
|
+
]
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
name: "delete_organization",
|
|
635
|
+
label: "Delete Organization",
|
|
636
|
+
icon: "trash-2",
|
|
637
|
+
variant: "danger",
|
|
638
|
+
mode: "delete",
|
|
639
|
+
locations: ["list_item"],
|
|
640
|
+
type: "api",
|
|
641
|
+
target: "/api/v1/auth/organization/delete",
|
|
642
|
+
recordIdParam: "organizationId",
|
|
643
|
+
confirmText: "Delete this organization? All members will lose access immediately. This cannot be undone.",
|
|
644
|
+
successMessage: "Organization deleted",
|
|
645
|
+
refreshAfter: true
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
// Switch the caller's active organization context. Standard
|
|
649
|
+
// better-auth endpoint; no extra params needed (org id ships as
|
|
650
|
+
// the row id). Used from the Setup list and the record header so
|
|
651
|
+
// admins can context-switch without leaving the page.
|
|
652
|
+
name: "set_active_organization",
|
|
653
|
+
label: "Set Active",
|
|
654
|
+
icon: "check-circle-2",
|
|
655
|
+
variant: "secondary",
|
|
656
|
+
mode: "custom",
|
|
657
|
+
locations: ["list_item", "record_header"],
|
|
658
|
+
type: "api",
|
|
659
|
+
target: "/api/v1/auth/organization/set-active",
|
|
660
|
+
recordIdParam: "organizationId",
|
|
661
|
+
successMessage: "Active organization switched",
|
|
662
|
+
refreshAfter: true
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
// Current user leaves the org. Distinct from `delete_organization`
|
|
666
|
+
// (admin-only, destroys the org) — `leave` only removes the caller's
|
|
667
|
+
// own membership. Better-auth: `organization/leave { organizationId }`.
|
|
668
|
+
name: "leave_organization",
|
|
669
|
+
label: "Leave Organization",
|
|
670
|
+
icon: "log-out",
|
|
671
|
+
variant: "danger",
|
|
672
|
+
mode: "custom",
|
|
673
|
+
locations: ["list_item", "record_header"],
|
|
674
|
+
type: "api",
|
|
675
|
+
target: "/api/v1/auth/organization/leave",
|
|
676
|
+
recordIdParam: "organizationId",
|
|
677
|
+
confirmText: "Leave this organization? You will lose access to all of its resources.",
|
|
678
|
+
successMessage: "You have left the organization",
|
|
679
|
+
refreshAfter: true
|
|
680
|
+
}
|
|
681
|
+
],
|
|
682
|
+
listViews: {
|
|
683
|
+
all_orgs: {
|
|
684
|
+
type: "grid",
|
|
685
|
+
name: "all_orgs",
|
|
686
|
+
label: "All",
|
|
687
|
+
data: { provider: "object", object: "sys_organization" },
|
|
688
|
+
columns: ["name", "slug", "created_at", "updated_at"],
|
|
689
|
+
sort: [{ field: "name", order: "asc" }],
|
|
690
|
+
pagination: { pageSize: 50 }
|
|
691
|
+
}
|
|
692
|
+
},
|
|
332
693
|
fields: {
|
|
333
694
|
// ── Identity ─────────────────────────────────────────────────
|
|
334
695
|
name: Field.text({
|
|
@@ -402,6 +763,63 @@ var SysMember = ObjectSchema.create({
|
|
|
402
763
|
description: "Organization membership records",
|
|
403
764
|
titleFormat: "{user_id} in {organization_id}",
|
|
404
765
|
compactLayout: ["user_id", "organization_id", "role"],
|
|
766
|
+
// Row-level actions: better-auth `organization/update-member-role` and
|
|
767
|
+
// `organization/remove-member`. Generic CRUD is suppressed on better-auth
|
|
768
|
+
// managed tables, so these are the canonical edit/delete entry points.
|
|
769
|
+
// The `add_member` toolbar action covers the admin "attach an existing
|
|
770
|
+
// user directly without sending an invitation" flow.
|
|
771
|
+
actions: [
|
|
772
|
+
{
|
|
773
|
+
// Admin-only: directly attach an existing user to the active org,
|
|
774
|
+
// bypassing the invite-accept flow. Better-auth:
|
|
775
|
+
// `organization/add-member { userId, role, organizationId?, teamId? }`.
|
|
776
|
+
// organizationId/teamId default to the caller's active org/team when
|
|
777
|
+
// omitted, so we leave them as optional params.
|
|
778
|
+
name: "add_member",
|
|
779
|
+
label: "Add Member",
|
|
780
|
+
icon: "user-plus",
|
|
781
|
+
variant: "primary",
|
|
782
|
+
locations: ["list_toolbar"],
|
|
783
|
+
type: "api",
|
|
784
|
+
target: "/api/v1/auth/organization/add-member",
|
|
785
|
+
successMessage: "Member added",
|
|
786
|
+
refreshAfter: true,
|
|
787
|
+
params: [
|
|
788
|
+
{ name: "userId", field: "user_id", required: true },
|
|
789
|
+
{ field: "role", required: true },
|
|
790
|
+
{ name: "organizationId", field: "organization_id" }
|
|
791
|
+
]
|
|
792
|
+
},
|
|
793
|
+
{
|
|
794
|
+
name: "update_member_role",
|
|
795
|
+
label: "Change Role",
|
|
796
|
+
icon: "shield",
|
|
797
|
+
mode: "edit",
|
|
798
|
+
locations: ["list_item"],
|
|
799
|
+
type: "api",
|
|
800
|
+
target: "/api/v1/auth/organization/update-member-role",
|
|
801
|
+
recordIdParam: "memberId",
|
|
802
|
+
successMessage: "Member role updated",
|
|
803
|
+
refreshAfter: true,
|
|
804
|
+
params: [
|
|
805
|
+
{ field: "role", required: true, defaultFromRow: true }
|
|
806
|
+
]
|
|
807
|
+
},
|
|
808
|
+
{
|
|
809
|
+
name: "remove_member",
|
|
810
|
+
label: "Remove Member",
|
|
811
|
+
icon: "user-minus",
|
|
812
|
+
variant: "danger",
|
|
813
|
+
mode: "delete",
|
|
814
|
+
locations: ["list_item"],
|
|
815
|
+
type: "api",
|
|
816
|
+
target: "/api/v1/auth/organization/remove-member",
|
|
817
|
+
recordIdParam: "memberIdOrEmail",
|
|
818
|
+
confirmText: "Remove this member from the organization? They will lose access to all org resources.",
|
|
819
|
+
successMessage: "Member removed",
|
|
820
|
+
refreshAfter: true
|
|
821
|
+
}
|
|
822
|
+
],
|
|
405
823
|
fields: {
|
|
406
824
|
id: Field.text({
|
|
407
825
|
label: "Member ID",
|
|
@@ -421,11 +839,16 @@ var SysMember = ObjectSchema.create({
|
|
|
421
839
|
label: "User",
|
|
422
840
|
required: true
|
|
423
841
|
}),
|
|
424
|
-
role: Field.
|
|
842
|
+
role: Field.select({
|
|
425
843
|
label: "Role",
|
|
426
844
|
required: false,
|
|
427
|
-
description: "Member role within the organization
|
|
428
|
-
|
|
845
|
+
description: "Member role within the organization",
|
|
846
|
+
options: [
|
|
847
|
+
{ label: "Owner", value: "owner" },
|
|
848
|
+
{ label: "Admin", value: "admin" },
|
|
849
|
+
{ label: "Member", value: "member" }
|
|
850
|
+
],
|
|
851
|
+
defaultValue: "member"
|
|
429
852
|
})
|
|
430
853
|
},
|
|
431
854
|
indexes: [
|
|
@@ -451,6 +874,97 @@ var SysInvitation = ObjectSchema.create({
|
|
|
451
874
|
description: "Organization invitations for user onboarding",
|
|
452
875
|
titleFormat: "Invitation to {organization_id}",
|
|
453
876
|
compactLayout: ["email", "organization_id", "status"],
|
|
877
|
+
// Custom actions — generic CRUD is suppressed (better-auth-managed).
|
|
878
|
+
// Mirror the `invite_user` toolbar action from sys_user here so admins
|
|
879
|
+
// landing on the Invitations page get an obvious entry point.
|
|
880
|
+
actions: [
|
|
881
|
+
{
|
|
882
|
+
name: "invite_user",
|
|
883
|
+
label: "Invite User",
|
|
884
|
+
icon: "user-plus",
|
|
885
|
+
variant: "primary",
|
|
886
|
+
locations: ["list_toolbar"],
|
|
887
|
+
type: "api",
|
|
888
|
+
target: "/api/v1/auth/organization/invite-member",
|
|
889
|
+
successMessage: "Invitation sent",
|
|
890
|
+
refreshAfter: true,
|
|
891
|
+
params: [
|
|
892
|
+
{ field: "email", required: true },
|
|
893
|
+
{ field: "role", required: true }
|
|
894
|
+
]
|
|
895
|
+
},
|
|
896
|
+
{
|
|
897
|
+
name: "cancel_invitation",
|
|
898
|
+
label: "Cancel Invitation",
|
|
899
|
+
icon: "x-circle",
|
|
900
|
+
variant: "danger",
|
|
901
|
+
mode: "delete",
|
|
902
|
+
locations: ["list_item"],
|
|
903
|
+
type: "api",
|
|
904
|
+
target: "/api/v1/auth/organization/cancel-invitation",
|
|
905
|
+
recordIdParam: "invitationId",
|
|
906
|
+
confirmText: "Cancel this invitation? The recipient will no longer be able to accept it.",
|
|
907
|
+
successMessage: "Invitation canceled",
|
|
908
|
+
refreshAfter: true
|
|
909
|
+
},
|
|
910
|
+
{
|
|
911
|
+
name: "resend_invitation",
|
|
912
|
+
label: "Resend Invitation",
|
|
913
|
+
icon: "send",
|
|
914
|
+
variant: "secondary",
|
|
915
|
+
locations: ["list_item"],
|
|
916
|
+
type: "api",
|
|
917
|
+
target: "/api/v1/auth/organization/invite-member",
|
|
918
|
+
bodyExtra: { resend: true },
|
|
919
|
+
successMessage: "Invitation resent",
|
|
920
|
+
refreshAfter: true,
|
|
921
|
+
params: [
|
|
922
|
+
{ field: "email", required: true, defaultFromRow: true },
|
|
923
|
+
{ field: "role", required: true, defaultFromRow: true }
|
|
924
|
+
]
|
|
925
|
+
}
|
|
926
|
+
],
|
|
927
|
+
listViews: {
|
|
928
|
+
pending: {
|
|
929
|
+
type: "grid",
|
|
930
|
+
name: "pending",
|
|
931
|
+
label: "Pending",
|
|
932
|
+
data: { provider: "object", object: "sys_invitation" },
|
|
933
|
+
columns: ["email", "role", "organization_id", "inviter_id", "expires_at"],
|
|
934
|
+
filter: [{ field: "status", operator: "equals", value: "pending" }],
|
|
935
|
+
sort: [{ field: "expires_at", order: "asc" }],
|
|
936
|
+
pagination: { pageSize: 50 }
|
|
937
|
+
},
|
|
938
|
+
accepted: {
|
|
939
|
+
type: "grid",
|
|
940
|
+
name: "accepted",
|
|
941
|
+
label: "Accepted",
|
|
942
|
+
data: { provider: "object", object: "sys_invitation" },
|
|
943
|
+
columns: ["email", "role", "organization_id", "inviter_id", "created_at"],
|
|
944
|
+
filter: [{ field: "status", operator: "equals", value: "accepted" }],
|
|
945
|
+
sort: [{ field: "created_at", order: "desc" }],
|
|
946
|
+
pagination: { pageSize: 50 }
|
|
947
|
+
},
|
|
948
|
+
expired: {
|
|
949
|
+
type: "grid",
|
|
950
|
+
name: "expired",
|
|
951
|
+
label: "Expired / Canceled",
|
|
952
|
+
data: { provider: "object", object: "sys_invitation" },
|
|
953
|
+
columns: ["email", "status", "organization_id", "expires_at"],
|
|
954
|
+
filter: [{ field: "status", operator: "in", value: ["expired", "rejected", "canceled"] }],
|
|
955
|
+
sort: [{ field: "expires_at", order: "desc" }],
|
|
956
|
+
pagination: { pageSize: 50 }
|
|
957
|
+
},
|
|
958
|
+
all_invitations: {
|
|
959
|
+
type: "grid",
|
|
960
|
+
name: "all_invitations",
|
|
961
|
+
label: "All",
|
|
962
|
+
data: { provider: "object", object: "sys_invitation" },
|
|
963
|
+
columns: ["email", "status", "role", "organization_id", "inviter_id", "created_at"],
|
|
964
|
+
sort: [{ field: "created_at", order: "desc" }],
|
|
965
|
+
pagination: { pageSize: 50 }
|
|
966
|
+
}
|
|
967
|
+
},
|
|
454
968
|
fields: {
|
|
455
969
|
id: Field.text({
|
|
456
970
|
label: "Invitation ID",
|
|
@@ -471,11 +985,16 @@ var SysInvitation = ObjectSchema.create({
|
|
|
471
985
|
required: true,
|
|
472
986
|
description: "Email address of the invited user"
|
|
473
987
|
}),
|
|
474
|
-
role: Field.
|
|
988
|
+
role: Field.select({
|
|
475
989
|
label: "Role",
|
|
476
990
|
required: false,
|
|
477
|
-
|
|
478
|
-
|
|
991
|
+
description: "Role to assign upon acceptance",
|
|
992
|
+
options: [
|
|
993
|
+
{ label: "Owner", value: "owner" },
|
|
994
|
+
{ label: "Admin", value: "admin" },
|
|
995
|
+
{ label: "Member", value: "member" }
|
|
996
|
+
],
|
|
997
|
+
defaultValue: "member"
|
|
479
998
|
}),
|
|
480
999
|
status: Field.select(["pending", "accepted", "rejected", "expired", "canceled"], {
|
|
481
1000
|
label: "Status",
|
|
@@ -522,6 +1041,84 @@ var SysTeam = ObjectSchema.create({
|
|
|
522
1041
|
displayNameField: "name",
|
|
523
1042
|
titleFormat: "{name}",
|
|
524
1043
|
compactLayout: ["name", "organization_id"],
|
|
1044
|
+
// Custom actions calling better-auth's team endpoints. Generic CRUD is
|
|
1045
|
+
// suppressed (managedBy: 'better-auth'), so these are the canonical
|
|
1046
|
+
// entry points for create/update/delete.
|
|
1047
|
+
actions: [
|
|
1048
|
+
{
|
|
1049
|
+
// Better-auth: `organization/create-team { name, organizationId? }`.
|
|
1050
|
+
// organizationId defaults to the caller's active org when omitted.
|
|
1051
|
+
name: "create_team",
|
|
1052
|
+
label: "Create Team",
|
|
1053
|
+
icon: "plus",
|
|
1054
|
+
variant: "primary",
|
|
1055
|
+
locations: ["list_toolbar"],
|
|
1056
|
+
type: "api",
|
|
1057
|
+
target: "/api/v1/auth/organization/create-team",
|
|
1058
|
+
successMessage: "Team created",
|
|
1059
|
+
refreshAfter: true,
|
|
1060
|
+
params: [
|
|
1061
|
+
{ field: "name", required: true },
|
|
1062
|
+
{ name: "organizationId", field: "organization_id" }
|
|
1063
|
+
]
|
|
1064
|
+
},
|
|
1065
|
+
{
|
|
1066
|
+
// Better-auth: `organization/update-team { teamId, data: { name } }`.
|
|
1067
|
+
// teamId stays flat (top-level); the user-editable params nest under
|
|
1068
|
+
// `data` via bodyShape.
|
|
1069
|
+
name: "update_team",
|
|
1070
|
+
label: "Edit Team",
|
|
1071
|
+
icon: "pencil",
|
|
1072
|
+
mode: "edit",
|
|
1073
|
+
locations: ["list_item"],
|
|
1074
|
+
type: "api",
|
|
1075
|
+
target: "/api/v1/auth/organization/update-team",
|
|
1076
|
+
recordIdParam: "teamId",
|
|
1077
|
+
bodyShape: { wrap: "data" },
|
|
1078
|
+
successMessage: "Team updated",
|
|
1079
|
+
refreshAfter: true,
|
|
1080
|
+
params: [
|
|
1081
|
+
{ field: "name", required: true, defaultFromRow: true }
|
|
1082
|
+
]
|
|
1083
|
+
},
|
|
1084
|
+
{
|
|
1085
|
+
// Better-auth: `organization/remove-team { teamId, organizationId? }`.
|
|
1086
|
+
// organizationId defaults to the caller's active org when omitted.
|
|
1087
|
+
name: "remove_team",
|
|
1088
|
+
label: "Delete Team",
|
|
1089
|
+
icon: "trash-2",
|
|
1090
|
+
variant: "danger",
|
|
1091
|
+
mode: "delete",
|
|
1092
|
+
locations: ["list_item"],
|
|
1093
|
+
type: "api",
|
|
1094
|
+
target: "/api/v1/auth/organization/remove-team",
|
|
1095
|
+
recordIdParam: "teamId",
|
|
1096
|
+
confirmText: "Delete this team? Members will lose any team-scoped access. This cannot be undone.",
|
|
1097
|
+
successMessage: "Team deleted",
|
|
1098
|
+
refreshAfter: true
|
|
1099
|
+
}
|
|
1100
|
+
],
|
|
1101
|
+
listViews: {
|
|
1102
|
+
by_org: {
|
|
1103
|
+
type: "grid",
|
|
1104
|
+
name: "by_org",
|
|
1105
|
+
label: "By Organization",
|
|
1106
|
+
data: { provider: "object", object: "sys_team" },
|
|
1107
|
+
columns: ["organization_id", "name", "created_at", "updated_at"],
|
|
1108
|
+
sort: [{ field: "organization_id", order: "asc" }, { field: "name", order: "asc" }],
|
|
1109
|
+
grouping: { fields: [{ field: "organization_id", order: "asc", collapsed: false }] },
|
|
1110
|
+
pagination: { pageSize: 100 }
|
|
1111
|
+
},
|
|
1112
|
+
all_teams: {
|
|
1113
|
+
type: "grid",
|
|
1114
|
+
name: "all_teams",
|
|
1115
|
+
label: "All",
|
|
1116
|
+
data: { provider: "object", object: "sys_team" },
|
|
1117
|
+
columns: ["name", "organization_id", "created_at", "updated_at"],
|
|
1118
|
+
sort: [{ field: "name", order: "asc" }],
|
|
1119
|
+
pagination: { pageSize: 50 }
|
|
1120
|
+
}
|
|
1121
|
+
},
|
|
525
1122
|
fields: {
|
|
526
1123
|
// ── Identity ─────────────────────────────────────────────────
|
|
527
1124
|
name: Field.text({
|
|
@@ -580,6 +1177,48 @@ var SysTeamMember = ObjectSchema.create({
|
|
|
580
1177
|
description: "Team membership records linking users to teams",
|
|
581
1178
|
titleFormat: "{user_id} in {team_id}",
|
|
582
1179
|
compactLayout: ["user_id", "team_id", "created_at"],
|
|
1180
|
+
// Custom actions calling better-auth's team-member endpoints. Generic
|
|
1181
|
+
// CRUD is suppressed (managedBy: 'better-auth') so these are the
|
|
1182
|
+
// canonical add/remove entry points.
|
|
1183
|
+
actions: [
|
|
1184
|
+
{
|
|
1185
|
+
// Better-auth: `organization/add-team-member { teamId, userId }`.
|
|
1186
|
+
name: "add_team_member",
|
|
1187
|
+
label: "Add Member",
|
|
1188
|
+
icon: "user-plus",
|
|
1189
|
+
variant: "primary",
|
|
1190
|
+
locations: ["list_toolbar"],
|
|
1191
|
+
type: "api",
|
|
1192
|
+
target: "/api/v1/auth/organization/add-team-member",
|
|
1193
|
+
successMessage: "Team member added",
|
|
1194
|
+
refreshAfter: true,
|
|
1195
|
+
params: [
|
|
1196
|
+
{ name: "teamId", field: "team_id", required: true },
|
|
1197
|
+
{ name: "userId", field: "user_id", required: true }
|
|
1198
|
+
]
|
|
1199
|
+
},
|
|
1200
|
+
{
|
|
1201
|
+
// Better-auth: `organization/remove-team-member { teamId, userId }`.
|
|
1202
|
+
// The endpoint identifies the membership by the (teamId, userId)
|
|
1203
|
+
// pair rather than the join-row id, so we pull both from the row
|
|
1204
|
+
// via `defaultFromRow` instead of using `recordIdParam`.
|
|
1205
|
+
name: "remove_team_member",
|
|
1206
|
+
label: "Remove from Team",
|
|
1207
|
+
icon: "user-minus",
|
|
1208
|
+
variant: "danger",
|
|
1209
|
+
mode: "delete",
|
|
1210
|
+
locations: ["list_item"],
|
|
1211
|
+
type: "api",
|
|
1212
|
+
target: "/api/v1/auth/organization/remove-team-member",
|
|
1213
|
+
confirmText: "Remove this user from the team? They will lose any team-scoped access.",
|
|
1214
|
+
successMessage: "Team member removed",
|
|
1215
|
+
refreshAfter: true,
|
|
1216
|
+
params: [
|
|
1217
|
+
{ name: "teamId", field: "team_id", required: true, defaultFromRow: true },
|
|
1218
|
+
{ name: "userId", field: "user_id", required: true, defaultFromRow: true }
|
|
1219
|
+
]
|
|
1220
|
+
}
|
|
1221
|
+
],
|
|
583
1222
|
fields: {
|
|
584
1223
|
id: Field.text({
|
|
585
1224
|
label: "Team Member ID",
|
|
@@ -613,6 +1252,248 @@ var SysTeamMember = ObjectSchema.create({
|
|
|
613
1252
|
mru: false
|
|
614
1253
|
}
|
|
615
1254
|
});
|
|
1255
|
+
var SysDepartment = ObjectSchema.create({
|
|
1256
|
+
name: "sys_department",
|
|
1257
|
+
label: "Department",
|
|
1258
|
+
pluralLabel: "Departments",
|
|
1259
|
+
icon: "building",
|
|
1260
|
+
isSystem: true,
|
|
1261
|
+
managedBy: "platform",
|
|
1262
|
+
description: "Hierarchical org-skeleton node (department / division / business unit / office).",
|
|
1263
|
+
displayNameField: "name",
|
|
1264
|
+
titleFormat: "{name}",
|
|
1265
|
+
compactLayout: ["name", "kind", "parent_department_id", "manager_user_id"],
|
|
1266
|
+
listViews: {
|
|
1267
|
+
active: {
|
|
1268
|
+
type: "grid",
|
|
1269
|
+
name: "active",
|
|
1270
|
+
label: "Active",
|
|
1271
|
+
data: { provider: "object", object: "sys_department" },
|
|
1272
|
+
columns: ["name", "code", "kind", "parent_department_id", "manager_user_id", "effective_from"],
|
|
1273
|
+
filter: [{ field: "active", operator: "equals", value: true }],
|
|
1274
|
+
sort: [{ field: "name", order: "asc" }],
|
|
1275
|
+
pagination: { pageSize: 100 }
|
|
1276
|
+
},
|
|
1277
|
+
inactive: {
|
|
1278
|
+
type: "grid",
|
|
1279
|
+
name: "inactive",
|
|
1280
|
+
label: "Inactive",
|
|
1281
|
+
data: { provider: "object", object: "sys_department" },
|
|
1282
|
+
columns: ["name", "code", "kind", "effective_to"],
|
|
1283
|
+
filter: [{ field: "active", operator: "equals", value: false }],
|
|
1284
|
+
sort: [{ field: "effective_to", order: "desc" }],
|
|
1285
|
+
pagination: { pageSize: 50 }
|
|
1286
|
+
},
|
|
1287
|
+
by_kind: {
|
|
1288
|
+
type: "grid",
|
|
1289
|
+
name: "by_kind",
|
|
1290
|
+
label: "By Kind",
|
|
1291
|
+
data: { provider: "object", object: "sys_department" },
|
|
1292
|
+
columns: ["kind", "name", "code", "parent_department_id", "manager_user_id", "active"],
|
|
1293
|
+
sort: [{ field: "kind", order: "asc" }, { field: "name", order: "asc" }],
|
|
1294
|
+
grouping: { fields: [{ field: "kind", order: "asc", collapsed: false }] },
|
|
1295
|
+
pagination: { pageSize: 100 }
|
|
1296
|
+
},
|
|
1297
|
+
all_departments: {
|
|
1298
|
+
type: "grid",
|
|
1299
|
+
name: "all_departments",
|
|
1300
|
+
label: "All",
|
|
1301
|
+
data: { provider: "object", object: "sys_department" },
|
|
1302
|
+
columns: ["name", "code", "kind", "parent_department_id", "manager_user_id", "active"],
|
|
1303
|
+
sort: [{ field: "name", order: "asc" }],
|
|
1304
|
+
pagination: { pageSize: 100 }
|
|
1305
|
+
}
|
|
1306
|
+
},
|
|
1307
|
+
fields: {
|
|
1308
|
+
// ── Identity ─────────────────────────────────────────────────
|
|
1309
|
+
name: Field.text({
|
|
1310
|
+
label: "Name",
|
|
1311
|
+
required: true,
|
|
1312
|
+
searchable: true,
|
|
1313
|
+
maxLength: 255,
|
|
1314
|
+
group: "Identity"
|
|
1315
|
+
}),
|
|
1316
|
+
code: Field.text({
|
|
1317
|
+
label: "Code",
|
|
1318
|
+
required: false,
|
|
1319
|
+
searchable: true,
|
|
1320
|
+
maxLength: 64,
|
|
1321
|
+
description: "Short stable code (e.g. EMEA-SALES). Unique within tenant.",
|
|
1322
|
+
group: "Identity"
|
|
1323
|
+
}),
|
|
1324
|
+
kind: Field.select(
|
|
1325
|
+
["company", "division", "department", "team", "office", "cost_center"],
|
|
1326
|
+
{
|
|
1327
|
+
label: "Kind",
|
|
1328
|
+
required: true,
|
|
1329
|
+
defaultValue: "department",
|
|
1330
|
+
description: "Categorisation hint \u2014 does not change graph semantics.",
|
|
1331
|
+
group: "Identity"
|
|
1332
|
+
}
|
|
1333
|
+
),
|
|
1334
|
+
// ── Hierarchy ────────────────────────────────────────────────
|
|
1335
|
+
parent_department_id: Field.lookup("sys_department", {
|
|
1336
|
+
label: "Parent Department",
|
|
1337
|
+
required: false,
|
|
1338
|
+
description: "Self-reference for the org tree. Null = root of tenant.",
|
|
1339
|
+
group: "Hierarchy"
|
|
1340
|
+
}),
|
|
1341
|
+
organization_id: Field.lookup("sys_organization", {
|
|
1342
|
+
label: "Organization",
|
|
1343
|
+
required: true,
|
|
1344
|
+
description: "Tenant scope.",
|
|
1345
|
+
group: "Hierarchy"
|
|
1346
|
+
}),
|
|
1347
|
+
// ── Leadership ───────────────────────────────────────────────
|
|
1348
|
+
manager_user_id: Field.lookup("sys_user", {
|
|
1349
|
+
label: "Department Head",
|
|
1350
|
+
required: false,
|
|
1351
|
+
description: "User responsible for this org unit (department head / lead).",
|
|
1352
|
+
group: "Leadership"
|
|
1353
|
+
}),
|
|
1354
|
+
// ── Lifecycle ────────────────────────────────────────────────
|
|
1355
|
+
active: Field.boolean({
|
|
1356
|
+
label: "Active",
|
|
1357
|
+
required: false,
|
|
1358
|
+
defaultValue: true,
|
|
1359
|
+
description: "When false, members are not expanded by graph queries.",
|
|
1360
|
+
group: "Lifecycle"
|
|
1361
|
+
}),
|
|
1362
|
+
effective_from: Field.datetime({
|
|
1363
|
+
label: "Effective From",
|
|
1364
|
+
required: false,
|
|
1365
|
+
description: "When this department came into existence (HRIS sync).",
|
|
1366
|
+
group: "Lifecycle"
|
|
1367
|
+
}),
|
|
1368
|
+
effective_to: Field.datetime({
|
|
1369
|
+
label: "Effective To",
|
|
1370
|
+
required: false,
|
|
1371
|
+
description: "When this department was retired (HRIS sync).",
|
|
1372
|
+
group: "Lifecycle"
|
|
1373
|
+
}),
|
|
1374
|
+
external_ref: Field.text({
|
|
1375
|
+
label: "External Reference",
|
|
1376
|
+
required: false,
|
|
1377
|
+
maxLength: 200,
|
|
1378
|
+
description: "ID in upstream HRIS (Workday / SAP HR / \u5317\u68EE).",
|
|
1379
|
+
group: "Lifecycle"
|
|
1380
|
+
}),
|
|
1381
|
+
// ── System ───────────────────────────────────────────────────
|
|
1382
|
+
id: Field.text({
|
|
1383
|
+
label: "Department ID",
|
|
1384
|
+
required: true,
|
|
1385
|
+
readonly: true,
|
|
1386
|
+
group: "System"
|
|
1387
|
+
}),
|
|
1388
|
+
created_at: Field.datetime({
|
|
1389
|
+
label: "Created At",
|
|
1390
|
+
defaultValue: "NOW()",
|
|
1391
|
+
readonly: true,
|
|
1392
|
+
group: "System"
|
|
1393
|
+
}),
|
|
1394
|
+
updated_at: Field.datetime({
|
|
1395
|
+
label: "Updated At",
|
|
1396
|
+
defaultValue: "NOW()",
|
|
1397
|
+
readonly: true,
|
|
1398
|
+
group: "System"
|
|
1399
|
+
})
|
|
1400
|
+
},
|
|
1401
|
+
indexes: [
|
|
1402
|
+
{ fields: ["organization_id"] },
|
|
1403
|
+
{ fields: ["parent_department_id"] },
|
|
1404
|
+
{ fields: ["code", "organization_id"], unique: true },
|
|
1405
|
+
{ fields: ["active"] }
|
|
1406
|
+
],
|
|
1407
|
+
enable: {
|
|
1408
|
+
trackHistory: true,
|
|
1409
|
+
searchable: true,
|
|
1410
|
+
apiEnabled: true,
|
|
1411
|
+
apiMethods: ["get", "list", "create", "update", "delete"],
|
|
1412
|
+
trash: true,
|
|
1413
|
+
mru: false
|
|
1414
|
+
}
|
|
1415
|
+
});
|
|
1416
|
+
var SysDepartmentMember = ObjectSchema.create({
|
|
1417
|
+
name: "sys_department_member",
|
|
1418
|
+
label: "Department Member",
|
|
1419
|
+
pluralLabel: "Department Members",
|
|
1420
|
+
icon: "user-cog",
|
|
1421
|
+
isSystem: true,
|
|
1422
|
+
managedBy: "platform",
|
|
1423
|
+
description: "User assignment to a department (matrix-org friendly, effective-dated).",
|
|
1424
|
+
titleFormat: "{user_id} in {department_id}",
|
|
1425
|
+
compactLayout: ["user_id", "department_id", "role_in_department", "is_primary"],
|
|
1426
|
+
fields: {
|
|
1427
|
+
id: Field.text({
|
|
1428
|
+
label: "Member ID",
|
|
1429
|
+
required: true,
|
|
1430
|
+
readonly: true,
|
|
1431
|
+
group: "System"
|
|
1432
|
+
}),
|
|
1433
|
+
department_id: Field.lookup("sys_department", {
|
|
1434
|
+
label: "Department",
|
|
1435
|
+
required: true,
|
|
1436
|
+
group: "Assignment"
|
|
1437
|
+
}),
|
|
1438
|
+
user_id: Field.lookup("sys_user", {
|
|
1439
|
+
label: "User",
|
|
1440
|
+
required: true,
|
|
1441
|
+
group: "Assignment"
|
|
1442
|
+
}),
|
|
1443
|
+
role_in_department: Field.select(
|
|
1444
|
+
["member", "lead", "deputy"],
|
|
1445
|
+
{
|
|
1446
|
+
label: "Role in Department",
|
|
1447
|
+
required: false,
|
|
1448
|
+
defaultValue: "member",
|
|
1449
|
+
description: "`lead` is the day-to-day head; `deputy` may stand in for the lead in approval routing.",
|
|
1450
|
+
group: "Assignment"
|
|
1451
|
+
}
|
|
1452
|
+
),
|
|
1453
|
+
is_primary: Field.boolean({
|
|
1454
|
+
label: "Primary Assignment",
|
|
1455
|
+
required: false,
|
|
1456
|
+
defaultValue: true,
|
|
1457
|
+
description: "When the user is in multiple departments, this marks the canonical one for reporting.",
|
|
1458
|
+
group: "Assignment"
|
|
1459
|
+
}),
|
|
1460
|
+
effective_from: Field.datetime({
|
|
1461
|
+
label: "Effective From",
|
|
1462
|
+
required: false,
|
|
1463
|
+
group: "Lifecycle"
|
|
1464
|
+
}),
|
|
1465
|
+
effective_to: Field.datetime({
|
|
1466
|
+
label: "Effective To",
|
|
1467
|
+
required: false,
|
|
1468
|
+
group: "Lifecycle"
|
|
1469
|
+
}),
|
|
1470
|
+
created_at: Field.datetime({
|
|
1471
|
+
label: "Created At",
|
|
1472
|
+
defaultValue: "NOW()",
|
|
1473
|
+
readonly: true,
|
|
1474
|
+
group: "System"
|
|
1475
|
+
}),
|
|
1476
|
+
updated_at: Field.datetime({
|
|
1477
|
+
label: "Updated At",
|
|
1478
|
+
defaultValue: "NOW()",
|
|
1479
|
+
readonly: true,
|
|
1480
|
+
group: "System"
|
|
1481
|
+
})
|
|
1482
|
+
},
|
|
1483
|
+
indexes: [
|
|
1484
|
+
{ fields: ["department_id", "user_id"], unique: true },
|
|
1485
|
+
{ fields: ["user_id"] },
|
|
1486
|
+
{ fields: ["is_primary"] }
|
|
1487
|
+
],
|
|
1488
|
+
enable: {
|
|
1489
|
+
trackHistory: true,
|
|
1490
|
+
searchable: true,
|
|
1491
|
+
apiEnabled: true,
|
|
1492
|
+
apiMethods: ["get", "list", "create", "update", "delete"],
|
|
1493
|
+
trash: true,
|
|
1494
|
+
mru: false
|
|
1495
|
+
}
|
|
1496
|
+
});
|
|
616
1497
|
var SysApiKey = ObjectSchema.create({
|
|
617
1498
|
name: "sys_api_key",
|
|
618
1499
|
label: "API Key",
|
|
@@ -624,6 +1505,85 @@ var SysApiKey = ObjectSchema.create({
|
|
|
624
1505
|
displayNameField: "name",
|
|
625
1506
|
titleFormat: "{name}",
|
|
626
1507
|
compactLayout: ["name", "prefix", "user_id", "expires_at", "revoked"],
|
|
1508
|
+
// Custom actions — sys_api_key is managed-by 'better-auth' but the
|
|
1509
|
+
// `revoked` boolean is a column we control via the data API. These row
|
|
1510
|
+
// actions use the generic PATCH /api/v1/sys_api_key/{id} endpoint with
|
|
1511
|
+
// `bodyExtra` to set the `revoked` flag explicitly.
|
|
1512
|
+
actions: [
|
|
1513
|
+
{
|
|
1514
|
+
name: "revoke_api_key",
|
|
1515
|
+
label: "Revoke API Key",
|
|
1516
|
+
icon: "shield-off",
|
|
1517
|
+
variant: "danger",
|
|
1518
|
+
mode: "custom",
|
|
1519
|
+
locations: ["list_item"],
|
|
1520
|
+
type: "api",
|
|
1521
|
+
method: "PATCH",
|
|
1522
|
+
target: "/api/v1/data/sys_api_key/{id}",
|
|
1523
|
+
bodyExtra: { revoked: true },
|
|
1524
|
+
confirmText: "Revoke this API key? Any clients using it will immediately lose access.",
|
|
1525
|
+
successMessage: "API key revoked",
|
|
1526
|
+
refreshAfter: true
|
|
1527
|
+
},
|
|
1528
|
+
{
|
|
1529
|
+
name: "restore_api_key",
|
|
1530
|
+
label: "Restore API Key",
|
|
1531
|
+
icon: "shield-check",
|
|
1532
|
+
variant: "secondary",
|
|
1533
|
+
mode: "custom",
|
|
1534
|
+
locations: ["list_item"],
|
|
1535
|
+
type: "api",
|
|
1536
|
+
method: "PATCH",
|
|
1537
|
+
target: "/api/v1/data/sys_api_key/{id}",
|
|
1538
|
+
bodyExtra: { revoked: false },
|
|
1539
|
+
confirmText: "Restore this revoked API key? Existing clients holding the key will regain access.",
|
|
1540
|
+
successMessage: "API key restored",
|
|
1541
|
+
refreshAfter: true
|
|
1542
|
+
}
|
|
1543
|
+
],
|
|
1544
|
+
listViews: {
|
|
1545
|
+
mine: {
|
|
1546
|
+
type: "grid",
|
|
1547
|
+
name: "mine",
|
|
1548
|
+
label: "My Keys",
|
|
1549
|
+
data: { provider: "object", object: "sys_api_key" },
|
|
1550
|
+
columns: ["name", "prefix", "expires_at", "last_used_at", "revoked"],
|
|
1551
|
+
filter: [
|
|
1552
|
+
{ field: "user_id", operator: "equals", value: "{current_user_id}" }
|
|
1553
|
+
],
|
|
1554
|
+
sort: [{ field: "created_at", order: "desc" }],
|
|
1555
|
+
pagination: { pageSize: 50 }
|
|
1556
|
+
},
|
|
1557
|
+
active: {
|
|
1558
|
+
type: "grid",
|
|
1559
|
+
name: "active",
|
|
1560
|
+
label: "Active",
|
|
1561
|
+
data: { provider: "object", object: "sys_api_key" },
|
|
1562
|
+
columns: ["name", "prefix", "user_id", "expires_at", "last_used_at"],
|
|
1563
|
+
filter: [{ field: "revoked", operator: "equals", value: false }],
|
|
1564
|
+
sort: [{ field: "last_used_at", order: "desc" }],
|
|
1565
|
+
pagination: { pageSize: 50 }
|
|
1566
|
+
},
|
|
1567
|
+
revoked: {
|
|
1568
|
+
type: "grid",
|
|
1569
|
+
name: "revoked",
|
|
1570
|
+
label: "Revoked",
|
|
1571
|
+
data: { provider: "object", object: "sys_api_key" },
|
|
1572
|
+
columns: ["name", "prefix", "user_id", "expires_at", "updated_at"],
|
|
1573
|
+
filter: [{ field: "revoked", operator: "equals", value: true }],
|
|
1574
|
+
sort: [{ field: "updated_at", order: "desc" }],
|
|
1575
|
+
pagination: { pageSize: 50 }
|
|
1576
|
+
},
|
|
1577
|
+
all_keys: {
|
|
1578
|
+
type: "grid",
|
|
1579
|
+
name: "all_keys",
|
|
1580
|
+
label: "All",
|
|
1581
|
+
data: { provider: "object", object: "sys_api_key" },
|
|
1582
|
+
columns: ["name", "prefix", "user_id", "expires_at", "last_used_at", "revoked"],
|
|
1583
|
+
sort: [{ field: "created_at", order: "desc" }],
|
|
1584
|
+
pagination: { pageSize: 50 }
|
|
1585
|
+
}
|
|
1586
|
+
},
|
|
627
1587
|
fields: {
|
|
628
1588
|
// ── Identity ─────────────────────────────────────────────────
|
|
629
1589
|
name: Field.text({
|
|
@@ -726,6 +1686,62 @@ var SysTwoFactor = ObjectSchema.create({
|
|
|
726
1686
|
description: "Two-factor authentication credentials",
|
|
727
1687
|
titleFormat: "Two-factor for {user_id}",
|
|
728
1688
|
compactLayout: ["user_id", "created_at"],
|
|
1689
|
+
listViews: {
|
|
1690
|
+
mine: {
|
|
1691
|
+
type: "grid",
|
|
1692
|
+
name: "mine",
|
|
1693
|
+
label: "My Enrollment",
|
|
1694
|
+
data: { provider: "object", object: "sys_two_factor" },
|
|
1695
|
+
columns: ["created_at", "updated_at"],
|
|
1696
|
+
filter: [{ field: "user_id", operator: "equals", value: "{current_user_id}" }],
|
|
1697
|
+
sort: [{ field: "created_at", order: "desc" }],
|
|
1698
|
+
pagination: { pageSize: 50 }
|
|
1699
|
+
},
|
|
1700
|
+
all_enrollments: {
|
|
1701
|
+
type: "grid",
|
|
1702
|
+
name: "all_enrollments",
|
|
1703
|
+
label: "All",
|
|
1704
|
+
data: { provider: "object", object: "sys_two_factor" },
|
|
1705
|
+
columns: ["user_id", "created_at", "updated_at"],
|
|
1706
|
+
sort: [{ field: "created_at", order: "desc" }],
|
|
1707
|
+
pagination: { pageSize: 50 }
|
|
1708
|
+
}
|
|
1709
|
+
},
|
|
1710
|
+
// Toolbar actions for self-service 2FA enrollment. The actual TOTP secret
|
|
1711
|
+
// and backup codes returned by better-auth must be shown in the response
|
|
1712
|
+
// toast / dialog — the action runner surfaces successMessage; the raw
|
|
1713
|
+
// payload is logged client-side for now (TODO: dedicated 2FA setup wizard).
|
|
1714
|
+
actions: [
|
|
1715
|
+
{
|
|
1716
|
+
name: "enable_two_factor",
|
|
1717
|
+
label: "Enable 2FA",
|
|
1718
|
+
icon: "shield-check",
|
|
1719
|
+
variant: "primary",
|
|
1720
|
+
locations: ["list_toolbar"],
|
|
1721
|
+
type: "api",
|
|
1722
|
+
target: "/api/v1/auth/two-factor/enable",
|
|
1723
|
+
successMessage: "2FA enrollment started \u2014 check response for TOTP URI and backup codes",
|
|
1724
|
+
refreshAfter: true,
|
|
1725
|
+
params: [
|
|
1726
|
+
{ name: "password", label: "Current Password", type: "text", required: true }
|
|
1727
|
+
]
|
|
1728
|
+
},
|
|
1729
|
+
{
|
|
1730
|
+
name: "disable_two_factor",
|
|
1731
|
+
label: "Disable 2FA",
|
|
1732
|
+
icon: "shield-off",
|
|
1733
|
+
variant: "danger",
|
|
1734
|
+
locations: ["list_toolbar"],
|
|
1735
|
+
type: "api",
|
|
1736
|
+
target: "/api/v1/auth/two-factor/disable",
|
|
1737
|
+
confirmText: "Disable two-factor authentication on your account?",
|
|
1738
|
+
successMessage: "2FA disabled",
|
|
1739
|
+
refreshAfter: true,
|
|
1740
|
+
params: [
|
|
1741
|
+
{ name: "password", label: "Current Password", type: "text", required: true }
|
|
1742
|
+
]
|
|
1743
|
+
}
|
|
1744
|
+
],
|
|
729
1745
|
fields: {
|
|
730
1746
|
id: Field.text({
|
|
731
1747
|
label: "Two Factor ID",
|
|
@@ -865,9 +1881,44 @@ var SysUserPreference = ObjectSchema.create({
|
|
|
865
1881
|
pluralLabel: "User Preferences",
|
|
866
1882
|
icon: "settings",
|
|
867
1883
|
isSystem: true,
|
|
1884
|
+
// managedBy: 'system' — preferences are per-user state authored from
|
|
1885
|
+
// the user's own settings page, never created by an admin. The list
|
|
1886
|
+
// surface in Setup is a support/diagnostic view only.
|
|
1887
|
+
managedBy: "system",
|
|
868
1888
|
description: "Per-user key-value preferences (theme, locale, etc.)",
|
|
869
1889
|
titleFormat: "{key}",
|
|
870
1890
|
compactLayout: ["user_id", "key"],
|
|
1891
|
+
listViews: {
|
|
1892
|
+
mine: {
|
|
1893
|
+
type: "grid",
|
|
1894
|
+
name: "mine",
|
|
1895
|
+
label: "My Preferences",
|
|
1896
|
+
data: { provider: "object", object: "sys_user_preference" },
|
|
1897
|
+
columns: ["key", "updated_at"],
|
|
1898
|
+
filter: [{ field: "user_id", operator: "equals", value: "{current_user_id}" }],
|
|
1899
|
+
sort: [{ field: "key", order: "asc" }],
|
|
1900
|
+
pagination: { pageSize: 100 }
|
|
1901
|
+
},
|
|
1902
|
+
by_user: {
|
|
1903
|
+
type: "grid",
|
|
1904
|
+
name: "by_user",
|
|
1905
|
+
label: "By User",
|
|
1906
|
+
data: { provider: "object", object: "sys_user_preference" },
|
|
1907
|
+
columns: ["user_id", "key", "updated_at"],
|
|
1908
|
+
sort: [{ field: "user_id", order: "asc" }, { field: "key", order: "asc" }],
|
|
1909
|
+
grouping: { fields: [{ field: "user_id", order: "asc", collapsed: true }] },
|
|
1910
|
+
pagination: { pageSize: 200 }
|
|
1911
|
+
},
|
|
1912
|
+
all_preferences: {
|
|
1913
|
+
type: "grid",
|
|
1914
|
+
name: "all_preferences",
|
|
1915
|
+
label: "All",
|
|
1916
|
+
data: { provider: "object", object: "sys_user_preference" },
|
|
1917
|
+
columns: ["user_id", "key", "created_at", "updated_at"],
|
|
1918
|
+
sort: [{ field: "updated_at", order: "desc" }],
|
|
1919
|
+
pagination: { pageSize: 100 }
|
|
1920
|
+
}
|
|
1921
|
+
},
|
|
871
1922
|
fields: {
|
|
872
1923
|
id: Field.text({
|
|
873
1924
|
label: "Preference ID",
|
|
@@ -924,6 +1975,37 @@ var SysOauthApplication = ObjectSchema.create({
|
|
|
924
1975
|
displayNameField: "name",
|
|
925
1976
|
titleFormat: "{name}",
|
|
926
1977
|
compactLayout: ["name", "client_id", "type", "disabled"],
|
|
1978
|
+
listViews: {
|
|
1979
|
+
active: {
|
|
1980
|
+
type: "grid",
|
|
1981
|
+
name: "active",
|
|
1982
|
+
label: "Active",
|
|
1983
|
+
data: { provider: "object", object: "sys_oauth_application" },
|
|
1984
|
+
columns: ["name", "client_id", "type", "updated_at"],
|
|
1985
|
+
filter: [{ field: "disabled", operator: "equals", value: false }],
|
|
1986
|
+
sort: [{ field: "name", order: "asc" }],
|
|
1987
|
+
pagination: { pageSize: 50 }
|
|
1988
|
+
},
|
|
1989
|
+
disabled_apps: {
|
|
1990
|
+
type: "grid",
|
|
1991
|
+
name: "disabled_apps",
|
|
1992
|
+
label: "Disabled",
|
|
1993
|
+
data: { provider: "object", object: "sys_oauth_application" },
|
|
1994
|
+
columns: ["name", "client_id", "type", "updated_at"],
|
|
1995
|
+
filter: [{ field: "disabled", operator: "equals", value: true }],
|
|
1996
|
+
sort: [{ field: "updated_at", order: "desc" }],
|
|
1997
|
+
pagination: { pageSize: 50 }
|
|
1998
|
+
},
|
|
1999
|
+
all_apps: {
|
|
2000
|
+
type: "grid",
|
|
2001
|
+
name: "all_apps",
|
|
2002
|
+
label: "All",
|
|
2003
|
+
data: { provider: "object", object: "sys_oauth_application" },
|
|
2004
|
+
columns: ["name", "client_id", "type", "disabled", "created_at"],
|
|
2005
|
+
sort: [{ field: "name", order: "asc" }],
|
|
2006
|
+
pagination: { pageSize: 50 }
|
|
2007
|
+
}
|
|
2008
|
+
},
|
|
927
2009
|
fields: {
|
|
928
2010
|
// ── Identity ─────────────────────────────────────────────────
|
|
929
2011
|
id: Field.text({
|
|
@@ -1397,6 +2479,6 @@ var SysJwks = ObjectSchema.create({
|
|
|
1397
2479
|
}
|
|
1398
2480
|
});
|
|
1399
2481
|
|
|
1400
|
-
export { SysAccount, SysApiKey, SysDeviceCode, SysInvitation, SysJwks, SysMember, SysOauthAccessToken, SysOauthApplication, SysOauthConsent, SysOauthRefreshToken, SysOrganization, SysSession, SysTeam, SysTeamMember, SysTwoFactor, SysUser, SysUserPreference, SysVerification };
|
|
2482
|
+
export { SysAccount, SysApiKey, SysDepartment, SysDepartmentMember, SysDeviceCode, SysInvitation, SysJwks, SysMember, SysOauthAccessToken, SysOauthApplication, SysOauthConsent, SysOauthRefreshToken, SysOrganization, SysSession, SysTeam, SysTeamMember, SysTwoFactor, SysUser, SysUserPreference, SysVerification };
|
|
1401
2483
|
//# sourceMappingURL=index.mjs.map
|
|
1402
2484
|
//# sourceMappingURL=index.mjs.map
|