@seshuk/payload-plugin-openapi 1.0.0-beta.1 → 1.0.0-beta.2
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 +8 -4
- package/dist/server/spec/buildDocument.js +62 -9
- package/dist/server/spec/buildDocument.js.map +1 -1
- package/dist/server/spec/components.js.map +1 -1
- package/dist/server/spec/entitySchemas.d.ts +5 -6
- package/dist/server/spec/entitySchemas.js.map +1 -1
- package/dist/server/spec/filters.d.ts +5 -1
- package/dist/server/spec/filters.js +21 -10
- package/dist/server/spec/filters.js.map +1 -1
- package/dist/server/spec/hierarchy.js +9 -4
- package/dist/server/spec/hierarchy.js.map +1 -1
- package/dist/server/spec/paths/collections.js +111 -7
- package/dist/server/spec/paths/collections.js.map +1 -1
- package/dist/server/spec/paths/custom.d.ts +4 -2
- package/dist/server/spec/paths/custom.js +1 -1
- package/dist/server/spec/paths/custom.js.map +1 -1
- package/dist/server/spec/paths/system.d.ts +2 -1
- package/dist/server/spec/paths/system.js +67 -1
- package/dist/server/spec/paths/system.js.map +1 -1
- package/dist/server/spec/plugins/ecommerce.d.ts +3 -0
- package/dist/server/spec/plugins/ecommerce.js +270 -0
- package/dist/server/spec/plugins/ecommerce.js.map +1 -0
- package/dist/server/spec/plugins/importExport.d.ts +3 -0
- package/dist/server/spec/plugins/importExport.js +202 -0
- package/dist/server/spec/plugins/importExport.js.map +1 -0
- package/dist/server/spec/plugins/index.d.ts +3 -0
- package/dist/server/spec/plugins/index.js +21 -0
- package/dist/server/spec/plugins/index.js.map +1 -0
- package/dist/server/spec/plugins/mcp.d.ts +3 -0
- package/dist/server/spec/plugins/mcp.js +118 -0
- package/dist/server/spec/plugins/mcp.js.map +1 -0
- package/dist/server/spec/plugins/multiTenant.d.ts +3 -0
- package/dist/server/spec/plugins/multiTenant.js +39 -0
- package/dist/server/spec/plugins/multiTenant.js.map +1 -0
- package/dist/server/spec/plugins/search.d.ts +3 -0
- package/dist/server/spec/plugins/search.js +43 -0
- package/dist/server/spec/plugins/search.js.map +1 -0
- package/dist/server/spec/plugins/seo.d.ts +3 -0
- package/dist/server/spec/plugins/seo.js +83 -0
- package/dist/server/spec/plugins/seo.js.map +1 -0
- package/dist/server/spec/plugins/shared.d.ts +35 -0
- package/dist/server/spec/plugins/shared.js +37 -0
- package/dist/server/spec/plugins/shared.js.map +1 -0
- package/dist/server/spec/plugins/storageR2.d.ts +3 -0
- package/dist/server/spec/plugins/storageR2.js +91 -0
- package/dist/server/spec/plugins/storageR2.js.map +1 -0
- package/dist/server/spec/plugins/stripe.d.ts +10 -0
- package/dist/server/spec/plugins/stripe.js +102 -0
- package/dist/server/spec/plugins/stripe.js.map +1 -0
- package/dist/server/spec/security.d.ts +2 -1
- package/dist/server/spec/security.js +5 -2
- package/dist/server/spec/security.js.map +1 -1
- package/dist/server/spec/tags.d.ts +4 -1
- package/dist/server/spec/tags.js +18 -2
- package/dist/server/spec/tags.js.map +1 -1
- package/dist/server/ui/html.js +2 -2
- package/dist/server/ui/html.js.map +1 -1
- package/dist/shared/translations/locales/ar.js +74 -0
- package/dist/shared/translations/locales/ar.js.map +1 -1
- package/dist/shared/translations/locales/az.js +74 -0
- package/dist/shared/translations/locales/az.js.map +1 -1
- package/dist/shared/translations/locales/bg.js +74 -0
- package/dist/shared/translations/locales/bg.js.map +1 -1
- package/dist/shared/translations/locales/bnBd.js +74 -0
- package/dist/shared/translations/locales/bnBd.js.map +1 -1
- package/dist/shared/translations/locales/bnIn.js +74 -0
- package/dist/shared/translations/locales/bnIn.js.map +1 -1
- package/dist/shared/translations/locales/ca.js +74 -0
- package/dist/shared/translations/locales/ca.js.map +1 -1
- package/dist/shared/translations/locales/cs.js +74 -0
- package/dist/shared/translations/locales/cs.js.map +1 -1
- package/dist/shared/translations/locales/da.js +74 -0
- package/dist/shared/translations/locales/da.js.map +1 -1
- package/dist/shared/translations/locales/de.js +74 -0
- package/dist/shared/translations/locales/de.js.map +1 -1
- package/dist/shared/translations/locales/en.js +74 -0
- package/dist/shared/translations/locales/en.js.map +1 -1
- package/dist/shared/translations/locales/es.js +74 -0
- package/dist/shared/translations/locales/es.js.map +1 -1
- package/dist/shared/translations/locales/et.js +74 -0
- package/dist/shared/translations/locales/et.js.map +1 -1
- package/dist/shared/translations/locales/fa.js +74 -0
- package/dist/shared/translations/locales/fa.js.map +1 -1
- package/dist/shared/translations/locales/fr.js +74 -0
- package/dist/shared/translations/locales/fr.js.map +1 -1
- package/dist/shared/translations/locales/he.js +74 -0
- package/dist/shared/translations/locales/he.js.map +1 -1
- package/dist/shared/translations/locales/hr.js +74 -0
- package/dist/shared/translations/locales/hr.js.map +1 -1
- package/dist/shared/translations/locales/hu.js +74 -0
- package/dist/shared/translations/locales/hu.js.map +1 -1
- package/dist/shared/translations/locales/hy.js +74 -0
- package/dist/shared/translations/locales/hy.js.map +1 -1
- package/dist/shared/translations/locales/id.js +74 -0
- package/dist/shared/translations/locales/id.js.map +1 -1
- package/dist/shared/translations/locales/is.js +74 -0
- package/dist/shared/translations/locales/is.js.map +1 -1
- package/dist/shared/translations/locales/it.js +74 -0
- package/dist/shared/translations/locales/it.js.map +1 -1
- package/dist/shared/translations/locales/ja.js +74 -0
- package/dist/shared/translations/locales/ja.js.map +1 -1
- package/dist/shared/translations/locales/ko.js +74 -0
- package/dist/shared/translations/locales/ko.js.map +1 -1
- package/dist/shared/translations/locales/lt.js +74 -0
- package/dist/shared/translations/locales/lt.js.map +1 -1
- package/dist/shared/translations/locales/lv.js +74 -0
- package/dist/shared/translations/locales/lv.js.map +1 -1
- package/dist/shared/translations/locales/my.js +74 -0
- package/dist/shared/translations/locales/my.js.map +1 -1
- package/dist/shared/translations/locales/nb.js +74 -0
- package/dist/shared/translations/locales/nb.js.map +1 -1
- package/dist/shared/translations/locales/nl.js +74 -0
- package/dist/shared/translations/locales/nl.js.map +1 -1
- package/dist/shared/translations/locales/pl.js +74 -0
- package/dist/shared/translations/locales/pl.js.map +1 -1
- package/dist/shared/translations/locales/pt.js +74 -0
- package/dist/shared/translations/locales/pt.js.map +1 -1
- package/dist/shared/translations/locales/ro.js +74 -0
- package/dist/shared/translations/locales/ro.js.map +1 -1
- package/dist/shared/translations/locales/rs.js +74 -0
- package/dist/shared/translations/locales/rs.js.map +1 -1
- package/dist/shared/translations/locales/rsLatin.js +74 -0
- package/dist/shared/translations/locales/rsLatin.js.map +1 -1
- package/dist/shared/translations/locales/ru.js +74 -0
- package/dist/shared/translations/locales/ru.js.map +1 -1
- package/dist/shared/translations/locales/sk.js +74 -0
- package/dist/shared/translations/locales/sk.js.map +1 -1
- package/dist/shared/translations/locales/sl.js +74 -0
- package/dist/shared/translations/locales/sl.js.map +1 -1
- package/dist/shared/translations/locales/sv.js +74 -0
- package/dist/shared/translations/locales/sv.js.map +1 -1
- package/dist/shared/translations/locales/ta.js +74 -0
- package/dist/shared/translations/locales/ta.js.map +1 -1
- package/dist/shared/translations/locales/th.js +74 -0
- package/dist/shared/translations/locales/th.js.map +1 -1
- package/dist/shared/translations/locales/tr.js +74 -0
- package/dist/shared/translations/locales/tr.js.map +1 -1
- package/dist/shared/translations/locales/uk.js +74 -0
- package/dist/shared/translations/locales/uk.js.map +1 -1
- package/dist/shared/translations/locales/vi.js +74 -0
- package/dist/shared/translations/locales/vi.js.map +1 -1
- package/dist/shared/translations/locales/zh.js +74 -0
- package/dist/shared/translations/locales/zh.js.map +1 -1
- package/dist/shared/translations/locales/zhTw.js +74 -0
- package/dist/shared/translations/locales/zhTw.js.map +1 -1
- package/dist/shared/translations/types.d.ts +74 -0
- package/dist/shared/types/index.d.ts +23 -8
- package/package.json +23 -15
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { BuildContext } from "../../../shared/types/index.js";
|
|
2
|
+
import { Translate } from "../../../shared/translations/types.js";
|
|
3
|
+
import { Endpoint, SanitizedCollectionConfig, SanitizedConfig } from "payload";
|
|
4
|
+
import { OperationObject, PathsObject, ResponsesObject, SchemaObject } from "@scalar/openapi-types/3.2";
|
|
5
|
+
export interface PluginBuildArgs {
|
|
6
|
+
config: SanitizedConfig;
|
|
7
|
+
collections: SanitizedCollectionConfig[];
|
|
8
|
+
ctx: BuildContext;
|
|
9
|
+
options: Record<string, unknown> | undefined;
|
|
10
|
+
schemas: Record<string, SchemaObject>;
|
|
11
|
+
t: Translate;
|
|
12
|
+
}
|
|
13
|
+
export interface PluginGroup {
|
|
14
|
+
paths: PathsObject;
|
|
15
|
+
slug?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface OfficialPlugin {
|
|
18
|
+
slug: string;
|
|
19
|
+
tag: string;
|
|
20
|
+
installed?: (config: SanitizedConfig) => boolean;
|
|
21
|
+
build: (args: PluginBuildArgs) => PluginGroup[];
|
|
22
|
+
}
|
|
23
|
+
export declare const mountEndpoints: (endpoints: Endpoint[] | false | undefined, prefix: string, operation: (endpoint: Endpoint) => OperationObject | undefined) => PathsObject;
|
|
24
|
+
export declare const collectionGroups: (collections: SanitizedCollectionConfig[], ctx: BuildContext, operation: (endpoint: Endpoint, collection: SanitizedCollectionConfig) => OperationObject | undefined) => PluginGroup[];
|
|
25
|
+
export declare const idSchema: (ctx: BuildContext) => SchemaObject;
|
|
26
|
+
export declare const messageSchema: SchemaObject;
|
|
27
|
+
declare const MESSAGE_ERROR_KEYS: {
|
|
28
|
+
readonly '400': 'errorPlugin400';
|
|
29
|
+
readonly '401': 'errorPlugin401';
|
|
30
|
+
readonly '403': 'errorPlugin403';
|
|
31
|
+
readonly '404': 'errorPlugin404';
|
|
32
|
+
readonly '500': 'errorPlugin500';
|
|
33
|
+
};
|
|
34
|
+
export declare const messageErrors: (codes: Array<keyof typeof MESSAGE_ERROR_KEYS>, t: Translate, schema?: SchemaObject) => ResponsesObject;
|
|
35
|
+
//# sourceMappingURL=shared.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { jsonResponse } from "../components.js";
|
|
2
|
+
import { getOpenapiMeta, normalizePath } from "../paths/custom.js";
|
|
3
|
+
const mountEndpoints = (endpoints, prefix, operation) => {
|
|
4
|
+
const out = {};
|
|
5
|
+
for (const endpoint of endpoints || []) {
|
|
6
|
+
if (getOpenapiMeta(endpoint)) continue;
|
|
7
|
+
const op = operation(endpoint);
|
|
8
|
+
if (!op) continue;
|
|
9
|
+
const path = normalizePath(prefix, endpoint.path);
|
|
10
|
+
out[path] = {
|
|
11
|
+
...out[path],
|
|
12
|
+
[endpoint.method]: op
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
return out;
|
|
16
|
+
};
|
|
17
|
+
const collectionGroups = (collections, ctx, operation) => collections.map((collection) => ({
|
|
18
|
+
slug: collection.slug,
|
|
19
|
+
paths: mountEndpoints(collection.endpoints, `${ctx.apiRoute}/${collection.slug}`, (endpoint) => operation(endpoint, collection))
|
|
20
|
+
})).filter((group) => Object.keys(group.paths).length > 0);
|
|
21
|
+
const idSchema = (ctx) => ({ type: ctx.defaultIDType === "number" ? "integer" : "string" });
|
|
22
|
+
const messageSchema = {
|
|
23
|
+
type: "object",
|
|
24
|
+
properties: { message: { type: "string" } },
|
|
25
|
+
required: ["message"]
|
|
26
|
+
};
|
|
27
|
+
const MESSAGE_ERROR_KEYS = {
|
|
28
|
+
"400": "errorPlugin400",
|
|
29
|
+
"401": "errorPlugin401",
|
|
30
|
+
"403": "errorPlugin403",
|
|
31
|
+
"404": "errorPlugin404",
|
|
32
|
+
"500": "errorPlugin500"
|
|
33
|
+
};
|
|
34
|
+
const messageErrors = (codes, t, schema = messageSchema) => Object.fromEntries(codes.map((code) => [code, jsonResponse(t(MESSAGE_ERROR_KEYS[code]), schema)]));
|
|
35
|
+
export { collectionGroups, idSchema, messageErrors, messageSchema, mountEndpoints };
|
|
36
|
+
|
|
37
|
+
//# sourceMappingURL=shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shared.js","names":[],"sources":["../../../../src/server/spec/plugins/shared.ts"],"sourcesContent":["import type { OperationObject, PathsObject, ResponsesObject, SchemaObject } from '@scalar/openapi-types/3.2'\nimport type { Endpoint, SanitizedCollectionConfig, SanitizedConfig } from 'payload'\n\nimport { jsonResponse } from '@/server/spec/components.js'\nimport { getOpenapiMeta, normalizePath } from '@/server/spec/paths/custom.js'\nimport type { Translate } from '@/shared/translations/types.js'\nimport type { BuildContext } from '@/shared/types/index.js'\n\nexport interface PluginBuildArgs {\n config: SanitizedConfig\n collections: SanitizedCollectionConfig[]\n ctx: BuildContext\n options: Record<string, unknown> | undefined\n schemas: Record<string, SchemaObject>\n t: Translate\n}\n\nexport interface PluginGroup {\n paths: PathsObject\n slug?: string\n}\n\nexport interface OfficialPlugin {\n slug: string\n tag: string\n installed?: (config: SanitizedConfig) => boolean\n build: (args: PluginBuildArgs) => PluginGroup[]\n}\n\nexport const mountEndpoints = (\n endpoints: Endpoint[] | false | undefined,\n prefix: string,\n operation: (endpoint: Endpoint) => OperationObject | undefined,\n): PathsObject => {\n const out: PathsObject = {}\n for (const endpoint of endpoints || []) {\n if (getOpenapiMeta(endpoint)) continue\n const op = operation(endpoint)\n if (!op) continue\n const path = normalizePath(prefix, endpoint.path)\n out[path] = { ...out[path], [endpoint.method]: op }\n }\n return out\n}\n\nexport const collectionGroups = (\n collections: SanitizedCollectionConfig[],\n ctx: BuildContext,\n operation: (endpoint: Endpoint, collection: SanitizedCollectionConfig) => OperationObject | undefined,\n): PluginGroup[] =>\n collections\n .map((collection) => ({\n slug: collection.slug,\n paths: mountEndpoints(collection.endpoints, `${ctx.apiRoute}/${collection.slug}`, (endpoint) =>\n operation(endpoint, collection),\n ),\n }))\n .filter((group) => Object.keys(group.paths).length > 0)\n\nexport const idSchema = (ctx: BuildContext): SchemaObject => ({\n type: ctx.defaultIDType === 'number' ? 'integer' : 'string',\n})\n\nexport const messageSchema: SchemaObject = {\n type: 'object',\n properties: { message: { type: 'string' } },\n required: ['message'],\n}\n\nconst MESSAGE_ERROR_KEYS = {\n '400': 'errorPlugin400',\n '401': 'errorPlugin401',\n '403': 'errorPlugin403',\n '404': 'errorPlugin404',\n '500': 'errorPlugin500',\n} as const\n\nexport const messageErrors = (\n codes: Array<keyof typeof MESSAGE_ERROR_KEYS>,\n t: Translate,\n schema: SchemaObject = messageSchema,\n): ResponsesObject => Object.fromEntries(codes.map((code) => [code, jsonResponse(t(MESSAGE_ERROR_KEYS[code]), schema)]))\n"],"mappings":";;AA6BA,MAAa,kBACX,WACA,QACA,cACgB;CAChB,MAAM,MAAmB,CAAC;CAC1B,KAAK,MAAM,YAAY,aAAa,CAAC,GAAG;EACtC,IAAI,eAAe,QAAQ,GAAG;EAC9B,MAAM,KAAK,UAAU,QAAQ;EAC7B,IAAI,CAAC,IAAI;EACT,MAAM,OAAO,cAAc,QAAQ,SAAS,IAAI;EAChD,IAAI,QAAQ;GAAE,GAAG,IAAI;IAAQ,SAAS,SAAS;EAAG;CACpD;CACA,OAAO;AACT;AAEA,MAAa,oBACX,aACA,KACA,cAEA,YACG,KAAK,gBAAgB;CACpB,MAAM,WAAW;CACjB,OAAO,eAAe,WAAW,WAAW,GAAG,IAAI,SAAS,GAAG,WAAW,SAAS,aACjF,UAAU,UAAU,UAAU,CAChC;AACF,EAAE,CAAC,CACF,QAAQ,UAAU,OAAO,KAAK,MAAM,KAAK,CAAC,CAAC,SAAS,CAAC;AAE1D,MAAa,YAAY,SAAqC,EAC5D,MAAM,IAAI,kBAAkB,WAAW,YAAY,SACrD;AAEA,MAAa,gBAA8B;CACzC,MAAM;CACN,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,EAAE;CAC1C,UAAU,CAAC,SAAS;AACtB;AAEA,MAAM,qBAAqB;CACzB,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;CACP,OAAO;AACT;AAEA,MAAa,iBACX,OACA,GACA,SAAuB,kBACH,OAAO,YAAY,MAAM,KAAK,SAAS,CAAC,MAAM,aAAa,EAAE,mBAAmB,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { errorResponses } from "../components.js";
|
|
2
|
+
import { securedRequirement } from "../security.js";
|
|
3
|
+
import { mountEndpoints } from "./shared.js";
|
|
4
|
+
const TAG = "Storage R2";
|
|
5
|
+
const UPLOAD_PATH = /^\/storage-r2-multi-part-upload(?:-(\d+))?$/;
|
|
6
|
+
const query = (name, required, type = "string") => ({
|
|
7
|
+
name,
|
|
8
|
+
in: "query",
|
|
9
|
+
required,
|
|
10
|
+
schema: { type }
|
|
11
|
+
});
|
|
12
|
+
const storageR2 = {
|
|
13
|
+
slug: "@payloadcms/storage-r2",
|
|
14
|
+
tag: TAG,
|
|
15
|
+
installed: (config) => (config.storage ?? []).some((adapter) => adapter?.name === "r2"),
|
|
16
|
+
build: ({ config, ctx, t }) => {
|
|
17
|
+
const upload = (suffix) => ({
|
|
18
|
+
tags: [TAG],
|
|
19
|
+
operationId: `r2MultipartUpload${suffix}`,
|
|
20
|
+
summary: t("r2Upload"),
|
|
21
|
+
description: t("r2UploadDesc"),
|
|
22
|
+
parameters: [
|
|
23
|
+
query("collection", true),
|
|
24
|
+
query("fileName", true),
|
|
25
|
+
query("fileType", true),
|
|
26
|
+
query("docPrefix", false),
|
|
27
|
+
query("multipartId", false),
|
|
28
|
+
query("multipartKey", false),
|
|
29
|
+
query("multipartNumber", false, "integer"),
|
|
30
|
+
query("signedReceipt", false)
|
|
31
|
+
],
|
|
32
|
+
requestBody: { content: {
|
|
33
|
+
"application/octet-stream": { schema: {
|
|
34
|
+
type: "string",
|
|
35
|
+
format: "binary"
|
|
36
|
+
} },
|
|
37
|
+
"application/json": { schema: {
|
|
38
|
+
type: "array",
|
|
39
|
+
items: {
|
|
40
|
+
type: "object",
|
|
41
|
+
properties: {
|
|
42
|
+
partNumber: { type: "integer" },
|
|
43
|
+
etag: { type: "string" }
|
|
44
|
+
},
|
|
45
|
+
required: ["partNumber", "etag"]
|
|
46
|
+
}
|
|
47
|
+
} }
|
|
48
|
+
} },
|
|
49
|
+
responses: {
|
|
50
|
+
"200": {
|
|
51
|
+
description: t("r2UploadResult"),
|
|
52
|
+
content: {
|
|
53
|
+
"application/json": { schema: {
|
|
54
|
+
type: "object",
|
|
55
|
+
additionalProperties: true,
|
|
56
|
+
properties: {
|
|
57
|
+
filename: { type: "string" },
|
|
58
|
+
key: { type: "string" },
|
|
59
|
+
uploadId: { type: "string" },
|
|
60
|
+
uploadReference: {
|
|
61
|
+
type: "object",
|
|
62
|
+
additionalProperties: true
|
|
63
|
+
},
|
|
64
|
+
partNumber: { type: "integer" },
|
|
65
|
+
etag: { type: "string" }
|
|
66
|
+
}
|
|
67
|
+
} },
|
|
68
|
+
"text/plain": { schema: { type: "string" } }
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
...errorResponses([
|
|
72
|
+
"400",
|
|
73
|
+
"403",
|
|
74
|
+
"500"
|
|
75
|
+
], t),
|
|
76
|
+
"412": {
|
|
77
|
+
description: t("errorStorageR2412"),
|
|
78
|
+
content: { "text/plain": { schema: { type: "string" } } }
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
security: securedRequirement()
|
|
82
|
+
});
|
|
83
|
+
return [{ paths: mountEndpoints(config.endpoints, ctx.apiRoute, ({ method, path }) => {
|
|
84
|
+
const match = UPLOAD_PATH.exec(path);
|
|
85
|
+
return method === "post" && match ? upload(match[1] ?? "") : void 0;
|
|
86
|
+
}) }];
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
export { storageR2 };
|
|
90
|
+
|
|
91
|
+
//# sourceMappingURL=storageR2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storageR2.js","names":[],"sources":["../../../../src/server/spec/plugins/storageR2.ts"],"sourcesContent":["import type { OperationObject, ParameterObject } from '@scalar/openapi-types/3.2'\n\nimport { errorResponses } from '@/server/spec/components.js'\nimport { securedRequirement } from '@/server/spec/security.js'\n\nimport { type OfficialPlugin, mountEndpoints } from './shared.js'\n\nconst TAG = 'Storage R2'\n\nconst UPLOAD_PATH = /^\\/storage-r2-multi-part-upload(?:-(\\d+))?$/\n\nconst query = (name: string, required: boolean, type: 'string' | 'integer' = 'string'): ParameterObject => ({\n name,\n in: 'query',\n required,\n schema: { type },\n})\n\nexport const storageR2: OfficialPlugin = {\n slug: '@payloadcms/storage-r2',\n tag: TAG,\n installed: (config) => (config.storage ?? []).some((adapter) => adapter?.name === 'r2'),\n build: ({ config, ctx, t }) => {\n const upload = (suffix: string): OperationObject => ({\n tags: [TAG],\n operationId: `r2MultipartUpload${suffix}`,\n summary: t('r2Upload'),\n description: t('r2UploadDesc'),\n parameters: [\n query('collection', true),\n query('fileName', true),\n query('fileType', true),\n query('docPrefix', false),\n query('multipartId', false),\n query('multipartKey', false),\n query('multipartNumber', false, 'integer'),\n query('signedReceipt', false),\n ],\n requestBody: {\n content: {\n 'application/octet-stream': { schema: { type: 'string', format: 'binary' } },\n 'application/json': {\n schema: {\n type: 'array',\n items: {\n type: 'object',\n properties: { partNumber: { type: 'integer' }, etag: { type: 'string' } },\n required: ['partNumber', 'etag'],\n },\n },\n },\n },\n },\n responses: {\n '200': {\n description: t('r2UploadResult'),\n content: {\n 'application/json': {\n schema: {\n type: 'object',\n additionalProperties: true,\n properties: {\n filename: { type: 'string' },\n key: { type: 'string' },\n uploadId: { type: 'string' },\n uploadReference: { type: 'object', additionalProperties: true },\n partNumber: { type: 'integer' },\n etag: { type: 'string' },\n },\n },\n },\n 'text/plain': { schema: { type: 'string' } },\n },\n },\n ...errorResponses(['400', '403', '500'], t),\n '412': { description: t('errorStorageR2412'), content: { 'text/plain': { schema: { type: 'string' } } } },\n },\n security: securedRequirement(),\n })\n return [\n {\n paths: mountEndpoints(config.endpoints, ctx.apiRoute, ({ method, path }) => {\n const match = UPLOAD_PATH.exec(path)\n return method === 'post' && match ? upload(match[1] ?? '') : undefined\n }),\n },\n ]\n },\n}\n"],"mappings":";;;AAOA,MAAM,MAAM;AAEZ,MAAM,cAAc;AAEpB,MAAM,SAAS,MAAc,UAAmB,OAA6B,cAA+B;CAC1G;CACA,IAAI;CACJ;CACA,QAAQ,EAAE,KAAK;AACjB;AAEA,MAAa,YAA4B;CACvC,MAAM;CACN,KAAK;CACL,YAAY,YAAY,OAAO,WAAW,CAAC,EAAA,CAAG,MAAM,YAAY,SAAS,SAAS,IAAI;CACtF,QAAQ,EAAE,QAAQ,KAAK,QAAQ;EAC7B,MAAM,UAAU,YAAqC;GACnD,MAAM,CAAC,GAAG;GACV,aAAa,oBAAoB;GACjC,SAAS,EAAE,UAAU;GACrB,aAAa,EAAE,cAAc;GAC7B,YAAY;IACV,MAAM,cAAc,IAAI;IACxB,MAAM,YAAY,IAAI;IACtB,MAAM,YAAY,IAAI;IACtB,MAAM,aAAa,KAAK;IACxB,MAAM,eAAe,KAAK;IAC1B,MAAM,gBAAgB,KAAK;IAC3B,MAAM,mBAAmB,OAAO,SAAS;IACzC,MAAM,iBAAiB,KAAK;GAC9B;GACA,aAAa,EACX,SAAS;IACP,4BAA4B,EAAE,QAAQ;KAAE,MAAM;KAAU,QAAQ;IAAS,EAAE;IAC3E,oBAAoB,EAClB,QAAQ;KACN,MAAM;KACN,OAAO;MACL,MAAM;MACN,YAAY;OAAE,YAAY,EAAE,MAAM,UAAU;OAAG,MAAM,EAAE,MAAM,SAAS;MAAE;MACxE,UAAU,CAAC,cAAc,MAAM;KACjC;IACF,EACF;GACF,EACF;GACA,WAAW;IACT,OAAO;KACL,aAAa,EAAE,gBAAgB;KAC/B,SAAS;MACP,oBAAoB,EAClB,QAAQ;OACN,MAAM;OACN,sBAAsB;OACtB,YAAY;QACV,UAAU,EAAE,MAAM,SAAS;QAC3B,KAAK,EAAE,MAAM,SAAS;QACtB,UAAU,EAAE,MAAM,SAAS;QAC3B,iBAAiB;SAAE,MAAM;SAAU,sBAAsB;QAAK;QAC9D,YAAY,EAAE,MAAM,UAAU;QAC9B,MAAM,EAAE,MAAM,SAAS;OACzB;MACF,EACF;MACA,cAAc,EAAE,QAAQ,EAAE,MAAM,SAAS,EAAE;KAC7C;IACF;IACA,GAAG,eAAe;KAAC;KAAO;KAAO;IAAK,GAAG,CAAC;IAC1C,OAAO;KAAE,aAAa,EAAE,mBAAmB;KAAG,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,SAAS,EAAE,EAAE;IAAE;GAC1G;GACA,UAAU,mBAAmB;EAC/B;EACA,OAAO,CACL,EACE,OAAO,eAAe,OAAO,WAAW,IAAI,WAAW,EAAE,QAAQ,WAAW;GAC1E,MAAM,QAAQ,YAAY,KAAK,IAAI;GACnC,OAAO,WAAW,UAAU,QAAQ,OAAO,MAAM,MAAM,EAAE,IAAI,KAAA;EAC/D,CAAC,EACH,CACF;CACF;AACF"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Translate } from "../../../shared/translations/types.js";
|
|
2
|
+
import { OfficialPlugin } from "./shared.js";
|
|
3
|
+
import { OperationObject } from "@scalar/openapi-types/3.2";
|
|
4
|
+
export declare const stripeWebhookOperation: ({ tag, operationId, t }: {
|
|
5
|
+
tag: string;
|
|
6
|
+
operationId: string;
|
|
7
|
+
t: Translate;
|
|
8
|
+
}) => OperationObject;
|
|
9
|
+
export declare const stripe: OfficialPlugin;
|
|
10
|
+
//# sourceMappingURL=stripe.d.ts.map
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { jsonBody, jsonResponse } from "../components.js";
|
|
2
|
+
import { securedRequirement } from "../security.js";
|
|
3
|
+
import { messageErrors, mountEndpoints } from "./shared.js";
|
|
4
|
+
const TAG = "Stripe";
|
|
5
|
+
const stripeWebhookOperation = ({ tag, operationId, t }) => {
|
|
6
|
+
const received = {
|
|
7
|
+
type: "object",
|
|
8
|
+
properties: { received: { type: "boolean" } },
|
|
9
|
+
required: ["received"]
|
|
10
|
+
};
|
|
11
|
+
return {
|
|
12
|
+
tags: [tag],
|
|
13
|
+
operationId,
|
|
14
|
+
summary: t("stripeWebhook"),
|
|
15
|
+
parameters: [{
|
|
16
|
+
name: "stripe-signature",
|
|
17
|
+
in: "header",
|
|
18
|
+
schema: { type: "string" }
|
|
19
|
+
}],
|
|
20
|
+
requestBody: {
|
|
21
|
+
...jsonBody({
|
|
22
|
+
type: "object",
|
|
23
|
+
additionalProperties: true
|
|
24
|
+
}),
|
|
25
|
+
description: t("stripeWebhookBody")
|
|
26
|
+
},
|
|
27
|
+
responses: {
|
|
28
|
+
"200": jsonResponse(t("stripeWebhookResult"), received),
|
|
29
|
+
"400": jsonResponse(t("errorPluginStripeWebhook400"), received)
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
const stripe = {
|
|
34
|
+
slug: "@payloadcms/plugin-stripe",
|
|
35
|
+
tag: TAG,
|
|
36
|
+
build: ({ config, ctx, options, t }) => {
|
|
37
|
+
const rest = options?.rest;
|
|
38
|
+
const allowed = Array.isArray(rest?.allowedMethods) ? rest.allowedMethods : void 0;
|
|
39
|
+
const restOperation = {
|
|
40
|
+
tags: [TAG],
|
|
41
|
+
operationId: "stripeRest",
|
|
42
|
+
summary: t("stripeRest"),
|
|
43
|
+
requestBody: jsonBody({
|
|
44
|
+
type: "object",
|
|
45
|
+
properties: {
|
|
46
|
+
stripeMethod: {
|
|
47
|
+
type: "string",
|
|
48
|
+
...allowed ? { enum: [...new Set(allowed)] } : {}
|
|
49
|
+
},
|
|
50
|
+
stripeArgs: {
|
|
51
|
+
type: "array",
|
|
52
|
+
items: {}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
required: ["stripeMethod", "stripeArgs"]
|
|
56
|
+
}),
|
|
57
|
+
responses: {
|
|
58
|
+
"200": jsonResponse(t("stripeRestResult"), {
|
|
59
|
+
type: "object",
|
|
60
|
+
properties: {
|
|
61
|
+
data: {},
|
|
62
|
+
status: {
|
|
63
|
+
type: "integer",
|
|
64
|
+
enum: [200]
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
required: ["data", "status"]
|
|
68
|
+
}),
|
|
69
|
+
...messageErrors([
|
|
70
|
+
"400",
|
|
71
|
+
"401",
|
|
72
|
+
"403"
|
|
73
|
+
], t),
|
|
74
|
+
"404": jsonResponse(t("errorPluginStripeRest404"), {
|
|
75
|
+
type: "object",
|
|
76
|
+
properties: {
|
|
77
|
+
message: { type: "string" },
|
|
78
|
+
status: {
|
|
79
|
+
type: "integer",
|
|
80
|
+
enum: [404]
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
required: ["message", "status"]
|
|
84
|
+
}),
|
|
85
|
+
...messageErrors(["500"], t)
|
|
86
|
+
},
|
|
87
|
+
security: securedRequirement()
|
|
88
|
+
};
|
|
89
|
+
return [{ paths: mountEndpoints(config.endpoints, ctx.apiRoute, ({ method, path }) => {
|
|
90
|
+
if (method !== "post") return void 0;
|
|
91
|
+
if (path === "/stripe/webhooks") return stripeWebhookOperation({
|
|
92
|
+
tag: TAG,
|
|
93
|
+
operationId: "stripeWebhooks",
|
|
94
|
+
t
|
|
95
|
+
});
|
|
96
|
+
return path === "/stripe/rest" ? restOperation : void 0;
|
|
97
|
+
}) }];
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
export { stripe, stripeWebhookOperation };
|
|
101
|
+
|
|
102
|
+
//# sourceMappingURL=stripe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stripe.js","names":[],"sources":["../../../../src/server/spec/plugins/stripe.ts"],"sourcesContent":["import type { OperationObject, SchemaObject } from '@scalar/openapi-types/3.2'\n\nimport { jsonBody, jsonResponse } from '@/server/spec/components.js'\nimport { securedRequirement } from '@/server/spec/security.js'\nimport type { Translate } from '@/shared/translations/types.js'\n\nimport { type OfficialPlugin, messageErrors, mountEndpoints } from './shared.js'\n\nconst TAG = 'Stripe'\n\nexport const stripeWebhookOperation = ({\n tag,\n operationId,\n t,\n}: {\n tag: string\n operationId: string\n t: Translate\n}): OperationObject => {\n const received: SchemaObject = {\n type: 'object',\n properties: { received: { type: 'boolean' } },\n required: ['received'],\n }\n return {\n tags: [tag],\n operationId,\n summary: t('stripeWebhook'),\n parameters: [{ name: 'stripe-signature', in: 'header', schema: { type: 'string' } }],\n requestBody: {\n ...jsonBody({ type: 'object', additionalProperties: true }),\n description: t('stripeWebhookBody'),\n },\n responses: {\n '200': jsonResponse(t('stripeWebhookResult'), received),\n '400': jsonResponse(t('errorPluginStripeWebhook400'), received),\n },\n }\n}\n\nexport const stripe: OfficialPlugin = {\n slug: '@payloadcms/plugin-stripe',\n tag: TAG,\n build: ({ config, ctx, options, t }) => {\n const rest = options?.rest as { allowedMethods?: unknown } | undefined\n const allowed = Array.isArray(rest?.allowedMethods) ? (rest.allowedMethods as string[]) : undefined\n const restOperation: OperationObject = {\n tags: [TAG],\n operationId: 'stripeRest',\n summary: t('stripeRest'),\n requestBody: jsonBody({\n type: 'object',\n properties: {\n stripeMethod: { type: 'string', ...(allowed ? { enum: [...new Set(allowed)] } : {}) },\n stripeArgs: { type: 'array', items: {} },\n },\n required: ['stripeMethod', 'stripeArgs'],\n }),\n responses: {\n '200': jsonResponse(t('stripeRestResult'), {\n type: 'object',\n properties: { data: {}, status: { type: 'integer', enum: [200] } },\n required: ['data', 'status'],\n }),\n ...messageErrors(['400', '401', '403'], t),\n '404': jsonResponse(t('errorPluginStripeRest404'), {\n type: 'object',\n properties: { message: { type: 'string' }, status: { type: 'integer', enum: [404] } },\n required: ['message', 'status'],\n }),\n ...messageErrors(['500'], t),\n },\n security: securedRequirement(),\n }\n return [\n {\n paths: mountEndpoints(config.endpoints, ctx.apiRoute, ({ method, path }) => {\n if (method !== 'post') return undefined\n if (path === '/stripe/webhooks') return stripeWebhookOperation({ tag: TAG, operationId: 'stripeWebhooks', t })\n return path === '/stripe/rest' ? restOperation : undefined\n }),\n },\n ]\n },\n}\n"],"mappings":";;;AAQA,MAAM,MAAM;AAEZ,MAAa,0BAA0B,EACrC,KACA,aACA,QAKqB;CACrB,MAAM,WAAyB;EAC7B,MAAM;EACN,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,EAAE;EAC5C,UAAU,CAAC,UAAU;CACvB;CACA,OAAO;EACL,MAAM,CAAC,GAAG;EACV;EACA,SAAS,EAAE,eAAe;EAC1B,YAAY,CAAC;GAAE,MAAM;GAAoB,IAAI;GAAU,QAAQ,EAAE,MAAM,SAAS;EAAE,CAAC;EACnF,aAAa;GACX,GAAG,SAAS;IAAE,MAAM;IAAU,sBAAsB;GAAK,CAAC;GAC1D,aAAa,EAAE,mBAAmB;EACpC;EACA,WAAW;GACT,OAAO,aAAa,EAAE,qBAAqB,GAAG,QAAQ;GACtD,OAAO,aAAa,EAAE,6BAA6B,GAAG,QAAQ;EAChE;CACF;AACF;AAEA,MAAa,SAAyB;CACpC,MAAM;CACN,KAAK;CACL,QAAQ,EAAE,QAAQ,KAAK,SAAS,QAAQ;EACtC,MAAM,OAAO,SAAS;EACtB,MAAM,UAAU,MAAM,QAAQ,MAAM,cAAc,IAAK,KAAK,iBAA8B,KAAA;EAC1F,MAAM,gBAAiC;GACrC,MAAM,CAAC,GAAG;GACV,aAAa;GACb,SAAS,EAAE,YAAY;GACvB,aAAa,SAAS;IACpB,MAAM;IACN,YAAY;KACV,cAAc;MAAE,MAAM;MAAU,GAAI,UAAU,EAAE,MAAM,CAAC,GAAG,IAAI,IAAI,OAAO,CAAC,EAAE,IAAI,CAAC;KAAG;KACpF,YAAY;MAAE,MAAM;MAAS,OAAO,CAAC;KAAE;IACzC;IACA,UAAU,CAAC,gBAAgB,YAAY;GACzC,CAAC;GACD,WAAW;IACT,OAAO,aAAa,EAAE,kBAAkB,GAAG;KACzC,MAAM;KACN,YAAY;MAAE,MAAM,CAAC;MAAG,QAAQ;OAAE,MAAM;OAAW,MAAM,CAAC,GAAG;MAAE;KAAE;KACjE,UAAU,CAAC,QAAQ,QAAQ;IAC7B,CAAC;IACD,GAAG,cAAc;KAAC;KAAO;KAAO;IAAK,GAAG,CAAC;IACzC,OAAO,aAAa,EAAE,0BAA0B,GAAG;KACjD,MAAM;KACN,YAAY;MAAE,SAAS,EAAE,MAAM,SAAS;MAAG,QAAQ;OAAE,MAAM;OAAW,MAAM,CAAC,GAAG;MAAE;KAAE;KACpF,UAAU,CAAC,WAAW,QAAQ;IAChC,CAAC;IACD,GAAG,cAAc,CAAC,KAAK,GAAG,CAAC;GAC7B;GACA,UAAU,mBAAmB;EAC/B;EACA,OAAO,CACL,EACE,OAAO,eAAe,OAAO,WAAW,IAAI,WAAW,EAAE,QAAQ,WAAW;GAC1E,IAAI,WAAW,QAAQ,OAAO,KAAA;GAC9B,IAAI,SAAS,oBAAoB,OAAO,uBAAuB;IAAE,KAAK;IAAK,aAAa;IAAkB;GAAE,CAAC;GAC7G,OAAO,SAAS,iBAAiB,gBAAgB,KAAA;EACnD,CAAC,EACH,CACF;CACF;AACF"}
|
|
@@ -23,10 +23,11 @@ export declare const resolveEntitySecurity: ({ entity, operations, locale, baseA
|
|
|
23
23
|
locale?: string;
|
|
24
24
|
baseAccess?: SanitizedConfig['baseAccess'];
|
|
25
25
|
}) => Promise<EntitySecurity>;
|
|
26
|
-
export declare const applySecurity: ({ paths, slug, kind, security, login }: {
|
|
26
|
+
export declare const applySecurity: ({ paths, slug, kind, plugin, security, login }: {
|
|
27
27
|
paths: PathsObject;
|
|
28
28
|
slug?: string;
|
|
29
29
|
kind: OperationKind;
|
|
30
|
+
plugin?: string;
|
|
30
31
|
security?: SecurityOption;
|
|
31
32
|
login?: boolean;
|
|
32
33
|
}) => Promise<PathsObject>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isPlainObject } from "../../shared/utils.js";
|
|
2
2
|
import { INTERACTIVE_SCHEME_NAME, SECURITY_SCHEME_NAME } from "./components.js";
|
|
3
3
|
import { PLUGIN_NAME } from "../../shared/constants.js";
|
|
4
|
+
import { operationPlugin } from "./filters.js";
|
|
4
5
|
const HTTP_METHODS = [
|
|
5
6
|
"get",
|
|
6
7
|
"post",
|
|
@@ -68,7 +69,7 @@ const resolveEntitySecurity = async ({ entity, operations, locale, baseAccess })
|
|
|
68
69
|
return out;
|
|
69
70
|
};
|
|
70
71
|
const withLogin = (security) => security.some((r) => "PayloadToken" in r) && !security.some((r) => "PayloadLogin" in r) ? [...security, { [INTERACTIVE_SCHEME_NAME]: [] }] : security;
|
|
71
|
-
const applySecurity = async ({ paths, slug, kind, security, login = false }) => {
|
|
72
|
+
const applySecurity = async ({ paths, slug, kind, plugin, security, login = false }) => {
|
|
72
73
|
if (!security && !login) return paths;
|
|
73
74
|
for (const [path, item] of Object.entries(paths)) {
|
|
74
75
|
if (!item) continue;
|
|
@@ -78,12 +79,14 @@ const applySecurity = async ({ paths, slug, kind, security, login = false }) =>
|
|
|
78
79
|
if (!op) continue;
|
|
79
80
|
if (login && op.security) op.security = withLogin(op.security);
|
|
80
81
|
if (!security) continue;
|
|
82
|
+
const detected = op.security?.length ? "secured" : "public";
|
|
81
83
|
const decision = await security({
|
|
82
84
|
method,
|
|
83
85
|
path,
|
|
84
86
|
slug,
|
|
85
87
|
kind,
|
|
86
|
-
|
|
88
|
+
plugin: operationPlugin(op, plugin),
|
|
89
|
+
detected
|
|
87
90
|
});
|
|
88
91
|
if (decision === "public") delete op.security;
|
|
89
92
|
else if (decision === "secured") op.security = securedRequirement(login);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security.js","names":[],"sources":["../../../src/server/spec/security.ts"],"sourcesContent":["import type { PathsObject, SecurityRequirementObject } from '@scalar/openapi-types/3.2'\nimport type { Access, SanitizedConfig } from 'payload'\n\nimport { PLUGIN_NAME } from '@/shared/constants.js'\nimport type {\n Entity,\n EntityOperation,\n EntitySecurityOverride,\n HttpMethod,\n OperationKind,\n SecurityOption,\n} from '@/shared/types/index.js'\nimport { isPlainObject } from '@/shared/utils.js'\n\nimport { INTERACTIVE_SCHEME_NAME, SECURITY_SCHEME_NAME } from './components.js'\n\nconst HTTP_METHODS: HttpMethod[] = ['get', 'post', 'patch', 'put', 'delete']\n\nexport const securedRequirement = (login = false): SecurityRequirementObject[] =>\n login ? [{ [SECURITY_SCHEME_NAME]: [] }, { [INTERACTIVE_SCHEME_NAME]: [] }] : [{ [SECURITY_SCHEME_NAME]: [] }]\n\ntype ProbedOperation = EntityOperation | 'readVersions'\n\nconst OVERRIDE_FALLBACK: Partial<Record<ProbedOperation, EntityOperation>> = {\n readVersions: 'read',\n validate: 'update',\n}\n\nexport type EntitySecurity = Partial<Record<ProbedOperation, SecurityRequirementObject[] | undefined>>\n\nconst withTimeout = async (promise: Promise<unknown>, timeoutMs: number): Promise<boolean> => {\n let timer: ReturnType<typeof setTimeout> | undefined\n const guard = new Promise<false>((resolve) => {\n timer = setTimeout(() => resolve(false), timeoutMs)\n })\n try {\n return await Promise.race([\n promise.then(\n (value) => value === true,\n () => false,\n ),\n guard,\n ])\n } finally {\n if (timer) clearTimeout(timer)\n }\n}\n\nexport const evaluateAccess = async (\n access: Access | undefined,\n opts: { locale?: string; timeoutMs?: number; baseAccess?: SanitizedConfig['baseAccess'] } = {},\n): Promise<boolean> => {\n if (!access) return false\n const { locale, timeoutMs = 250, baseAccess } = opts\n const trap = <T extends object>(allowed: PropertyKey, value: unknown): T =>\n new Proxy({} as T, {\n get(_target, key) {\n if (key === allowed) return value\n throw new Error('req.payload accessed during build')\n },\n })\n const payloadTrap = trap('config', trap('baseAccess', baseAccess))\n const req = { user: null, headers: new Headers(), context: {}, locale, payload: payloadTrap }\n try {\n const result = access({ req } as never)\n if (result && typeof (result as Promise<unknown>).then === 'function') {\n return await withTimeout(result as Promise<unknown>, timeoutMs)\n }\n return result === true\n } catch {\n return false\n }\n}\n\nexport const readSecurityOverride = (entity: {\n slug: string\n custom?: { openapi?: { security?: unknown } }\n}): EntitySecurityOverride | undefined => {\n const meta = entity.custom?.openapi?.security\n const values = isPlainObject(meta) ? Object.values(meta) : [meta]\n if (values.some((value) => typeof value === 'boolean')) {\n throw new Error(\n `[${PLUGIN_NAME}] custom.openapi.security true/false on \"${entity.slug}\" was removed, use 'public' or 'secured'`,\n )\n }\n return meta === undefined ? undefined : (meta as EntitySecurityOverride)\n}\n\nconst overrideFor = (override: EntitySecurityOverride | undefined, op: ProbedOperation): boolean | undefined => {\n const fallback = OVERRIDE_FALLBACK[op]\n const marking =\n typeof override === 'string'\n ? override\n : (override?.[op as EntityOperation] ?? (fallback ? override?.[fallback] : undefined))\n return marking === undefined ? undefined : marking === 'public'\n}\n\nexport const resolveEntitySecurity = async ({\n entity,\n operations,\n locale,\n baseAccess,\n}: {\n entity: Entity\n operations: ProbedOperation[]\n locale?: string\n baseAccess?: SanitizedConfig['baseAccess']\n}): Promise<EntitySecurity> => {\n const override = readSecurityOverride(entity)\n const access = entity.access as Partial<Record<ProbedOperation, Access>> | undefined\n const out: EntitySecurity = {}\n for (const op of operations) {\n const declared = overrideFor(override, op)\n const isPublic = declared ?? (await evaluateAccess(access?.[op], { locale, baseAccess }))\n out[op] = isPublic ? undefined : securedRequirement()\n }\n return out\n}\n\nconst withLogin = (security: SecurityRequirementObject[]): SecurityRequirementObject[] =>\n security.some((r) => SECURITY_SCHEME_NAME in r) && !security.some((r) => INTERACTIVE_SCHEME_NAME in r)\n ? [...security, { [INTERACTIVE_SCHEME_NAME]: [] }]\n : security\n\nexport const applySecurity = async ({\n paths,\n slug,\n kind,\n security,\n login = false,\n}: {\n paths: PathsObject\n slug?: string\n kind: OperationKind\n security?: SecurityOption\n login?: boolean\n}): Promise<PathsObject> => {\n if (!security && !login) return paths\n for (const [path, item] of Object.entries(paths)) {\n if (!item) continue\n const ops = item as Record<HttpMethod, { security?: SecurityRequirementObject[] } | undefined>\n for (const method of HTTP_METHODS) {\n const op = ops[method]\n if (!op) continue\n if (login && op.security) op.security = withLogin(op.security)\n if (!security) continue\n const detected = op.security?.length ? 'secured' : 'public'\n const decision = await security({ method, path, slug, kind, detected })\n if (decision === 'public') delete op.security\n else if (decision === 'secured') op.security = securedRequirement(login)\n else if (Array.isArray(decision)) op.security = decision\n }\n }\n return paths\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"security.js","names":[],"sources":["../../../src/server/spec/security.ts"],"sourcesContent":["import type { PathsObject, SecurityRequirementObject } from '@scalar/openapi-types/3.2'\nimport type { Access, SanitizedConfig } from 'payload'\n\nimport { PLUGIN_NAME } from '@/shared/constants.js'\nimport type {\n Entity,\n EntityOperation,\n EntitySecurityOverride,\n HttpMethod,\n OperationKind,\n SecurityOption,\n} from '@/shared/types/index.js'\nimport { isPlainObject } from '@/shared/utils.js'\n\nimport { INTERACTIVE_SCHEME_NAME, SECURITY_SCHEME_NAME } from './components.js'\nimport { operationPlugin } from './filters.js'\n\nconst HTTP_METHODS: HttpMethod[] = ['get', 'post', 'patch', 'put', 'delete']\n\nexport const securedRequirement = (login = false): SecurityRequirementObject[] =>\n login ? [{ [SECURITY_SCHEME_NAME]: [] }, { [INTERACTIVE_SCHEME_NAME]: [] }] : [{ [SECURITY_SCHEME_NAME]: [] }]\n\ntype ProbedOperation = EntityOperation | 'readVersions'\n\nconst OVERRIDE_FALLBACK: Partial<Record<ProbedOperation, EntityOperation>> = {\n readVersions: 'read',\n validate: 'update',\n}\n\nexport type EntitySecurity = Partial<Record<ProbedOperation, SecurityRequirementObject[] | undefined>>\n\nconst withTimeout = async (promise: Promise<unknown>, timeoutMs: number): Promise<boolean> => {\n let timer: ReturnType<typeof setTimeout> | undefined\n const guard = new Promise<false>((resolve) => {\n timer = setTimeout(() => resolve(false), timeoutMs)\n })\n try {\n return await Promise.race([\n promise.then(\n (value) => value === true,\n () => false,\n ),\n guard,\n ])\n } finally {\n if (timer) clearTimeout(timer)\n }\n}\n\nexport const evaluateAccess = async (\n access: Access | undefined,\n opts: { locale?: string; timeoutMs?: number; baseAccess?: SanitizedConfig['baseAccess'] } = {},\n): Promise<boolean> => {\n if (!access) return false\n const { locale, timeoutMs = 250, baseAccess } = opts\n const trap = <T extends object>(allowed: PropertyKey, value: unknown): T =>\n new Proxy({} as T, {\n get(_target, key) {\n if (key === allowed) return value\n throw new Error('req.payload accessed during build')\n },\n })\n const payloadTrap = trap('config', trap('baseAccess', baseAccess))\n const req = { user: null, headers: new Headers(), context: {}, locale, payload: payloadTrap }\n try {\n const result = access({ req } as never)\n if (result && typeof (result as Promise<unknown>).then === 'function') {\n return await withTimeout(result as Promise<unknown>, timeoutMs)\n }\n return result === true\n } catch {\n return false\n }\n}\n\nexport const readSecurityOverride = (entity: {\n slug: string\n custom?: { openapi?: { security?: unknown } }\n}): EntitySecurityOverride | undefined => {\n const meta = entity.custom?.openapi?.security\n const values = isPlainObject(meta) ? Object.values(meta) : [meta]\n if (values.some((value) => typeof value === 'boolean')) {\n throw new Error(\n `[${PLUGIN_NAME}] custom.openapi.security true/false on \"${entity.slug}\" was removed, use 'public' or 'secured'`,\n )\n }\n return meta === undefined ? undefined : (meta as EntitySecurityOverride)\n}\n\nconst overrideFor = (override: EntitySecurityOverride | undefined, op: ProbedOperation): boolean | undefined => {\n const fallback = OVERRIDE_FALLBACK[op]\n const marking =\n typeof override === 'string'\n ? override\n : (override?.[op as EntityOperation] ?? (fallback ? override?.[fallback] : undefined))\n return marking === undefined ? undefined : marking === 'public'\n}\n\nexport const resolveEntitySecurity = async ({\n entity,\n operations,\n locale,\n baseAccess,\n}: {\n entity: Entity\n operations: ProbedOperation[]\n locale?: string\n baseAccess?: SanitizedConfig['baseAccess']\n}): Promise<EntitySecurity> => {\n const override = readSecurityOverride(entity)\n const access = entity.access as Partial<Record<ProbedOperation, Access>> | undefined\n const out: EntitySecurity = {}\n for (const op of operations) {\n const declared = overrideFor(override, op)\n const isPublic = declared ?? (await evaluateAccess(access?.[op], { locale, baseAccess }))\n out[op] = isPublic ? undefined : securedRequirement()\n }\n return out\n}\n\nconst withLogin = (security: SecurityRequirementObject[]): SecurityRequirementObject[] =>\n security.some((r) => SECURITY_SCHEME_NAME in r) && !security.some((r) => INTERACTIVE_SCHEME_NAME in r)\n ? [...security, { [INTERACTIVE_SCHEME_NAME]: [] }]\n : security\n\nexport const applySecurity = async ({\n paths,\n slug,\n kind,\n plugin,\n security,\n login = false,\n}: {\n paths: PathsObject\n slug?: string\n kind: OperationKind\n plugin?: string\n security?: SecurityOption\n login?: boolean\n}): Promise<PathsObject> => {\n if (!security && !login) return paths\n for (const [path, item] of Object.entries(paths)) {\n if (!item) continue\n const ops = item as Record<HttpMethod, { security?: SecurityRequirementObject[] } | undefined>\n for (const method of HTTP_METHODS) {\n const op = ops[method]\n if (!op) continue\n if (login && op.security) op.security = withLogin(op.security)\n if (!security) continue\n const detected = op.security?.length ? 'secured' : 'public'\n const decision = await security({ method, path, slug, kind, plugin: operationPlugin(op, plugin), detected })\n if (decision === 'public') delete op.security\n else if (decision === 'secured') op.security = securedRequirement(login)\n else if (Array.isArray(decision)) op.security = decision\n }\n }\n return paths\n}\n"],"mappings":";;;;AAiBA,MAAM,eAA6B;CAAC;CAAO;CAAQ;CAAS;CAAO;AAAQ;AAE3E,MAAa,sBAAsB,QAAQ,UACzC,QAAQ,CAAC,GAAG,uBAAuB,CAAC,EAAE,GAAG,GAAG,0BAA0B,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,uBAAuB,CAAC,EAAE,CAAC;AAI/G,MAAM,oBAAuE;CAC3E,cAAc;CACd,UAAU;AACZ;AAIA,MAAM,cAAc,OAAO,SAA2B,cAAwC;CAC5F,IAAI;CACJ,MAAM,QAAQ,IAAI,SAAgB,YAAY;EAC5C,QAAQ,iBAAiB,QAAQ,KAAK,GAAG,SAAS;CACpD,CAAC;CACD,IAAI;EACF,OAAO,MAAM,QAAQ,KAAK,CACxB,QAAQ,MACL,UAAU,UAAU,YACf,KACR,GACA,KACF,CAAC;CACH,UAAU;EACR,IAAI,OAAO,aAAa,KAAK;CAC/B;AACF;AAEA,MAAa,iBAAiB,OAC5B,QACA,OAA4F,CAAC,MACxE;CACrB,IAAI,CAAC,QAAQ,OAAO;CACpB,MAAM,EAAE,QAAQ,YAAY,KAAK,eAAe;CAChD,MAAM,QAA0B,SAAsB,UACpD,IAAI,MAAM,CAAC,GAAQ,EACjB,IAAI,SAAS,KAAK;EAChB,IAAI,QAAQ,SAAS,OAAO;EAC5B,MAAM,IAAI,MAAM,mCAAmC;CACrD,EACF,CAAC;CACH,MAAM,cAAc,KAAK,UAAU,KAAK,cAAc,UAAU,CAAC;CACjE,MAAM,MAAM;EAAE,MAAM;EAAM,SAAS,IAAI,QAAQ;EAAG,SAAS,CAAC;EAAG;EAAQ,SAAS;CAAY;CAC5F,IAAI;EACF,MAAM,SAAS,OAAO,EAAE,IAAI,CAAU;EACtC,IAAI,UAAU,OAAQ,OAA4B,SAAS,YACzD,OAAO,MAAM,YAAY,QAA4B,SAAS;EAEhE,OAAO,WAAW;CACpB,QAAQ;EACN,OAAO;CACT;AACF;AAEA,MAAa,wBAAwB,WAGK;CACxC,MAAM,OAAO,OAAO,QAAQ,SAAS;CAErC,KADe,cAAc,IAAI,IAAI,OAAO,OAAO,IAAI,IAAI,CAAC,IAAI,EAAA,CACrD,MAAM,UAAU,OAAO,UAAU,SAAS,GACnD,MAAM,IAAI,MACR,IAAI,YAAY,2CAA2C,OAAO,KAAK,yCACzE;CAEF,OAAO,SAAS,KAAA,IAAY,KAAA,IAAa;AAC3C;AAEA,MAAM,eAAe,UAA8C,OAA6C;CAC9G,MAAM,WAAW,kBAAkB;CACnC,MAAM,UACJ,OAAO,aAAa,WAChB,WACC,WAAW,QAA2B,WAAW,WAAW,YAAY,KAAA;CAC/E,OAAO,YAAY,KAAA,IAAY,KAAA,IAAY,YAAY;AACzD;AAEA,MAAa,wBAAwB,OAAO,EAC1C,QACA,YACA,QACA,iBAM6B;CAC7B,MAAM,WAAW,qBAAqB,MAAM;CAC5C,MAAM,SAAS,OAAO;CACtB,MAAM,MAAsB,CAAC;CAC7B,KAAK,MAAM,MAAM,YAGf,IAAI,MAFa,YAAY,UAAU,EACf,KAAM,MAAM,eAAe,SAAS,KAAK;EAAE;EAAQ;CAAW,CAAC,IAClE,KAAA,IAAY,mBAAmB;CAEtD,OAAO;AACT;AAEA,MAAM,aAAa,aACjB,SAAS,MAAM,MAAA,kBAA8B,CAAC,KAAK,CAAC,SAAS,MAAM,MAAA,kBAAiC,CAAC,IACjG,CAAC,GAAG,UAAU,GAAG,0BAA0B,CAAC,EAAE,CAAC,IAC/C;AAEN,MAAa,gBAAgB,OAAO,EAClC,OACA,MACA,MACA,QACA,UACA,QAAQ,YAQkB;CAC1B,IAAI,CAAC,YAAY,CAAC,OAAO,OAAO;CAChC,KAAK,MAAM,CAAC,MAAM,SAAS,OAAO,QAAQ,KAAK,GAAG;EAChD,IAAI,CAAC,MAAM;EACX,MAAM,MAAM;EACZ,KAAK,MAAM,UAAU,cAAc;GACjC,MAAM,KAAK,IAAI;GACf,IAAI,CAAC,IAAI;GACT,IAAI,SAAS,GAAG,UAAU,GAAG,WAAW,UAAU,GAAG,QAAQ;GAC7D,IAAI,CAAC,UAAU;GACf,MAAM,WAAW,GAAG,UAAU,SAAS,YAAY;GACnD,MAAM,WAAW,MAAM,SAAS;IAAE;IAAQ;IAAM;IAAM;IAAM,QAAQ,gBAAgB,IAAI,MAAM;IAAG;GAAS,CAAC;GAC3G,IAAI,aAAa,UAAU,OAAO,GAAG;QAChC,IAAI,aAAa,WAAW,GAAG,WAAW,mBAAmB,KAAK;QAClE,IAAI,MAAM,QAAQ,QAAQ,GAAG,GAAG,WAAW;EAClD;CACF;CACA,OAAO;AACT"}
|
|
@@ -3,12 +3,14 @@ import { TagObject } from "@scalar/openapi-types/3.2";
|
|
|
3
3
|
export declare const NAV_COLLECTIONS = "Collections";
|
|
4
4
|
export declare const NAV_GLOBALS = "Globals";
|
|
5
5
|
export declare const NAV_SYSTEM = "System";
|
|
6
|
+
export declare const NAV_PLUGINS = "Plugins";
|
|
6
7
|
export declare const entityTagName: (base: string) => string;
|
|
7
8
|
export declare const authTagName: (base: string) => string;
|
|
8
9
|
export declare const versionsTagName: (base: string) => string;
|
|
9
10
|
declare const SYSTEM_TAG_SUMMARIES: {
|
|
10
11
|
readonly Jobs: 'tagJobs';
|
|
11
12
|
readonly Uploads: 'tagUploads';
|
|
13
|
+
readonly Reorder: 'tagReorder';
|
|
12
14
|
readonly Access: 'tagAccess';
|
|
13
15
|
};
|
|
14
16
|
export type SystemTag = keyof typeof SYSTEM_TAG_SUMMARIES;
|
|
@@ -27,8 +29,9 @@ export interface TagHierarchyInput {
|
|
|
27
29
|
collections: CollectionTagInfo[];
|
|
28
30
|
globals: GlobalTagInfo[];
|
|
29
31
|
systemTags: SystemTag[];
|
|
32
|
+
pluginTags?: string[];
|
|
30
33
|
t: Translate;
|
|
31
34
|
nested: boolean;
|
|
32
35
|
}
|
|
33
|
-
export declare const buildTagHierarchy: ({ collections, globals, systemTags, t, nested }: TagHierarchyInput) => TagObject[];
|
|
36
|
+
export declare const buildTagHierarchy: ({ collections, globals, systemTags, pluginTags, t, nested }: TagHierarchyInput) => TagObject[];
|
|
34
37
|
//# sourceMappingURL=tags.d.ts.map
|
package/dist/server/spec/tags.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
const NAV_COLLECTIONS = "Collections";
|
|
2
2
|
const NAV_GLOBALS = "Globals";
|
|
3
3
|
const NAV_SYSTEM = "System";
|
|
4
|
+
const NAV_PLUGINS = "Plugins";
|
|
4
5
|
const entityTagName = (base) => base;
|
|
5
6
|
const authTagName = (base) => `${base} Auth`;
|
|
6
7
|
const versionsTagName = (base) => `${base} Versions`;
|
|
7
8
|
const SYSTEM_TAG_SUMMARIES = {
|
|
8
9
|
Jobs: "tagJobs",
|
|
9
10
|
Uploads: "tagUploads",
|
|
11
|
+
Reorder: "tagReorder",
|
|
10
12
|
Access: "tagAccess"
|
|
11
13
|
};
|
|
12
|
-
const buildTagHierarchy = ({ collections, globals, systemTags, t, nested }) => {
|
|
14
|
+
const buildTagHierarchy = ({ collections, globals, systemTags, pluginTags = [], t, nested }) => {
|
|
13
15
|
if (!nested) return [...collections.map((c) => ({
|
|
14
16
|
name: entityTagName(c.base),
|
|
15
17
|
description: c.description
|
|
@@ -76,8 +78,22 @@ const buildTagHierarchy = ({ collections, globals, systemTags, t, nested }) => {
|
|
|
76
78
|
summary: t(SYSTEM_TAG_SUMMARIES[name])
|
|
77
79
|
});
|
|
78
80
|
}
|
|
81
|
+
const taken = new Set(tags.map((tag) => tag.name));
|
|
82
|
+
const freePluginTags = pluginTags.filter((name) => !taken.has(name));
|
|
83
|
+
if (freePluginTags.length > 0) {
|
|
84
|
+
tags.push({
|
|
85
|
+
name: NAV_PLUGINS,
|
|
86
|
+
summary: t("tagPlugins"),
|
|
87
|
+
description: t("tagPluginsDesc"),
|
|
88
|
+
kind: "nav"
|
|
89
|
+
});
|
|
90
|
+
for (const name of freePluginTags) tags.push({
|
|
91
|
+
name,
|
|
92
|
+
parent: NAV_PLUGINS
|
|
93
|
+
});
|
|
94
|
+
}
|
|
79
95
|
return tags;
|
|
80
96
|
};
|
|
81
|
-
export { NAV_COLLECTIONS, NAV_GLOBALS, NAV_SYSTEM, authTagName, buildTagHierarchy, entityTagName, versionsTagName };
|
|
97
|
+
export { NAV_COLLECTIONS, NAV_GLOBALS, NAV_PLUGINS, NAV_SYSTEM, authTagName, buildTagHierarchy, entityTagName, versionsTagName };
|
|
82
98
|
|
|
83
99
|
//# sourceMappingURL=tags.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tags.js","names":[],"sources":["../../../src/server/spec/tags.ts"],"sourcesContent":["import type { TagObject } from '@scalar/openapi-types/3.2'\n\nimport type { Translate } from '@/shared/translations/types.js'\n\nexport const NAV_COLLECTIONS = 'Collections'\nexport const NAV_GLOBALS = 'Globals'\nexport const NAV_SYSTEM = 'System'\n\nexport const entityTagName = (base: string): string => base\nexport const authTagName = (base: string): string => `${base} Auth`\nexport const versionsTagName = (base: string): string => `${base} Versions`\n\nconst SYSTEM_TAG_SUMMARIES = {
|
|
1
|
+
{"version":3,"file":"tags.js","names":[],"sources":["../../../src/server/spec/tags.ts"],"sourcesContent":["import type { TagObject } from '@scalar/openapi-types/3.2'\n\nimport type { Translate } from '@/shared/translations/types.js'\n\nexport const NAV_COLLECTIONS = 'Collections'\nexport const NAV_GLOBALS = 'Globals'\nexport const NAV_SYSTEM = 'System'\nexport const NAV_PLUGINS = 'Plugins'\n\nexport const entityTagName = (base: string): string => base\nexport const authTagName = (base: string): string => `${base} Auth`\nexport const versionsTagName = (base: string): string => `${base} Versions`\n\nconst SYSTEM_TAG_SUMMARIES = {\n Jobs: 'tagJobs',\n Uploads: 'tagUploads',\n Reorder: 'tagReorder',\n Access: 'tagAccess',\n} as const\n\nexport type SystemTag = keyof typeof SYSTEM_TAG_SUMMARIES\n\nexport interface CollectionTagInfo {\n base: string\n hasAuth: boolean\n hasVersions: boolean\n description?: string\n}\n\nexport interface GlobalTagInfo {\n base: string\n hasVersions: boolean\n description?: string\n}\n\nexport interface TagHierarchyInput {\n collections: CollectionTagInfo[]\n globals: GlobalTagInfo[]\n systemTags: SystemTag[]\n pluginTags?: string[]\n t: Translate\n nested: boolean\n}\n\nexport const buildTagHierarchy = ({\n collections,\n globals,\n systemTags,\n pluginTags = [],\n t,\n nested,\n}: TagHierarchyInput): TagObject[] => {\n if (!nested) {\n return [\n ...collections.map((c) => ({ name: entityTagName(c.base), description: c.description })),\n ...globals.map((g) => ({ name: entityTagName(g.base), description: g.description })),\n ]\n }\n\n const tags: TagObject[] = []\n\n if (collections.length > 0) {\n tags.push({\n name: NAV_COLLECTIONS,\n summary: t('tagCollections'),\n description: t('tagCollectionsDesc'),\n kind: 'nav',\n })\n for (const c of collections) {\n tags.push({ name: entityTagName(c.base), parent: NAV_COLLECTIONS, description: c.description })\n if (c.hasAuth) tags.push({ name: authTagName(c.base), parent: c.base, summary: t('tagAuth') })\n if (c.hasVersions) tags.push({ name: versionsTagName(c.base), parent: c.base, summary: t('tagVersions') })\n }\n }\n\n if (globals.length > 0) {\n tags.push({ name: NAV_GLOBALS, summary: t('tagGlobals'), description: t('tagGlobalsDesc'), kind: 'nav' })\n for (const g of globals) {\n tags.push({ name: entityTagName(g.base), parent: NAV_GLOBALS, description: g.description })\n if (g.hasVersions) tags.push({ name: versionsTagName(g.base), parent: g.base, summary: t('tagVersions') })\n }\n }\n\n if (systemTags.length > 0) {\n tags.push({ name: NAV_SYSTEM, summary: t('tagSystem'), description: t('tagSystemDesc'), kind: 'nav' })\n for (const name of systemTags) tags.push({ name, parent: NAV_SYSTEM, summary: t(SYSTEM_TAG_SUMMARIES[name]) })\n }\n\n const taken = new Set(tags.map((tag) => tag.name))\n const freePluginTags = pluginTags.filter((name) => !taken.has(name))\n if (freePluginTags.length > 0) {\n tags.push({ name: NAV_PLUGINS, summary: t('tagPlugins'), description: t('tagPluginsDesc'), kind: 'nav' })\n for (const name of freePluginTags) tags.push({ name, parent: NAV_PLUGINS })\n }\n\n return tags\n}\n"],"mappings":"AAIA,MAAa,kBAAkB;AAC/B,MAAa,cAAc;AAC3B,MAAa,aAAa;AAC1B,MAAa,cAAc;AAE3B,MAAa,iBAAiB,SAAyB;AACvD,MAAa,eAAe,SAAyB,GAAG,KAAK;AAC7D,MAAa,mBAAmB,SAAyB,GAAG,KAAK;AAEjE,MAAM,uBAAuB;CAC3B,MAAM;CACN,SAAS;CACT,SAAS;CACT,QAAQ;AACV;AA0BA,MAAa,qBAAqB,EAChC,aACA,SACA,YACA,aAAa,CAAC,GACd,GACA,aACoC;CACpC,IAAI,CAAC,QACH,OAAO,CACL,GAAG,YAAY,KAAK,OAAO;EAAE,MAAM,cAAc,EAAE,IAAI;EAAG,aAAa,EAAE;CAAY,EAAE,GACvF,GAAG,QAAQ,KAAK,OAAO;EAAE,MAAM,cAAc,EAAE,IAAI;EAAG,aAAa,EAAE;CAAY,EAAE,CACrF;CAGF,MAAM,OAAoB,CAAC;CAE3B,IAAI,YAAY,SAAS,GAAG;EAC1B,KAAK,KAAK;GACR,MAAM;GACN,SAAS,EAAE,gBAAgB;GAC3B,aAAa,EAAE,oBAAoB;GACnC,MAAM;EACR,CAAC;EACD,KAAK,MAAM,KAAK,aAAa;GAC3B,KAAK,KAAK;IAAE,MAAM,cAAc,EAAE,IAAI;IAAG,QAAQ;IAAiB,aAAa,EAAE;GAAY,CAAC;GAC9F,IAAI,EAAE,SAAS,KAAK,KAAK;IAAE,MAAM,YAAY,EAAE,IAAI;IAAG,QAAQ,EAAE;IAAM,SAAS,EAAE,SAAS;GAAE,CAAC;GAC7F,IAAI,EAAE,aAAa,KAAK,KAAK;IAAE,MAAM,gBAAgB,EAAE,IAAI;IAAG,QAAQ,EAAE;IAAM,SAAS,EAAE,aAAa;GAAE,CAAC;EAC3G;CACF;CAEA,IAAI,QAAQ,SAAS,GAAG;EACtB,KAAK,KAAK;GAAE,MAAM;GAAa,SAAS,EAAE,YAAY;GAAG,aAAa,EAAE,gBAAgB;GAAG,MAAM;EAAM,CAAC;EACxG,KAAK,MAAM,KAAK,SAAS;GACvB,KAAK,KAAK;IAAE,MAAM,cAAc,EAAE,IAAI;IAAG,QAAQ;IAAa,aAAa,EAAE;GAAY,CAAC;GAC1F,IAAI,EAAE,aAAa,KAAK,KAAK;IAAE,MAAM,gBAAgB,EAAE,IAAI;IAAG,QAAQ,EAAE;IAAM,SAAS,EAAE,aAAa;GAAE,CAAC;EAC3G;CACF;CAEA,IAAI,WAAW,SAAS,GAAG;EACzB,KAAK,KAAK;GAAE,MAAM;GAAY,SAAS,EAAE,WAAW;GAAG,aAAa,EAAE,eAAe;GAAG,MAAM;EAAM,CAAC;EACrG,KAAK,MAAM,QAAQ,YAAY,KAAK,KAAK;GAAE;GAAM,QAAQ;GAAY,SAAS,EAAE,qBAAqB,KAAK;EAAE,CAAC;CAC/G;CAEA,MAAM,QAAQ,IAAI,IAAI,KAAK,KAAK,QAAQ,IAAI,IAAI,CAAC;CACjD,MAAM,iBAAiB,WAAW,QAAQ,SAAS,CAAC,MAAM,IAAI,IAAI,CAAC;CACnE,IAAI,eAAe,SAAS,GAAG;EAC7B,KAAK,KAAK;GAAE,MAAM;GAAa,SAAS,EAAE,YAAY;GAAG,aAAa,EAAE,gBAAgB;GAAG,MAAM;EAAM,CAAC;EACxG,KAAK,MAAM,QAAQ,gBAAgB,KAAK,KAAK;GAAE;GAAM,QAAQ;EAAY,CAAC;CAC5E;CAEA,OAAO;AACT"}
|
package/dist/server/ui/html.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const escapeHtml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
2
|
-
const jsonForScript = (value) => JSON.stringify(value ?? {}).replace(
|
|
2
|
+
const jsonForScript = (value) => JSON.stringify(value ?? {}).replace(/[<>&\u2028\u2029]/g, (char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
3
3
|
const htmlPage = ({ title, head, body }) => `<!doctype html><html><head><meta charset="utf-8"/><title>${escapeHtml(title)}</title><meta name="viewport" content="width=device-width, initial-scale=1"/>${head}</head><body>${body}</body></html>`;
|
|
4
4
|
const specUrlExpr = (specUrl) => {
|
|
5
|
-
const u =
|
|
5
|
+
const u = jsonForScript(specUrl);
|
|
6
6
|
return `(function(){var l=new URLSearchParams(location.search).get('lang');return ${u}+(l?((${u}.indexOf('?')>-1?'&':'?')+'lang='+encodeURIComponent(l)):'')})()`;
|
|
7
7
|
};
|
|
8
8
|
const integrityAttrs = (pinned, integrity) => pinned ? ` integrity="${integrity}" crossorigin="anonymous"` : "";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"html.js","names":[],"sources":["../../../src/server/ui/html.ts"],"sourcesContent":["export const escapeHtml = (value: string): string =>\n value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"')\n\nexport const jsonForScript = (value: unknown): string
|
|
1
|
+
{"version":3,"file":"html.js","names":[],"sources":["../../../src/server/ui/html.ts"],"sourcesContent":["export const escapeHtml = (value: string): string =>\n value.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/\"/g, '"')\n\nexport const jsonForScript = (value: unknown): string =>\n JSON.stringify(value ?? {}).replace(\n /[<>&\\u2028\\u2029]/g,\n (char) => `\\\\u${char.charCodeAt(0).toString(16).padStart(4, '0')}`,\n )\n\nexport const htmlPage = ({ title, head, body }: { title: string; head: string; body: string }): string =>\n `<!doctype html><html><head><meta charset=\"utf-8\"/><title>${escapeHtml(title)}</title>` +\n `<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/>${head}</head>` +\n `<body>${body}</body></html>`\n\nexport const specUrlExpr = (specUrl: string): string => {\n const u = jsonForScript(specUrl)\n return `(function(){var l=new URLSearchParams(location.search).get('lang');return ${u}+(l?((${u}.indexOf('?')>-1?'&':'?')+'lang='+encodeURIComponent(l)):'')})()`\n}\n\nexport const integrityAttrs = (pinned: boolean, integrity: string): string =>\n pinned ? ` integrity=\"${integrity}\" crossorigin=\"anonymous\"` : ''\n"],"mappings":"AAAA,MAAa,cAAc,UACzB,MAAM,QAAQ,MAAM,OAAO,CAAC,CAAC,QAAQ,MAAM,MAAM,CAAC,CAAC,QAAQ,MAAM,MAAM,CAAC,CAAC,QAAQ,MAAM,QAAQ;AAEjG,MAAa,iBAAiB,UAC5B,KAAK,UAAU,SAAS,CAAC,CAAC,CAAC,CAAC,QAC1B,uBACC,SAAS,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,GACjE;AAEF,MAAa,YAAY,EAAE,OAAO,MAAM,WACtC,4DAA4D,WAAW,KAAK,EAAE,+EACN,KAAK,eACpE,KAAK;AAEhB,MAAa,eAAe,YAA4B;CACtD,MAAM,IAAI,cAAc,OAAO;CAC/B,OAAO,6EAA6E,EAAE,QAAQ,EAAE;AAClG;AAEA,MAAa,kBAAkB,QAAiB,cAC9C,SAAS,eAAe,UAAU,6BAA6B"}
|
|
@@ -67,11 +67,24 @@ const ar = { "@seshuk/payload-plugin-openapi": {
|
|
|
67
67
|
uploadPayloadField: "حقول {{schema}} بصيغة JSON. مثال: `{\"alt\":\"A caption\"}`.",
|
|
68
68
|
fileServe: "الملف",
|
|
69
69
|
filePartial: "جزء من الملف، لطلب `Range`",
|
|
70
|
+
paramFileVersion: "يعرض الملف المحفوظ مع معرّف الإصدار هذا.",
|
|
70
71
|
uploadInstructionsSummary: "الحصول على تعليمات رفع ملف قبل حفظ المستند",
|
|
71
72
|
uploadInstructionsResult: "مكان إرسال بايتات الملف، وقيمة `file` التي تُرسل مع طلب الإنشاء أو التحديث",
|
|
72
73
|
uploadStagePutSummary: "إرسال بايتات الملف لرفع مؤقت",
|
|
73
74
|
uploadStageDeleteSummary: "حذف رفع مؤقت",
|
|
74
75
|
uploadStageResult: "تم، بدون محتوى",
|
|
76
|
+
paramPreferenceKey: "مفتاح التفضيل، مثل `nav`.",
|
|
77
|
+
preferenceGet: "الحصول على تفضيل للمستخدم الحالي",
|
|
78
|
+
preferenceGetResult: "التفضيل المحفوظ، أو `value: null` إذا لم يكن موجودًا",
|
|
79
|
+
preferenceSet: "حفظ تفضيل للمستخدم الحالي",
|
|
80
|
+
preferenceSetBody: "أرسل القيمة في `value`. يُحفظ الجسم الذي لا يحتوي على `value` كاملًا كقيمة.",
|
|
81
|
+
preferenceSetResult: "التفضيل المحفوظ",
|
|
82
|
+
preferenceDelete: "حذف تفضيل للمستخدم الحالي",
|
|
83
|
+
preferenceDeleteResult: "التفضيل المحذوف",
|
|
84
|
+
reorder: "نقل المستندات في قائمة قابلة للترتيب",
|
|
85
|
+
reorderBody: "ينقل `docsToMove` بعد `target` مع `newKeyWillBe: \"greater\"`، أو قبله مع `\"less\"`. قيمة `orderableFieldName` هي `_order` لمجموعة قابلة للترتيب، أو حقل الترتيب لـ join قابل للترتيب. بدون `target.key` يمنح Payload مفاتيح الترتيب فقط للمستندات التي لا تملكها.",
|
|
86
|
+
reorderResult: "مفاتيح الترتيب الجديدة، أو `message: \"initial migration\"` عندما عيّن Payload مفاتيح الترتيب لأول مرة",
|
|
87
|
+
errorReorder400: "جسم الطلب غير صالح؛ السبب في `error`",
|
|
75
88
|
error400: "خطأ في التحقق أو الاستعلام (ValidationError، QueryError)",
|
|
76
89
|
error401: "غير مُصادَق عليه (AuthenticationError)",
|
|
77
90
|
error403: "محظور بواسطة التحكم في الوصول (Forbidden، UnverifiedEmail)",
|
|
@@ -90,6 +103,67 @@ const ar = { "@seshuk/payload-plugin-openapi": {
|
|
|
90
103
|
tagJobs: "المهام",
|
|
91
104
|
tagUploads: "الرفع",
|
|
92
105
|
tagAccess: "الوصول",
|
|
106
|
+
tagReorder: "إعادة الترتيب",
|
|
107
|
+
tagPlugins: "الإضافات",
|
|
108
|
+
tagPluginsDesc: "نقاط نهاية تضيفها إضافات Payload الرسمية.",
|
|
109
|
+
errorPlugin400: "طلب غير صالح",
|
|
110
|
+
errorPlugin401: "غير مصادق",
|
|
111
|
+
errorPlugin403: "محظور",
|
|
112
|
+
errorPlugin404: "غير موجود",
|
|
113
|
+
errorPlugin500: "خطأ داخلي في الخادم",
|
|
114
|
+
ecommerceAddItem: "إضافة منتج إلى السلة",
|
|
115
|
+
ecommerceRemoveItem: "إزالة منتج من السلة",
|
|
116
|
+
ecommerceUpdateItem: "تغيير كمية منتج في السلة",
|
|
117
|
+
ecommerceClearCart: "إزالة كل المنتجات من السلة",
|
|
118
|
+
ecommerceMergeCart: "دمج سلة ضيف في هذه السلة",
|
|
119
|
+
ecommerceCartAccess: "مسموح لمالك السلة، أو لسلة ضيف مع `secret` الخاص بها في جسم الطلب.",
|
|
120
|
+
ecommerceCartResult: "السلة المحدّثة",
|
|
121
|
+
errorPluginEcommerce404: "السلة غير موجودة أو لا يمكن الوصول إليها",
|
|
122
|
+
ecommerceQuantity: "الكمية الجديدة، أو `{ \"$inc\": n }` لتغييرها بمقدار n.",
|
|
123
|
+
ecommerceInitiatePayment: "بدء دفعة عبر `{{method}}`",
|
|
124
|
+
ecommerceConfirmOrder: "تأكيد الدفع عبر `{{method}}` وإنشاء الطلب",
|
|
125
|
+
ecommercePaymentBody: "يستخدم `cartID` (مع `secret` لسلة ضيف) أو سلة المستخدم. بدون مستخدم يكون `customerEmail` مطلوبًا. قد يحتاج محوّل الدفع إلى حقول إضافية.",
|
|
126
|
+
ecommerceInitiateResult: "بدأت عملية الدفع. يضيف المحوّل حقوله الخاصة، مثل client secret.",
|
|
127
|
+
ecommerceConfirmResult: "تم إنشاء الطلب",
|
|
128
|
+
stripeWebhook: "استقبال أحداث webhook من Stripe",
|
|
129
|
+
stripeWebhookBody: "حدث Stripe الخام، موقّع في الترويسة `Stripe-Signature`. يستدعيه Stripe وليس عملاء API.",
|
|
130
|
+
stripeWebhookResult: "تم استلام الحدث",
|
|
131
|
+
errorPluginStripeWebhook400: "فشل التحقق من التوقيع",
|
|
132
|
+
stripeRest: "استدعاء دالة مسموح بها من Stripe API",
|
|
133
|
+
stripeRestResult: "نتيجة Stripe API",
|
|
134
|
+
errorPluginStripeRest404: "أعادت Stripe API خطأ",
|
|
135
|
+
mcp: "إرسال رسالة MCP JSON-RPC",
|
|
136
|
+
mcpDesc: "Model Context Protocol عبر Streamable HTTP مع ردود JSON. الطلبات المجهولة تعمل؛ الأدوات المعروضة تعتمد على صلاحيات المستخدم. يجب على العملاء الذين يستخدمون إصدار بروتوكول من 2025 إرسال `Accept: application/json, text/event-stream`.",
|
|
137
|
+
mcpResult: "رد JSON-RPC",
|
|
138
|
+
mcpOverrideAccess: "تجاوز فحوص الوصول. للتطوير فقط.",
|
|
139
|
+
mcpGet: "غير مدعوم: الخادم لا يفتح أي تدفق أحداث",
|
|
140
|
+
errorPluginMcpGet405: "الطريقة غير مسموح بها، استخدم POST",
|
|
141
|
+
mcpProtocolVersion: "إصدار بروتوكول MCP المتفق عليه، مثل `2025-06-18`.",
|
|
142
|
+
mcpResult202: "مقبول: احتوى الجسم على إشعارات أو ردود فقط",
|
|
143
|
+
errorPluginMcp404: "دالة MCP غير معروفة",
|
|
144
|
+
errorPluginMcp406: "الترويسة `Accept` لا تتضمن `application/json` أو `text/event-stream`",
|
|
145
|
+
errorPluginMcp413: "جسم الطلب كبير جدًا",
|
|
146
|
+
errorPluginMcp415: "يجب أن يكون `Content-Type` هو `application/json`",
|
|
147
|
+
seoTitle: "توليد عنوان الميتا",
|
|
148
|
+
seoDescription: "توليد وصف الميتا",
|
|
149
|
+
seoUrl: "توليد رابط المعاينة",
|
|
150
|
+
seoImage: "توليد صورة الميتا",
|
|
151
|
+
seoBody: "المستند قيد التحرير: `collectionSlug` أو `globalSlug`، و`id` الخاص به، وبيانات `doc` الحالية. يُمرَّر إلى دالة التوليد لديك.",
|
|
152
|
+
seoResult: "القيمة المولَّدة. سلسلة فارغة عند عدم تعيين دالة توليد.",
|
|
153
|
+
searchReindex: "إعادة بناء فهرس البحث لبعض المجموعات",
|
|
154
|
+
searchReindexResult: "ملخص إعادة الفهرسة",
|
|
155
|
+
tenantOptions: "عرض المستأجرين الذين يمكن للمستخدم اختيارهم",
|
|
156
|
+
tenantOptionsResult: "خيارات المستأجرين",
|
|
157
|
+
exportDownload: "تصدير المستندات إلى ملف",
|
|
158
|
+
exportDownloadResult: "ملف التصدير",
|
|
159
|
+
exportPreview: "معاينة تصدير",
|
|
160
|
+
importPreview: "معاينة ملف استيراد",
|
|
161
|
+
previewResult: "صفحة من مستندات المعاينة",
|
|
162
|
+
importFileData: "محتوى الملف بترميز base64.",
|
|
163
|
+
r2Upload: "رفع ملف إلى R2 على أجزاء",
|
|
164
|
+
r2UploadDesc: "ثلاث خطوات على مسار واحد. البدء: أرسل `collection` و`fileName` و`fileType`. كل جزء: أضف `multipartId` و`multipartKey` و`multipartNumber` و`signedReceipt` وأرسل البايتات. الإنهاء: الشيء نفسه بدون `multipartNumber`، مع قائمة JSON بالأجزاء.",
|
|
165
|
+
r2UploadResult: "بدأ الرفع، أو رُفع جزء، أو اكتمل الرفع (مفتاح الكائن كنص)",
|
|
166
|
+
errorStorageR2412: "يوجد ملف بالفعل عند هذا المفتاح",
|
|
93
167
|
localizationHeading: "الترجمة",
|
|
94
168
|
localizationNote: "اللغات المتاحة: {{locales}}. مرّر `?locale=<code>` إلى نقطة نهاية قراءة لاختيار لغة واحدة. مرّر `?locale=all` لتلقّي كل اللغات دفعة واحدة — عندئذٍ يُرجَع كل حقل مترجم ككائن مفهرس بحسب رمز اللغة (مثل `{ \"en\": \"Hello\", \"de\": \"Hallo\" }`) بدلاً من قيمة واحدة. اضبط `?flattenLocales=false` مع `locale=all` للإبقاء على شكل الكائن لكل لغة. تعرض مخططات الحقول شكل اللغة الواحدة.",
|
|
95
169
|
docLanguagesNote: "يتوفر هذا التوثيق باللغات التالية: {{languages}}. أضِف `?lang=<code>` إلى عنوان URL لهذه المواصفة للتبديل."
|