includio-cms 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/ROADMAP.md +17 -11
  3. package/dist/admin/api/accept-invite.d.ts +2 -0
  4. package/dist/admin/api/accept-invite.js +30 -0
  5. package/dist/admin/api/handler.d.ts +13 -0
  6. package/dist/admin/api/handler.js +36 -0
  7. package/dist/admin/api/invite.d.ts +5 -0
  8. package/dist/admin/api/invite.js +78 -0
  9. package/dist/admin/auth-client.d.ts +1165 -5
  10. package/dist/admin/auth-client.js +4 -1
  11. package/dist/admin/client/account/sessions-section.svelte +1 -21
  12. package/dist/admin/client/collection/collection-page.svelte +5 -7
  13. package/dist/admin/client/collection/collection-page.svelte.d.ts +2 -2
  14. package/dist/admin/client/entry/entry-page.svelte +5 -7
  15. package/dist/admin/client/entry/entry-page.svelte.d.ts +2 -2
  16. package/dist/admin/client/index.d.ts +2 -0
  17. package/dist/admin/client/index.js +2 -0
  18. package/dist/admin/client/users/accept-invite-page.svelte +118 -0
  19. package/dist/admin/client/users/accept-invite-page.svelte.d.ts +4 -0
  20. package/dist/admin/client/users/create-user-dialog.svelte +157 -0
  21. package/dist/admin/client/users/create-user-dialog.svelte.d.ts +8 -0
  22. package/dist/admin/client/users/delete-user-dialog.svelte +53 -0
  23. package/dist/admin/client/users/delete-user-dialog.svelte.d.ts +10 -0
  24. package/dist/admin/client/users/edit-user-dialog.svelte +127 -0
  25. package/dist/admin/client/users/edit-user-dialog.svelte.d.ts +16 -0
  26. package/dist/admin/client/users/invite-user-dialog.svelte +107 -0
  27. package/dist/admin/client/users/invite-user-dialog.svelte.d.ts +8 -0
  28. package/dist/admin/client/users/lang.d.ts +57 -0
  29. package/dist/admin/client/users/lang.js +114 -0
  30. package/dist/admin/client/users/pending-invitations.svelte +145 -0
  31. package/dist/admin/client/users/pending-invitations.svelte.d.ts +6 -0
  32. package/dist/admin/client/users/user-sessions-sheet.svelte +141 -0
  33. package/dist/admin/client/users/user-sessions-sheet.svelte.d.ts +8 -0
  34. package/dist/admin/client/users/users-page.svelte +262 -0
  35. package/dist/admin/client/users/users-page.svelte.d.ts +6 -0
  36. package/dist/admin/components/layout/lang.d.ts +1 -0
  37. package/dist/admin/components/layout/lang.js +4 -2
  38. package/dist/admin/components/layout/nav-main.svelte +15 -1
  39. package/dist/admin/remote/invite.d.ts +44 -0
  40. package/dist/admin/remote/invite.js +44 -0
  41. package/dist/admin/remote/middleware/auth.d.ts +5 -0
  42. package/dist/admin/remote/middleware/auth.js +7 -0
  43. package/dist/admin/utils/parseUserAgent.d.ts +5 -0
  44. package/dist/admin/utils/parseUserAgent.js +26 -0
  45. package/dist/cli/index.d.ts +2 -0
  46. package/dist/cli/index.js +28 -0
  47. package/dist/cli/scaffold/admin.d.ts +4 -0
  48. package/dist/cli/scaffold/admin.js +185 -0
  49. package/dist/cms/runtime/types.d.ts +0 -7
  50. package/dist/components/ui/accordion/accordion.svelte.d.ts +1 -1
  51. package/dist/components/ui/calendar/calendar.svelte.d.ts +1 -1
  52. package/dist/components/ui/command/command-dialog.svelte.d.ts +1 -1
  53. package/dist/components/ui/command/command-input.svelte.d.ts +1 -1
  54. package/dist/components/ui/command/command.svelte.d.ts +1 -1
  55. package/dist/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte.d.ts +1 -1
  56. package/dist/components/ui/input/input.svelte.d.ts +1 -1
  57. package/dist/components/ui/input-group/input-group-input.svelte.d.ts +2 -2
  58. package/dist/components/ui/input-group/input-group-textarea.svelte.d.ts +1 -1
  59. package/dist/components/ui/radio-group/radio-group.svelte.d.ts +1 -1
  60. package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +2 -2
  61. package/dist/components/ui/tabs/tabs.svelte.d.ts +1 -1
  62. package/dist/components/ui/textarea/textarea.svelte.d.ts +1 -1
  63. package/dist/components/ui/toggle-group/toggle-group-item.svelte.d.ts +1 -1
  64. package/dist/components/ui/toggle-group/toggle-group.svelte.d.ts +1 -1
  65. package/dist/core/cms.d.ts +3 -1
  66. package/dist/core/cms.js +3 -1
  67. package/dist/core/server/forms/submissions/operations/create.js +1 -1
  68. package/dist/email-nodemailer/index.d.ts +1 -0
  69. package/dist/server/auth.d.ts +8 -8
  70. package/dist/server/db/schema/auth-schema.d.ts +143 -0
  71. package/dist/server/db/schema/auth-schema.js +12 -0
  72. package/dist/sveltekit/server/handle.js +13 -0
  73. package/dist/types/cms.d.ts +20 -2
  74. package/dist/types/roles.d.ts +1 -0
  75. package/dist/types/roles.js +1 -0
  76. package/dist/updates/0.1.2/index.d.ts +2 -0
  77. package/dist/updates/0.1.2/index.js +36 -0
  78. package/dist/updates/0.1.3/index.d.ts +2 -0
  79. package/dist/updates/0.1.3/index.js +16 -0
  80. package/dist/updates/index.js +3 -1
  81. package/package.json +11 -3
