@manablox/api-rpc 0.1.0 → 0.3.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/README.md +20 -0
- package/dist/index.d.ts +5971 -0
- package/dist/index.js +994 -0
- package/package.json +20 -11
- package/src/base.ts +0 -68
- package/src/context.ts +0 -18
- package/src/index.ts +0 -22
- package/src/routers/asset.ts +0 -67
- package/src/routers/content-type.ts +0 -116
- package/src/routers/content.ts +0 -200
- package/src/routers/space.ts +0 -267
- package/src/routers/user.ts +0 -68
- package/tsconfig.json +0 -1
package/package.json
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@manablox/api-rpc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
7
|
-
"types": "./
|
|
8
|
-
"default": "./
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"default": "./dist/index.js"
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
|
-
"main": "./
|
|
12
|
-
"types": "./
|
|
11
|
+
"main": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@manablox/auth": "0.
|
|
15
|
-
"@manablox/core": "0.
|
|
16
|
-
"@manablox/db": "0.
|
|
17
|
-
"@manablox/media": "0.
|
|
18
|
-
"@manablox/services": "0.
|
|
14
|
+
"@manablox/auth": "0.3.0",
|
|
15
|
+
"@manablox/core": "0.3.0",
|
|
16
|
+
"@manablox/db": "0.3.0",
|
|
17
|
+
"@manablox/media": "0.3.0",
|
|
18
|
+
"@manablox/services": "0.3.0",
|
|
19
|
+
"@manablox/workflows": "0.3.0",
|
|
19
20
|
"@orpc/server": "^1.15.0",
|
|
20
21
|
"@orpc/openapi": "^1.15.0",
|
|
21
22
|
"zod": "^4.5.4"
|
|
@@ -23,10 +24,18 @@
|
|
|
23
24
|
"devDependencies": {
|
|
24
25
|
"@manablox/config-typescript": "0.0.0",
|
|
25
26
|
"@types/node": "^26.4.1",
|
|
27
|
+
"tsdown": "^0.23.0",
|
|
26
28
|
"typescript": "^7.0.2",
|
|
27
29
|
"vitest": "^5.0.0"
|
|
28
30
|
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"!dist/**/*.map",
|
|
34
|
+
"README.md"
|
|
35
|
+
],
|
|
29
36
|
"scripts": {
|
|
30
|
-
"
|
|
37
|
+
"build": "tsdown",
|
|
38
|
+
"typecheck": "tsc --noEmit",
|
|
39
|
+
"test": "vitest run"
|
|
31
40
|
}
|
|
32
41
|
}
|
package/src/base.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import { assertCan, type Permission } from '@manablox/auth';
|
|
2
|
-
import { ManabloxError } from '@manablox/core';
|
|
3
|
-
import { ORPCError, os } from '@orpc/server';
|
|
4
|
-
import type { RpcContext } from './context.js';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Base procedure builder. Every management procedure runs through it, so the
|
|
8
|
-
* `ManabloxError` → transport mapping lives in exactly one place.
|
|
9
|
-
*/
|
|
10
|
-
export const base = os.$context<RpcContext>().use(async ({ next }) => {
|
|
11
|
-
try {
|
|
12
|
-
return await next();
|
|
13
|
-
} catch (error) {
|
|
14
|
-
throw toOrpcError(error);
|
|
15
|
-
}
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
export function toOrpcError(error: unknown): unknown {
|
|
19
|
-
if (!ManabloxError.is(error)) return error;
|
|
20
|
-
|
|
21
|
-
const code = (
|
|
22
|
-
{
|
|
23
|
-
validation: 'BAD_REQUEST',
|
|
24
|
-
bad_request: 'BAD_REQUEST',
|
|
25
|
-
not_found: 'NOT_FOUND',
|
|
26
|
-
conflict: 'CONFLICT',
|
|
27
|
-
forbidden: 'FORBIDDEN',
|
|
28
|
-
unauthorized: 'UNAUTHORIZED',
|
|
29
|
-
internal: 'INTERNAL_SERVER_ERROR',
|
|
30
|
-
} as const
|
|
31
|
-
)[error.kind];
|
|
32
|
-
|
|
33
|
-
return new ORPCError(code, {
|
|
34
|
-
message: error.key,
|
|
35
|
-
// Structured details survive to the client: key, path and params per problem.
|
|
36
|
-
data: { key: error.key, details: error.details },
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** Requires an authenticated principal. */
|
|
41
|
-
export const authed = base.use(async ({ context, next }) => {
|
|
42
|
-
if (!context.principal) throw toOrpcError(ManabloxError.unauthorized());
|
|
43
|
-
return next({ context: { ...context, principal: context.principal } });
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Requires a permission in the space named by the input's `spaceId`.
|
|
48
|
-
*
|
|
49
|
-
* Authorisation is a middleware rather than a call at the top of each handler, so a new
|
|
50
|
-
* procedure cannot forget it — the input type makes `spaceId` mandatory.
|
|
51
|
-
*/
|
|
52
|
-
export function scoped(permission: Permission) {
|
|
53
|
-
return authed.use(async ({ context, next }, input: unknown) => {
|
|
54
|
-
const spaceId = (input as { spaceId?: string } | undefined)?.spaceId ?? null;
|
|
55
|
-
assertCan(context.principal, spaceId, permission);
|
|
56
|
-
return next();
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** Requires an instance-wide superadmin, for operations that are not space-scoped. */
|
|
61
|
-
export const superadmin = authed.use(async ({ context, next }) => {
|
|
62
|
-
// A space-restricted API key is confined to those spaces, so it never reaches an
|
|
63
|
-
// instance-wide operation even when its owner is a superadmin.
|
|
64
|
-
if (context.principal?.role !== 'superadmin' || context.principal.allowedSpaceIds) {
|
|
65
|
-
throw toOrpcError(ManabloxError.forbidden('auth.superadminRequired'));
|
|
66
|
-
}
|
|
67
|
-
return next();
|
|
68
|
-
});
|
package/src/context.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { ApiKeyService, ManabloxAuth, Principal } from '@manablox/auth';
|
|
2
|
-
import type { Manablox } from '@manablox/core';
|
|
3
|
-
import type { Repositories } from '@manablox/db';
|
|
4
|
-
import type { MediaService } from '@manablox/media';
|
|
5
|
-
import type { ContentService, ContentTypeService, Loaders } from '@manablox/services';
|
|
6
|
-
|
|
7
|
-
export interface RpcContext {
|
|
8
|
-
manablox: Manablox;
|
|
9
|
-
repos: Repositories;
|
|
10
|
-
auth: ManabloxAuth;
|
|
11
|
-
apiKeys: ApiKeyService;
|
|
12
|
-
media: MediaService;
|
|
13
|
-
content: ContentService;
|
|
14
|
-
contentTypes: ContentTypeService;
|
|
15
|
-
loaders: Loaders;
|
|
16
|
-
principal: Principal | null;
|
|
17
|
-
headers: Headers;
|
|
18
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { assetRouter } from './routers/asset.js';
|
|
2
|
-
import { contentRouter } from './routers/content.js';
|
|
3
|
-
import { contentTypeRouter } from './routers/content-type.js';
|
|
4
|
-
import { spaceRouter } from './routers/space.js';
|
|
5
|
-
import { userRouter } from './routers/user.js';
|
|
6
|
-
|
|
7
|
-
export * from './base.js';
|
|
8
|
-
export * from './context.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* The management API. The admin imports the *type* of this object and gets end-to-end
|
|
12
|
-
* safety with no codegen step.
|
|
13
|
-
*/
|
|
14
|
-
export const router = {
|
|
15
|
-
content: contentRouter,
|
|
16
|
-
contentTypes: contentTypeRouter,
|
|
17
|
-
spaces: spaceRouter,
|
|
18
|
-
assets: assetRouter,
|
|
19
|
-
users: userRouter,
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export type ManabloxRouter = typeof router;
|
package/src/routers/asset.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { scoped } from '../base.js';
|
|
3
|
-
|
|
4
|
-
const uuid = z.string().uuid();
|
|
5
|
-
|
|
6
|
-
export const assetRouter = {
|
|
7
|
-
list: scoped('asset:read')
|
|
8
|
-
.input(
|
|
9
|
-
z.object({
|
|
10
|
-
spaceId: uuid,
|
|
11
|
-
mimeType: z.string().optional(),
|
|
12
|
-
search: z.string().max(200).optional(),
|
|
13
|
-
limit: z.number().int().min(1).max(100).default(40),
|
|
14
|
-
offset: z.number().int().min(0).default(0),
|
|
15
|
-
}),
|
|
16
|
-
)
|
|
17
|
-
.handler(async ({ input, context }) => {
|
|
18
|
-
const page = await context.repos.assets.list(
|
|
19
|
-
{
|
|
20
|
-
spaceId: input.spaceId,
|
|
21
|
-
...(input.mimeType ? { mimeType: input.mimeType } : {}),
|
|
22
|
-
...(input.search ? { search: input.search } : {}),
|
|
23
|
-
},
|
|
24
|
-
{ limit: input.limit, offset: input.offset },
|
|
25
|
-
);
|
|
26
|
-
return {
|
|
27
|
-
...page,
|
|
28
|
-
items: page.items.map((asset) => ({
|
|
29
|
-
...asset,
|
|
30
|
-
url: context.media.urlFor(asset),
|
|
31
|
-
thumbnailUrl: asset.mimeType.startsWith('image/')
|
|
32
|
-
? context.media.urlFor(asset, 'thumb', 'webp')
|
|
33
|
-
: null,
|
|
34
|
-
})),
|
|
35
|
-
};
|
|
36
|
-
}),
|
|
37
|
-
|
|
38
|
-
get: scoped('asset:read')
|
|
39
|
-
.input(z.object({ spaceId: uuid, id: uuid }))
|
|
40
|
-
.handler(async ({ input, context }) => {
|
|
41
|
-
const asset = await context.repos.assets.findById(input.id);
|
|
42
|
-
if (!asset) return null;
|
|
43
|
-
return { ...asset, url: context.media.urlFor(asset) };
|
|
44
|
-
}),
|
|
45
|
-
|
|
46
|
-
update: scoped('asset:write')
|
|
47
|
-
.input(
|
|
48
|
-
z.object({
|
|
49
|
-
spaceId: uuid,
|
|
50
|
-
id: uuid,
|
|
51
|
-
name: z.string().max(200).optional(),
|
|
52
|
-
alt: z.string().max(500).nullable().optional(),
|
|
53
|
-
title: z.string().max(500).nullable().optional(),
|
|
54
|
-
}),
|
|
55
|
-
)
|
|
56
|
-
.handler(async ({ input, context }) => {
|
|
57
|
-
const { spaceId: _spaceId, id, ...data } = input;
|
|
58
|
-
return context.repos.assets.update(id, data);
|
|
59
|
-
}),
|
|
60
|
-
|
|
61
|
-
delete: scoped('asset:delete')
|
|
62
|
-
.input(z.object({ spaceId: uuid, id: uuid }))
|
|
63
|
-
.handler(async ({ input, context }) => {
|
|
64
|
-
await context.media.delete(input.id);
|
|
65
|
-
return { ok: true };
|
|
66
|
-
}),
|
|
67
|
-
};
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { renderContentTypeConfig } from '@manablox/services';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { base, scoped, superadmin } from '../base.js';
|
|
4
|
-
|
|
5
|
-
const uuid = z.string().uuid();
|
|
6
|
-
|
|
7
|
-
const fieldSchema = z.object({
|
|
8
|
-
id: z.string().optional(),
|
|
9
|
-
name: z.string().min(1).max(64),
|
|
10
|
-
label: z.string().optional(),
|
|
11
|
-
type: z.string(),
|
|
12
|
-
settings: z.record(z.string(), z.unknown()).default({}),
|
|
13
|
-
required: z.boolean().default(false),
|
|
14
|
-
localized: z.boolean().default(false),
|
|
15
|
-
unique: z.boolean().default(false),
|
|
16
|
-
readRoles: z.array(z.string()).optional(),
|
|
17
|
-
writeRoles: z.array(z.string()).optional(),
|
|
18
|
-
admin: z
|
|
19
|
-
.object({
|
|
20
|
-
zone: z.enum(['main', 'sidebar']).default('main'),
|
|
21
|
-
width: z.number().int().min(25).max(100).default(100),
|
|
22
|
-
position: z.number().int().default(0),
|
|
23
|
-
help: z.string().optional(),
|
|
24
|
-
placeholder: z.string().optional(),
|
|
25
|
-
})
|
|
26
|
-
.optional(),
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const contentTypeSchema = z.object({
|
|
30
|
-
name: z.string().min(1).max(64),
|
|
31
|
-
label: z.string().optional(),
|
|
32
|
-
description: z.string().optional(),
|
|
33
|
-
icon: z.string().optional(),
|
|
34
|
-
kind: z.enum(['content', 'block']).default('content'),
|
|
35
|
-
spaceId: uuid.nullable().default(null),
|
|
36
|
-
hasSlug: z.boolean().optional(),
|
|
37
|
-
isPublishable: z.boolean().optional(),
|
|
38
|
-
isVisibleInTree: z.boolean().optional(),
|
|
39
|
-
canBeVisibleInMenu: z.boolean().optional(),
|
|
40
|
-
fields: z.array(fieldSchema).default([]),
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
export const contentTypeRouter = {
|
|
44
|
-
list: scoped('contentType:read')
|
|
45
|
-
.input(z.object({ spaceId: uuid }))
|
|
46
|
-
.handler(async ({ input, context }) => context.contentTypes.list(input.spaceId)),
|
|
47
|
-
|
|
48
|
-
get: scoped('contentType:read')
|
|
49
|
-
.input(z.object({ spaceId: uuid, id: uuid }))
|
|
50
|
-
.handler(async ({ input, context }) => context.contentTypes.get(input.id)),
|
|
51
|
-
|
|
52
|
-
create: scoped('contentType:write')
|
|
53
|
-
.input(contentTypeSchema.extend({ spaceId: uuid }))
|
|
54
|
-
.handler(async ({ input, context }) => context.contentTypes.create(input)),
|
|
55
|
-
|
|
56
|
-
update: scoped('contentType:write')
|
|
57
|
-
.input(contentTypeSchema.extend({ spaceId: uuid, id: uuid }))
|
|
58
|
-
.handler(async ({ input, context }) => context.contentTypes.update(input.id, input)),
|
|
59
|
-
|
|
60
|
-
delete: scoped('contentType:delete')
|
|
61
|
-
.input(z.object({ spaceId: uuid, id: uuid }))
|
|
62
|
-
.handler(async ({ input, context }) => {
|
|
63
|
-
await context.contentTypes.delete(input.id);
|
|
64
|
-
return { ok: true };
|
|
65
|
-
}),
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* The field-type catalogue the admin's "add field" menu is built from. Derived from
|
|
69
|
-
* the registry, so a plugin's field type appears in the menu with no admin change.
|
|
70
|
-
*/
|
|
71
|
-
fieldTypes: base.handler(async ({ context }) =>
|
|
72
|
-
context.manablox.fieldTypes.all.map((type) => ({
|
|
73
|
-
name: type.name,
|
|
74
|
-
label: type.label,
|
|
75
|
-
icon: type.icon ?? null,
|
|
76
|
-
description: type.description ?? null,
|
|
77
|
-
nested: type.nested ?? false,
|
|
78
|
-
filters: type.filters,
|
|
79
|
-
admin: type.admin,
|
|
80
|
-
})),
|
|
81
|
-
),
|
|
82
|
-
|
|
83
|
-
/** JSON Schema for one field type's settings, so the admin renders its form generically. */
|
|
84
|
-
fieldTypeSettingsSchema: base
|
|
85
|
-
.input(z.object({ name: z.string() }))
|
|
86
|
-
.handler(async ({ input, context }) => {
|
|
87
|
-
const type = context.manablox.fieldTypes.get(input.name);
|
|
88
|
-
const schema = type.settingsSchema as unknown as { toJSONSchema?: () => unknown };
|
|
89
|
-
return {
|
|
90
|
-
name: type.name,
|
|
91
|
-
jsonSchema: typeof schema.toJSONSchema === 'function' ? schema.toJSONSchema() : null,
|
|
92
|
-
};
|
|
93
|
-
}),
|
|
94
|
-
|
|
95
|
-
/**
|
|
96
|
-
* The space's runtime types rendered as `manablox.config.ts` source, for moving a type
|
|
97
|
-
* built in the admin into code where it can be reviewed and versioned.
|
|
98
|
-
*/
|
|
99
|
-
config: scoped('contentType:read')
|
|
100
|
-
.input(z.object({ spaceId: uuid, ids: z.array(uuid).optional() }))
|
|
101
|
-
.handler(async ({ input, context }) => {
|
|
102
|
-
const wanted = input.ids?.length ? new Set(input.ids) : null;
|
|
103
|
-
const types = context.manablox.contentTypes
|
|
104
|
-
.forSpace(input.spaceId)
|
|
105
|
-
// Code-defined types already live in a config file; re-emitting them would invite
|
|
106
|
-
// a second, diverging definition of the same type.
|
|
107
|
-
.filter((type) => type.source !== 'code' && (!wanted || wanted.has(type.id)));
|
|
108
|
-
|
|
109
|
-
return { code: renderContentTypeConfig(types), count: types.length };
|
|
110
|
-
}),
|
|
111
|
-
|
|
112
|
-
reload: superadmin.handler(async ({ context }) => {
|
|
113
|
-
await context.manablox.reload(await context.repos.contentTypes.all());
|
|
114
|
-
return { schemaVersion: context.manablox.contentTypes.schemaVersion };
|
|
115
|
-
}),
|
|
116
|
-
};
|
package/src/routers/content.ts
DELETED
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import { actorRoles } from '@manablox/auth';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { base, scoped } from '../base.js';
|
|
4
|
-
|
|
5
|
-
const uuid = z.string().uuid();
|
|
6
|
-
|
|
7
|
-
const filterSchema = z.object({
|
|
8
|
-
spaceId: uuid,
|
|
9
|
-
typeIds: z.array(uuid).optional(),
|
|
10
|
-
locale: z.string().optional(),
|
|
11
|
-
status: z.enum(['draft', 'published', 'archived']).optional(),
|
|
12
|
-
parentId: uuid.nullable().optional(),
|
|
13
|
-
under: uuid.optional(),
|
|
14
|
-
search: z.string().max(200).optional(),
|
|
15
|
-
visibleInMenu: z.boolean().optional(),
|
|
16
|
-
fields: z
|
|
17
|
-
.array(
|
|
18
|
-
z.object({
|
|
19
|
-
name: z.string(),
|
|
20
|
-
op: z.enum([
|
|
21
|
-
'eq',
|
|
22
|
-
'neq',
|
|
23
|
-
'lt',
|
|
24
|
-
'lte',
|
|
25
|
-
'gt',
|
|
26
|
-
'gte',
|
|
27
|
-
'in',
|
|
28
|
-
'notIn',
|
|
29
|
-
'contains',
|
|
30
|
-
'startsWith',
|
|
31
|
-
'endsWith',
|
|
32
|
-
'isNull',
|
|
33
|
-
'isNotNull',
|
|
34
|
-
]),
|
|
35
|
-
value: z.unknown().optional(),
|
|
36
|
-
}),
|
|
37
|
-
)
|
|
38
|
-
.max(10)
|
|
39
|
-
.optional(),
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const paginationSchema = z.object({
|
|
43
|
-
limit: z.number().int().min(1).max(200).default(25),
|
|
44
|
-
offset: z.number().int().min(0).default(0),
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
const sortSchema = z
|
|
48
|
-
.array(
|
|
49
|
-
z.object({
|
|
50
|
-
by: z.enum(['position', 'title', 'createdAt', 'updatedAt', 'publishedAt', 'slug']),
|
|
51
|
-
direction: z.enum(['asc', 'desc']).default('asc'),
|
|
52
|
-
}),
|
|
53
|
-
)
|
|
54
|
-
.max(3);
|
|
55
|
-
|
|
56
|
-
const saveSchema = z.object({
|
|
57
|
-
spaceId: uuid,
|
|
58
|
-
typeId: uuid,
|
|
59
|
-
locale: z.string().min(2).max(10).default('en'),
|
|
60
|
-
localizationId: uuid.optional(),
|
|
61
|
-
parentId: uuid.nullable().optional(),
|
|
62
|
-
title: z.string().min(1).max(500),
|
|
63
|
-
slug: z.string().max(200).optional(),
|
|
64
|
-
fields: z.record(z.string(), z.unknown()).default({}),
|
|
65
|
-
visibleInMenu: z.boolean().optional(),
|
|
66
|
-
position: z.number().int().optional(),
|
|
67
|
-
expectedVersion: z.number().int().positive().optional(),
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
export const contentRouter = {
|
|
71
|
-
list: scoped('content:read')
|
|
72
|
-
.input(
|
|
73
|
-
z
|
|
74
|
-
.object({
|
|
75
|
-
filter: filterSchema,
|
|
76
|
-
pagination: paginationSchema.optional(),
|
|
77
|
-
sort: sortSchema.optional(),
|
|
78
|
-
})
|
|
79
|
-
.transform((v) => ({ ...v, spaceId: v.filter.spaceId })),
|
|
80
|
-
)
|
|
81
|
-
.handler(async ({ input, context }) =>
|
|
82
|
-
context.content.list(
|
|
83
|
-
input.filter,
|
|
84
|
-
input.pagination ?? { limit: 25, offset: 0 },
|
|
85
|
-
input.sort ?? [],
|
|
86
|
-
{ actor: toActor(context, input.filter.spaceId) },
|
|
87
|
-
),
|
|
88
|
-
),
|
|
89
|
-
|
|
90
|
-
tree: scoped('content:read')
|
|
91
|
-
.input(
|
|
92
|
-
z.object({
|
|
93
|
-
spaceId: uuid,
|
|
94
|
-
locale: z.string().default('en'),
|
|
95
|
-
rootId: uuid.nullable().default(null),
|
|
96
|
-
}),
|
|
97
|
-
)
|
|
98
|
-
.handler(async ({ input, context }) =>
|
|
99
|
-
context.content.tree(input.spaceId, input.locale, input.rootId),
|
|
100
|
-
),
|
|
101
|
-
|
|
102
|
-
get: scoped('content:read')
|
|
103
|
-
.input(z.object({ spaceId: uuid, id: uuid }))
|
|
104
|
-
.handler(async ({ input, context }) =>
|
|
105
|
-
context.content.get(input.id, { actor: toActor(context, input.spaceId) }),
|
|
106
|
-
),
|
|
107
|
-
|
|
108
|
-
/** Field values with defaults filled in — what the editor opens a new document with. */
|
|
109
|
-
blank: scoped('content:read')
|
|
110
|
-
.input(z.object({ spaceId: uuid, typeId: uuid }))
|
|
111
|
-
.handler(async ({ input, context }) => ({
|
|
112
|
-
fields: await context.content.initFields(context.manablox.contentTypes.get(input.typeId)),
|
|
113
|
-
})),
|
|
114
|
-
|
|
115
|
-
create: scoped('content:write')
|
|
116
|
-
.input(saveSchema)
|
|
117
|
-
.handler(async ({ input, context }) =>
|
|
118
|
-
context.content.create(input, toActor(context, input.spaceId)),
|
|
119
|
-
),
|
|
120
|
-
|
|
121
|
-
update: scoped('content:write')
|
|
122
|
-
.input(saveSchema.extend({ id: uuid }))
|
|
123
|
-
.handler(async ({ input, context }) =>
|
|
124
|
-
context.content.update(input.id, input, toActor(context, input.spaceId)),
|
|
125
|
-
),
|
|
126
|
-
|
|
127
|
-
delete: scoped('content:delete')
|
|
128
|
-
.input(z.object({ spaceId: uuid, id: uuid }))
|
|
129
|
-
.handler(async ({ input, context }) => ({
|
|
130
|
-
deleted: await context.content.delete(input.id, toActor(context, input.spaceId)),
|
|
131
|
-
})),
|
|
132
|
-
|
|
133
|
-
publish: scoped('content:publish')
|
|
134
|
-
.input(z.object({ spaceId: uuid, id: uuid }))
|
|
135
|
-
.handler(async ({ input, context }) =>
|
|
136
|
-
context.content.publish(input.id, toActor(context, input.spaceId)),
|
|
137
|
-
),
|
|
138
|
-
|
|
139
|
-
unpublish: scoped('content:publish')
|
|
140
|
-
.input(z.object({ spaceId: uuid, id: uuid }))
|
|
141
|
-
.handler(async ({ input, context }) => {
|
|
142
|
-
await context.content.unpublish(input.id, toActor(context, input.spaceId));
|
|
143
|
-
return { ok: true };
|
|
144
|
-
}),
|
|
145
|
-
|
|
146
|
-
/** Reparent or reorder a document in the tree — a drag in the admin's tree panel. */
|
|
147
|
-
move: scoped('content:write')
|
|
148
|
-
.input(
|
|
149
|
-
z.object({
|
|
150
|
-
spaceId: uuid,
|
|
151
|
-
id: uuid,
|
|
152
|
-
parentId: uuid.nullable(),
|
|
153
|
-
position: z.number().int().min(0),
|
|
154
|
-
}),
|
|
155
|
-
)
|
|
156
|
-
.handler(async ({ input, context }) =>
|
|
157
|
-
context.content.move(input.spaceId, input.id, input.parentId, input.position),
|
|
158
|
-
),
|
|
159
|
-
|
|
160
|
-
/** Every locale a document exists in, for the editor's language switcher. */
|
|
161
|
-
translations: scoped('content:read')
|
|
162
|
-
.input(z.object({ spaceId: uuid, id: uuid }))
|
|
163
|
-
.handler(async ({ input, context }) => context.content.translations(input.spaceId, input.id)),
|
|
164
|
-
|
|
165
|
-
/** Starts a translation of an existing document, in the localization group it shares. */
|
|
166
|
-
createTranslation: scoped('content:write')
|
|
167
|
-
.input(z.object({ spaceId: uuid, id: uuid, locale: z.string().min(2).max(10) }))
|
|
168
|
-
.handler(async ({ input, context }) =>
|
|
169
|
-
context.content.createTranslation(
|
|
170
|
-
input.spaceId,
|
|
171
|
-
input.id,
|
|
172
|
-
input.locale,
|
|
173
|
-
toActor(context, input.spaceId),
|
|
174
|
-
),
|
|
175
|
-
),
|
|
176
|
-
|
|
177
|
-
versions: scoped('content:read')
|
|
178
|
-
.input(z.object({ spaceId: uuid, id: uuid }))
|
|
179
|
-
.handler(async ({ input, context }) => context.repos.content.versions(input.id)),
|
|
180
|
-
|
|
181
|
-
versionSnapshot: scoped('content:read')
|
|
182
|
-
.input(z.object({ spaceId: uuid, id: uuid, version: z.number().int().positive() }))
|
|
183
|
-
.handler(async ({ input, context }) =>
|
|
184
|
-
context.repos.content.versionSnapshot(input.id, input.version),
|
|
185
|
-
),
|
|
186
|
-
|
|
187
|
-
restore: scoped('content:write')
|
|
188
|
-
.input(z.object({ spaceId: uuid, id: uuid, version: z.number().int().positive() }))
|
|
189
|
-
.handler(async ({ input, context }) =>
|
|
190
|
-
context.content.restore(input.id, input.version, toActor(context, input.spaceId)),
|
|
191
|
-
),
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
export { base };
|
|
195
|
-
|
|
196
|
-
function toActor(context: { principal: unknown }, spaceId: string) {
|
|
197
|
-
const principal = context.principal as { userId: string } | null;
|
|
198
|
-
if (!principal) return null;
|
|
199
|
-
return { userId: principal.userId, roles: actorRoles(principal as never, spaceId) };
|
|
200
|
-
}
|