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.
- package/CHANGELOG.md +51 -0
- package/ROADMAP.md +17 -11
- package/dist/admin/api/accept-invite.d.ts +2 -0
- package/dist/admin/api/accept-invite.js +30 -0
- package/dist/admin/api/handler.d.ts +13 -0
- package/dist/admin/api/handler.js +36 -0
- package/dist/admin/api/invite.d.ts +5 -0
- package/dist/admin/api/invite.js +78 -0
- package/dist/admin/auth-client.d.ts +1165 -5
- package/dist/admin/auth-client.js +4 -1
- package/dist/admin/client/account/sessions-section.svelte +1 -21
- package/dist/admin/client/collection/collection-page.svelte +5 -7
- package/dist/admin/client/collection/collection-page.svelte.d.ts +2 -2
- package/dist/admin/client/entry/entry-page.svelte +5 -7
- package/dist/admin/client/entry/entry-page.svelte.d.ts +2 -2
- package/dist/admin/client/index.d.ts +2 -0
- package/dist/admin/client/index.js +2 -0
- package/dist/admin/client/users/accept-invite-page.svelte +118 -0
- package/dist/admin/client/users/accept-invite-page.svelte.d.ts +4 -0
- package/dist/admin/client/users/create-user-dialog.svelte +157 -0
- package/dist/admin/client/users/create-user-dialog.svelte.d.ts +8 -0
- package/dist/admin/client/users/delete-user-dialog.svelte +53 -0
- package/dist/admin/client/users/delete-user-dialog.svelte.d.ts +10 -0
- package/dist/admin/client/users/edit-user-dialog.svelte +127 -0
- package/dist/admin/client/users/edit-user-dialog.svelte.d.ts +16 -0
- package/dist/admin/client/users/invite-user-dialog.svelte +107 -0
- package/dist/admin/client/users/invite-user-dialog.svelte.d.ts +8 -0
- package/dist/admin/client/users/lang.d.ts +57 -0
- package/dist/admin/client/users/lang.js +114 -0
- package/dist/admin/client/users/pending-invitations.svelte +145 -0
- package/dist/admin/client/users/pending-invitations.svelte.d.ts +6 -0
- package/dist/admin/client/users/user-sessions-sheet.svelte +141 -0
- package/dist/admin/client/users/user-sessions-sheet.svelte.d.ts +8 -0
- package/dist/admin/client/users/users-page.svelte +262 -0
- package/dist/admin/client/users/users-page.svelte.d.ts +6 -0
- package/dist/admin/components/layout/lang.d.ts +1 -0
- package/dist/admin/components/layout/lang.js +4 -2
- package/dist/admin/components/layout/nav-main.svelte +15 -1
- package/dist/admin/remote/invite.d.ts +44 -0
- package/dist/admin/remote/invite.js +44 -0
- package/dist/admin/remote/middleware/auth.d.ts +5 -0
- package/dist/admin/remote/middleware/auth.js +7 -0
- package/dist/admin/utils/parseUserAgent.d.ts +5 -0
- package/dist/admin/utils/parseUserAgent.js +26 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +28 -0
- package/dist/cli/scaffold/admin.d.ts +4 -0
- package/dist/cli/scaffold/admin.js +185 -0
- package/dist/cms/runtime/types.d.ts +0 -7
- package/dist/components/ui/accordion/accordion.svelte.d.ts +1 -1
- package/dist/components/ui/calendar/calendar.svelte.d.ts +1 -1
- package/dist/components/ui/command/command-dialog.svelte.d.ts +1 -1
- package/dist/components/ui/command/command-input.svelte.d.ts +1 -1
- package/dist/components/ui/command/command.svelte.d.ts +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte.d.ts +1 -1
- package/dist/components/ui/input/input.svelte.d.ts +1 -1
- package/dist/components/ui/input-group/input-group-input.svelte.d.ts +2 -2
- package/dist/components/ui/input-group/input-group-textarea.svelte.d.ts +1 -1
- package/dist/components/ui/radio-group/radio-group.svelte.d.ts +1 -1
- package/dist/components/ui/sidebar/sidebar-input.svelte.d.ts +2 -2
- package/dist/components/ui/tabs/tabs.svelte.d.ts +1 -1
- package/dist/components/ui/textarea/textarea.svelte.d.ts +1 -1
- package/dist/components/ui/toggle-group/toggle-group-item.svelte.d.ts +1 -1
- package/dist/components/ui/toggle-group/toggle-group.svelte.d.ts +1 -1
- package/dist/core/cms.d.ts +3 -1
- package/dist/core/cms.js +3 -1
- package/dist/core/server/forms/submissions/operations/create.js +1 -1
- package/dist/email-nodemailer/index.d.ts +1 -0
- package/dist/server/auth.d.ts +8 -8
- package/dist/server/db/schema/auth-schema.d.ts +143 -0
- package/dist/server/db/schema/auth-schema.js +12 -0
- package/dist/sveltekit/server/handle.js +13 -0
- package/dist/types/cms.d.ts +20 -2
- package/dist/types/roles.d.ts +1 -0
- package/dist/types/roles.js +1 -0
- package/dist/updates/0.1.2/index.d.ts +2 -0
- package/dist/updates/0.1.2/index.js +36 -0
- package/dist/updates/0.1.3/index.d.ts +2 -0
- package/dist/updates/0.1.3/index.js +16 -0
- package/dist/updates/index.js +3 -1
- 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
|
-
- [
|
|
35
|
-
- [
|
|
36
|
-
- [
|
|
37
|
-
- [
|
|
38
|
-
- [
|
|
39
|
-
- [
|
|
40
|
-
- [
|
|
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
|
-
- [
|
|
45
|
-
- [
|
|
46
|
-
|
|
47
|
-
|
|
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 system — invite link generation (custom, nie w better-auth) <!-- files: src/lib/admin/remote/invite.ts -->
|
|
46
|
+
|
|
47
|
+
## 0.1.3 — Admin 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,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,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
|
+
};
|