package/CHANGELOG.md CHANGED
@@ -3,6 +3,57 @@
3
3
  All notable changes to includio-cms are documented here.
4
4
  Generated from `src/lib/updates/` — do not edit manually.
5
5
 
6
+ ## 0.1.3 — 2026-02-18
7
+
8
+ Admin DX — route scaffolding & boilerplate reduction
9
+
10
+ ### Added
11
+ - CLI: `includio scaffold admin` generates all admin route files (14 files, 0 page.server.ts)
12
+ - CollectionPage reads page.params.collection directly (no +page.server.ts needed)
13
+ - EntryPage reads page.params.entryId directly (no +page.server.ts needed)
14
+ - Catch-all API handler: createAdminApiHandler() routes upload/orphaned/replace/invite/accept-invite
15
+ - accept-invite handler factory: createAcceptInviteHandler(auth) for project-specific auth injection
16
+ - AcceptInvitePage exported from includio-cms/admin/client
17
+ - AccountPage exported via includio-cms/admin/client/account
18
+
19
+ ## 0.1.2 — 2026-02-18
20
+
21
+ User management & RBAC
22
+
23
+ ### Added
24
+ - Role type system (admin/user) with UserRole type
25
+ - RBAC middleware: requireRole() for server-side role checks
26
+ - Admin users page: list, search, pagination via authClient.admin.listUsers
27
+ - Create user dialog: email, password, name, role via authClient.admin.createUser
28
+ - Edit user dialog: name, email, role with self-demotion protection
29
+ - Delete user dialog with self-deletion protection
30
+ - Route gating: /admin/users restricted to admin role
31
+ - Sidebar gating: Users nav item visible only for admins
32
+ - Auth client: adminClient() plugin added
33
+ - CLI: addUser rewritten to use better-auth API with role prompt
34
+ - Admin session management: view/revoke other users' sessions
35
+ - Email invitation system: invite users by email with role assignment
36
+ - Accept invite page: public registration via invite token
37
+ - Pending invitations list with cancel and resend support
38
+ - Email adapter now optional in CMS config
39
+
40
+ ### Migration
41
+
42
+ ```sql
43
+ CREATE TABLE IF NOT EXISTS invitation (
44
+ id TEXT PRIMARY KEY,
45
+ email TEXT NOT NULL,
46
+ role TEXT NOT NULL DEFAULT 'user',
47
+ token TEXT NOT NULL UNIQUE,
48
+ expires_at TIMESTAMP NOT NULL,
49
+ created_at TIMESTAMP NOT NULL DEFAULT NOW(),
50
+ created_by TEXT NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
51
+ used_at TIMESTAMP
52
+ );
53
+
54
+ CREATE INDEX IF NOT EXISTS invitation_token_idx ON invitation (token);
55
+ ```
56
+
6
57
  ## 0.1.1 — 2026-02-18
7
58
 
8
59
  Field constraint UI — visible limits, counters, and hints
package/ROADMAP.md CHANGED
@@ -31,20 +31,22 @@
31
31
 
32
32
  ### Phase 1 — Core
33
33
 
34
- - [ ] `[feature]` `[P0]` RBAC middleware — `requireRole()`, role check w `requireAuth()` <!-- files: src/lib/admin/remote/middleware/auth.ts -->
35
- - [ ] `[feature]` `[P0]` Admin users page — list, search, pagination via `authClient.admin.listUsers` <!-- files: src/lib/admin/client/users/ -->
36
- - [ ] `[feature]` `[P0]` Create user — dialog z email/password/name/role via `authClient.admin.createUser` <!-- files: src/lib/admin/client/users/create-user-dialog.svelte -->
37
- - [ ] `[feature]` `[P0]` Edit user — name, email, role via `adminUpdateUser` + `setRole` <!-- files: src/lib/admin/client/users/edit-user-dialog.svelte -->
38
- - [ ] `[feature]` `[P0]` Delete user — confirmation dialog via `removeUser` <!-- files: src/lib/admin/client/users/ -->
39
- - [ ] `[feature]` `[P0]` Route/sidebar gating — ukryj Users nav + chroń `/admin/users` dla non-admin <!-- files: src/lib/admin/components/layout/nav-main.svelte, src/lib/sveltekit/server/handle.ts -->
40
- - [ ] `[feature]` `[P0]` First user bootstrap — pierwszy utworzony user auto-gets role `admin` <!-- files: src/lib/server/auth.ts -->
34
+ - [x] `[feature]` `[P0]` RBAC middleware — `requireRole()`, role check w `requireAuth()` <!-- files: src/lib/admin/remote/middleware/auth.ts -->
35
+ - [x] `[feature]` `[P0]` Admin users page — list, search, pagination via `authClient.admin.listUsers` <!-- files: src/lib/admin/client/users/ -->
36
+ - [x] `[feature]` `[P0]` Create user — dialog z email/password/name/role via `authClient.admin.createUser` <!-- files: src/lib/admin/client/users/create-user-dialog.svelte -->
37
+ - [x] `[feature]` `[P0]` Edit user — name, email, role via `adminUpdateUser` + `setRole` <!-- files: src/lib/admin/client/users/edit-user-dialog.svelte -->
38
+ - [x] `[feature]` `[P0]` Delete user — confirmation dialog via `removeUser` <!-- files: src/lib/admin/client/users/ -->
39
+ - [x] `[feature]` `[P0]` Route/sidebar gating — ukryj Users nav + chroń `/admin/users` dla non-admin <!-- files: src/lib/admin/components/layout/nav-main.svelte, src/lib/sveltekit/server/handle.ts -->
40
+ - [x] `[feature]` `[P0]` First user bootstrap — pierwszy utworzony user auto-gets role `admin` <!-- files: src/lib/server/auth.ts -->
41
41
 
42
42
  ### Phase 2 — Extended
43
43
 
44
- - [ ] `[feature]` `[P1]` Ban/unban UIreason + expiry, `banUser`/`unbanUser` <!-- files: src/lib/admin/client/users/ban-user-dialog.svelte -->
45
- - [ ] `[feature]` `[P1]` Admin session mgmtlist/revoke sesji innych userów <!-- files: src/lib/admin/client/users/user-sessions-sheet.svelte -->
46
- - [ ] `[feature]` `[P1]` Impersonation UI — impersonate/stop bar <!-- files: src/lib/admin/client/users/impersonation-bar.svelte -->
47
- - [ ] `[feature]` `[P2]` Email invitation system invite link generation (custom, nie w better-auth) <!-- files: src/lib/core/server/auth/invite.ts -->
44
+ - [x] `[feature]` `[P1]` Admin session mgmt list/revoke sesji innych userów <!-- files: src/lib/admin/client/users/user-sessions-sheet.svelte -->
45
+ - [x] `[feature]` `[P2]` Email invitation systeminvite link generation (custom, nie w better-auth) <!-- files: src/lib/admin/remote/invite.ts -->
46
+
47
+ ## 0.1.3Admin DX
48
+
49
+ - [x] `[feature]` `[P1]` Admin route scaffolding — CLI `includio scaffold admin`, catch-all API handler, page.params fallbacks for CollectionPage/EntryPage <!-- files: src/lib/cli/scaffold/admin.ts, src/lib/admin/api/handler.ts, src/lib/admin/client/collection/collection-page.svelte, src/lib/admin/client/entry/entry-page.svelte -->
48
50
 
49
51
  ## 0.2.0 — Plugin system
50
52
 
@@ -57,6 +59,8 @@
57
59
  - [ ] `[feature]` `[P1]` Server-side pagination API (formalize 0.1.0 fix)
58
60
  - [ ] `[feature]` `[P1]` Improved type generation <!-- files: src/lib/core/server/generator/ -->
59
61
  - [ ] `[feature]` `[P1]` Proper filtering API (SQL-level, not JS post-query)
62
+ - [ ] `[feature]` `[breaking]` `[P1]` Simple array field — `type: 'array'` with `of: 'text' | 'number' | ...` for flat lists (tags, categories) <!-- files: src/lib/types/fields.ts, src/lib/admin/components/fields/array-field.svelte, src/lib/core/fields/fieldSchemaToTs.ts -->
63
+ - [ ] `[feature]` `[breaking]` `[P1]` Rename array → blocks — current array becomes `type: 'blocks'`, keeps `of: ObjectField[]` and accordion/DnD UI <!-- files: src/lib/types/fields.ts, src/lib/admin/components/fields/array-field.svelte, src/lib/admin/components/fields/field-renderer.svelte, src/lib/core/server/fields/resolve*.ts -->
60
64
 
61
65
  ## 0.3.0 — Admin experience
62
66
 
@@ -87,6 +91,8 @@
87
91
 
88
92
  ## Backlog
89
93
 
94
+ - [ ] `[feature]` `[P1]` Ban/unban UI — reason + expiry, `banUser`/`unbanUser` <!-- files: src/lib/admin/client/users/ban-user-dialog.svelte -->
95
+ - [ ] `[feature]` `[P1]` Impersonation UI — impersonate/stop bar <!-- files: src/lib/admin/client/users/impersonation-bar.svelte -->
90
96
  - [ ] `[feature]` `[P2]` Alternative richtext editor — Word-like mode, single richtext field instead of blocks
91
97
  - [ ] `[chore]` `[P2]` Caching/performance layer (scope TBD)
92
98
  - [ ] `[feature]` `[P2]` API/CLI for configuration (setup DX for less technical users)
@@ -0,0 +1,2 @@
1
+ import { type RequestHandler } from '@sveltejs/kit';
2
+ export declare const POST: RequestHandler;
@@ -0,0 +1,30 @@
1
+ import { getInvitationByToken, markInvitationUsed } from '../remote/invite.js';
2
+ import { getCMS } from '../../core/cms.js';
3
+ import { json } from '@sveltejs/kit';
4
+ export const POST = async ({ request }) => {
5
+ const auth = getCMS().auth;
6
+ if (!auth) {
7
+ return json({ error: 'Auth adapter not configured' }, { status: 500 });
8
+ }
9
+ const { token, name, password } = await request.json();
10
+ if (!token || !name || !password) {
11
+ return json({ error: 'Token, name and password are required' }, { status: 400 });
12
+ }
13
+ const inv = await getInvitationByToken(token);
14
+ if (!inv) {
15
+ return json({ error: 'Invalid or expired invitation' }, { status: 400 });
16
+ }
17
+ const response = await auth.api.createUser({
18
+ body: {
19
+ email: inv.email,
20
+ password,
21
+ name,
22
+ role: inv.role
23
+ }
24
+ });
25
+ if (!response?.user) {
26
+ return json({ error: 'Failed to create user' }, { status: 500 });
27
+ }
28
+ await markInvitationUsed(inv.id);
29
+ return json({ success: true });
30
+ };
@@ -0,0 +1,13 @@
1
+ import { type RequestHandler } from '@sveltejs/kit';
2
+ type Handlers = Partial<Record<'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE', RequestHandler>>;
3
+ type AdminApiOptions = {
4
+ extraRoutes?: Record<string, Handlers>;
5
+ };
6
+ export declare function createAdminApiHandler(options?: AdminApiOptions): {
7
+ GET: RequestHandler;
8
+ POST: RequestHandler;
9
+ PATCH: RequestHandler;
10
+ PUT: RequestHandler;
11
+ DELETE: RequestHandler;
12
+ };
13
+ export {};
@@ -0,0 +1,36 @@
1
+ import { json } from '@sveltejs/kit';
2
+ import * as uploadHandlers from './upload.js';
3
+ import * as orphanedHandlers from './orphaned.js';
4
+ import * as replaceHandlers from './replace.js';
5
+ import * as inviteHandlers from './invite.js';
6
+ import * as acceptInviteHandlers from './accept-invite.js';
7
+ export function createAdminApiHandler(options) {
8
+ const routes = {
9
+ upload: uploadHandlers,
10
+ orphaned: orphanedHandlers,
11
+ replace: replaceHandlers,
12
+ invite: inviteHandlers,
13
+ 'accept-invite': acceptInviteHandlers,
14
+ ...options?.extraRoutes
15
+ };
16
+ function handle(method) {
17
+ return (event) => {
18
+ const path = event.params.path;
19
+ if (!path) {
20
+ return json({ error: 'Not found' }, { status: 404 });
21
+ }
22
+ const handler = routes[path]?.[method];
23
+ if (!handler) {
24
+ return json({ error: 'Not found' }, { status: 404 });
25
+ }
26
+ return handler(event);
27
+ };
28
+ }
29
+ return {
30
+ GET: handle('GET'),
31
+ POST: handle('POST'),
32
+ PATCH: handle('PATCH'),
33
+ PUT: handle('PUT'),
34
+ DELETE: handle('DELETE')
35
+ };
36
+ }
@@ -0,0 +1,5 @@
1
+ import { type RequestHandler } from '@sveltejs/kit';
2
+ export declare const POST: RequestHandler;
3
+ export declare const GET: RequestHandler;
4
+ export declare const PATCH: RequestHandler;
5
+ export declare const DELETE: RequestHandler;
@@ -0,0 +1,78 @@
1
+ import { requireRole } from '../remote/middleware/auth.js';
2
+ import { createInvitation, getPendingInvitations, deleteInvitation, checkEmailExists, getInvitationById } from '../remote/invite.js';
3
+ import { getCMS } from '../../core/cms.js';
4
+ import { json } from '@sveltejs/kit';
5
+ export const POST = async ({ request, url }) => {
6
+ const { user } = requireRole('admin');
7
+ const { email, role } = await request.json();
8
+ if (!email || !role) {
9
+ return json({ error: 'Email and role are required' }, { status: 400 });
10
+ }
11
+ const emailAdapter = getCMS().emailAdapter;
12
+ if (!emailAdapter) {
13
+ return json({ error: 'Email adapter not configured' }, { status: 400 });
14
+ }
15
+ const exists = await checkEmailExists(email);
16
+ if (exists) {
17
+ return json({ error: 'Email already registered' }, { status: 409 });
18
+ }
19
+ const inv = await createInvitation(email, role, user.id);
20
+ const inviteUrl = `${url.origin}/admin/accept-invite?token=${inv.token}`;
21
+ try {
22
+ await emailAdapter.sendMail({
23
+ to: [email],
24
+ subject: 'You have been invited to includio CMS',
25
+ html: `<p>You have been invited to join the CMS as <strong>${role}</strong>.</p>
26
+ <p><a href="${inviteUrl}">Click here to accept the invitation</a></p>
27
+ <p>This link expires in 7 days.</p>`
28
+ });
29
+ }
30
+ catch (err) {
31
+ await deleteInvitation(inv.id);
32
+ return json({ error: 'Failed to send email: ' + (err instanceof Error ? err.message : 'Unknown error') }, { status: 502 });
33
+ }
34
+ return json({ success: true, invitationId: inv.id });
35
+ };
36
+ export const GET = async () => {
37
+ requireRole('admin');
38
+ const invitations = await getPendingInvitations();
39
+ return json({ invitations });
40
+ };
41
+ export const PATCH = async ({ request, url }) => {
42
+ requireRole('admin');
43
+ const { id } = await request.json();
44
+ if (!id) {
45
+ return json({ error: 'Invitation id is required' }, { status: 400 });
46
+ }
47
+ const emailAdapter = getCMS().emailAdapter;
48
+ if (!emailAdapter) {
49
+ return json({ error: 'Email adapter not configured' }, { status: 400 });
50
+ }
51
+ const inv = await getInvitationById(id);
52
+ if (!inv) {
53
+ return json({ error: 'Invitation not found or expired' }, { status: 404 });
54
+ }
55
+ const inviteUrl = `${url.origin}/admin/accept-invite?token=${inv.token}`;
56
+ try {
57
+ await emailAdapter.sendMail({
58
+ to: [inv.email],
59
+ subject: 'You have been invited to includio CMS',
60
+ html: `<p>You have been invited to join the CMS as <strong>${inv.role}</strong>.</p>
61
+ <p><a href="${inviteUrl}">Click here to accept the invitation</a></p>
62
+ <p>This link expires in 7 days.</p>`
63
+ });
64
+ }
65
+ catch (err) {
66
+ return json({ error: 'Failed to send email: ' + (err instanceof Error ? err.message : 'Unknown error') }, { status: 502 });
67
+ }
68
+ return json({ success: true });
69
+ };
70
+ export const DELETE = async ({ request }) => {
71
+ requireRole('admin');
72
+ const { id } = await request.json();
73
+ if (!id) {
74
+ return json({ error: 'Invitation id is required' }, { status: 400 });
75
+ }
76
+ await deleteInvitation(id);
77
+ return json({ success: true });
78
+ };