From 70f0240da506681f1289870a8e9ae97785e2eeb0 Mon Sep 17 00:00:00 2001 From: dswbx Date: Tue, 12 Aug 2025 22:13:09 +0200 Subject: [PATCH] mcp: added auth tests, updated data tests --- app/__test__/app/mcp/mcp.auth.test.ts | 221 +++- app/__test__/app/mcp/mcp.data.test.ts | 4 +- app/package.json | 2 +- app/src/auth/auth-schema.ts | 1 + app/src/auth/authenticate/Authenticator.ts | 6 +- app/src/core/test/utils.ts | 19 +- app/src/data/data-schema.ts | 16 +- app/src/modules/ModuleHelper.ts | 10 +- app/src/modules/mcp/$record.ts | 40 +- app/src/modules/mcp/McpSchemaHelper.ts | 12 +- bun.lock | 4 +- docs/mcp.json | 1337 +++++++++++++++++--- 12 files changed, 1422 insertions(+), 250 deletions(-) diff --git a/app/__test__/app/mcp/mcp.auth.test.ts b/app/__test__/app/mcp/mcp.auth.test.ts index 1ae032e..9213b90 100644 --- a/app/__test__/app/mcp/mcp.auth.test.ts +++ b/app/__test__/app/mcp/mcp.auth.test.ts @@ -1,32 +1,40 @@ -import { describe, beforeAll } from "bun:test"; -import { type App, createApp } from "core/test/utils"; +import { describe, test, expect, beforeEach, beforeAll, afterAll } from "bun:test"; +import { type App, createApp, createMcpToolCaller } from "core/test/utils"; import { getSystemMcp } from "modules/mcp/system-mcp"; +import { disableConsoleLog, enableConsoleLog } from "core/utils"; + +beforeAll(disableConsoleLog); +afterAll(enableConsoleLog); /** - * - [ ] auth_me - * - [ ] auth_strategies - * - [ ] auth_user_create - * - [ ] auth_user_token - * - [ ] auth_user_password_change - * - [ ] auth_user_password_test - * - [ ] config_auth_update - * - [ ] config_auth_strategies_get - * - [ ] config_auth_strategies_add - * - [ ] config_auth_strategies_update - * - [ ] config_auth_strategies_remove - * - [ ] config_auth_roles_get - * - [ ] config_auth_roles_add - * - [ ] config_auth_roles_update - * - [ ] config_auth_roles_remove + * - [x] auth_me + * - [x] auth_strategies + * - [x] auth_user_create + * - [x] auth_user_token + * - [x] auth_user_password_change + * - [x] auth_user_password_test + * - [x] config_auth_get + * - [x] config_auth_update + * - [x] config_auth_strategies_get + * - [x] config_auth_strategies_add + * - [x] config_auth_strategies_update + * - [x] config_auth_strategies_remove + * - [x] config_auth_roles_get + * - [x] config_auth_roles_add + * - [x] config_auth_roles_update + * - [x] config_auth_roles_remove */ describe("mcp auth", async () => { let app: App; let server: ReturnType; - beforeAll(async () => { + beforeEach(async () => { app = createApp({ initialConfig: { auth: { enabled: true, + jwt: { + secret: "secret", + }, }, server: { mcp: { @@ -37,5 +45,182 @@ describe("mcp auth", async () => { }); await app.build(); server = getSystemMcp(app); + server.setLogLevel("error"); + server.onNotification((message) => { + console.dir(message, { depth: null }); + }); + }); + + const tool = createMcpToolCaller(); + + test("auth_*", async () => { + const me = await tool(server, "auth_me", {}); + expect(me.user).toBeNull(); + + // strategies + const strategies = await tool(server, "auth_strategies", {}); + expect(Object.keys(strategies.strategies).length).toEqual(1); + expect(strategies.strategies.password.enabled).toBe(true); + + // create user + const user = await tool( + server, + "auth_user_create", + { + email: "test@test.com", + password: "12345678", + }, + new Headers(), + ); + expect(user.email).toBe("test@test.com"); + + // create token + const token = await tool( + server, + "auth_user_token", + { + email: "test@test.com", + }, + new Headers(), + ); + expect(token.token).toBeDefined(); + expect(token.user.email).toBe("test@test.com"); + + // me + const me2 = await tool( + server, + "auth_me", + {}, + new Request("http://localhost", { + headers: new Headers({ + Authorization: `Bearer ${token.token}`, + }), + }), + ); + expect(me2.user.email).toBe("test@test.com"); + + // change password + const changePassword = await tool( + server, + "auth_user_password_change", + { + email: "test@test.com", + password: "87654321", + }, + new Headers(), + ); + expect(changePassword.changed).toBe(true); + + // test password + const testPassword = await tool( + server, + "auth_user_password_test", + { + email: "test@test.com", + password: "87654321", + }, + new Headers(), + ); + expect(testPassword.valid).toBe(true); + }); + + test("config_auth_{get,update}", async () => { + expect(await tool(server, "config_auth_get", {})).toEqual({ + path: "", + secrets: false, + partial: false, + value: app.toJSON().auth, + }); + + // update + await tool(server, "config_auth_update", { + value: { + allow_register: false, + }, + }); + expect(app.toJSON().auth.allow_register).toBe(false); + }); + + test("config_auth_strategies_{get,add,update,remove}", async () => { + const strategies = await tool(server, "config_auth_strategies_get", { + key: "password", + }); + expect(strategies).toEqual({ + secrets: false, + module: "auth", + key: "password", + value: { + enabled: true, + type: "password", + }, + }); + + // add google oauth + const addGoogleOauth = await tool(server, "config_auth_strategies_add", { + key: "google", + value: { + type: "oauth", + enabled: true, + config: { + name: "google", + type: "oidc", + client: { + client_id: "client_id", + client_secret: "client_secret", + }, + }, + }, + return_config: true, + }); + expect(addGoogleOauth.config.google.enabled).toBe(true); + expect(app.toJSON().auth.strategies.google?.enabled).toBe(true); + + // update (disable) google oauth + await tool(server, "config_auth_strategies_update", { + key: "google", + value: { + enabled: false, + }, + }); + expect(app.toJSON().auth.strategies.google?.enabled).toBe(false); + + // remove google oauth + await tool(server, "config_auth_strategies_remove", { + key: "google", + }); + expect(app.toJSON().auth.strategies.google).toBeUndefined(); + }); + + test("config_auth_roles_{get,add,update,remove}", async () => { + // add role + const addGuestRole = await tool(server, "config_auth_roles_add", { + key: "guest", + value: { + permissions: ["read", "write"], + }, + return_config: true, + }); + expect(addGuestRole.config.guest.permissions).toEqual(["read", "write"]); + + // update role + await tool(server, "config_auth_roles_update", { + key: "guest", + value: { + permissions: ["read"], + }, + }); + expect(app.toJSON().auth.roles?.guest?.permissions).toEqual(["read"]); + + // get role + const getGuestRole = await tool(server, "config_auth_roles_get", { + key: "guest", + }); + expect(getGuestRole.value.permissions).toEqual(["read"]); + + // remove role + await tool(server, "config_auth_roles_remove", { + key: "guest", + }); + expect(app.toJSON().auth.roles?.guest).toBeUndefined(); }); }); diff --git a/app/__test__/app/mcp/mcp.data.test.ts b/app/__test__/app/mcp/mcp.data.test.ts index 580f729..a29c764 100644 --- a/app/__test__/app/mcp/mcp.data.test.ts +++ b/app/__test__/app/mcp/mcp.data.test.ts @@ -66,7 +66,7 @@ describe("mcp data", async () => { }); expect(result.success).toBe(true); expect(result.module).toBe("data"); - expect(result.config.entities.test.type).toEqual("regular"); + expect(result.config.test?.type).toEqual("regular"); const entities = Object.keys(app.toJSON().data.entities ?? {}); expect(entities).toContain("test"); @@ -94,7 +94,7 @@ describe("mcp data", async () => { }); expect(result.success).toBe(true); expect(result.module).toBe("data"); - expect(result.config.entities.test.config?.name).toEqual("Test"); + expect(result.config.test.config?.name).toEqual("Test"); expect(app.toJSON().data.entities?.test?.config?.name).toEqual("Test"); } diff --git a/app/package.json b/app/package.json index d4f906f..9a894ef 100644 --- a/app/package.json +++ b/app/package.json @@ -65,7 +65,7 @@ "hono": "4.8.3", "json-schema-library": "10.0.0-rc7", "json-schema-to-ts": "^3.1.1", - "jsonv-ts": "^0.7.4", + "jsonv-ts": "^0.7.5", "kysely": "0.27.6", "lodash-es": "^4.17.21", "oauth4webapi": "^2.11.1", diff --git a/app/src/auth/auth-schema.ts b/app/src/auth/auth-schema.ts index 1f21a9a..4fd40a4 100644 --- a/app/src/auth/auth-schema.ts +++ b/app/src/auth/auth-schema.ts @@ -72,6 +72,7 @@ export const authConfigSchema = $object( }, s.strictObject({ type: s.string(), + enabled: s.boolean({ default: true }).optional(), config: s.object({}), }), ), diff --git a/app/src/auth/authenticate/Authenticator.ts b/app/src/auth/authenticate/Authenticator.ts index 6c24c93..0350aba 100644 --- a/app/src/auth/authenticate/Authenticator.ts +++ b/app/src/auth/authenticate/Authenticator.ts @@ -385,7 +385,11 @@ export class Authenticator< headers = c.headers; } else { is_context = true; - headers = c.req.raw.headers; + try { + headers = c.req.raw.headers; + } catch (e) { + throw new Exception("Request/Headers/Context is required to resolve auth", 400); + } } let token: string | undefined; diff --git a/app/src/core/test/utils.ts b/app/src/core/test/utils.ts index 19eaa63..3eb4a39 100644 --- a/app/src/core/test/utils.ts +++ b/app/src/core/test/utils.ts @@ -13,15 +13,18 @@ export function createApp({ connection, ...config }: CreateAppConfig = {}) { } export function createMcpToolCaller() { - return async (server: ReturnType, name: string, args: any) => { - const res = await server.handle({ - jsonrpc: "2.0", - method: "tools/call", - params: { - name, - arguments: args, + return async (server: ReturnType, name: string, args: any, raw?: any) => { + const res = await server.handle( + { + jsonrpc: "2.0", + method: "tools/call", + params: { + name, + arguments: args, + }, }, - }); + raw, + ); if ((res.result as any)?.isError) { console.dir(res.result, { depth: null }); diff --git a/app/src/data/data-schema.ts b/app/src/data/data-schema.ts index f08a711..f416da6 100644 --- a/app/src/data/data-schema.ts +++ b/app/src/data/data-schema.ts @@ -80,9 +80,19 @@ export const dataConfigSchema = $object("config_data", { basepath: s.string({ default: "/api/data" }).optional(), default_primary_format: s.string({ enum: primaryFieldTypes, default: "integer" }).optional(), entities: $record("config_data_entities", entitiesSchema, { default: {} }).optional(), - relations: $record("config_data_relations", s.anyOf(relationsSchema), { - default: {}, - }).optional(), + relations: $record( + "config_data_relations", + s.anyOf(relationsSchema), + { + default: {}, + }, + s.strictObject({ + type: s.string({ enum: Object.keys(RelationClassMap) }), + source: s.string(), + target: s.string(), + config: s.object({}).optional(), + }), + ).optional(), indices: $record("config_data_indices", indicesSchema, { default: {}, mcp: { update: false }, diff --git a/app/src/modules/ModuleHelper.ts b/app/src/modules/ModuleHelper.ts index 842033d..60a6dfc 100644 --- a/app/src/modules/ModuleHelper.ts +++ b/app/src/modules/ModuleHelper.ts @@ -7,7 +7,7 @@ import type { ModuleBuildContext, ModuleBuildContextMcpContext } from "./Module" import type { EntityRelation } from "data/relations"; import type { Permission } from "core/security/Permission"; import { Exception } from "core/errors"; -import { invariant } from "bknd/utils"; +import { invariant, isPlainObject } from "bknd/utils"; export class ModuleHelper { constructor(protected ctx: Omit) {} @@ -119,12 +119,14 @@ export class ModuleHelper { c: { context: ModuleBuildContextMcpContext; raw?: unknown }, ) { invariant(c.context.app, "app is not available in mcp context"); - invariant(c.raw instanceof Request, "request is not available in mcp context"); - const auth = c.context.app.module.auth; if (!auth.enabled) return; - const user = await auth.authenticator?.resolveAuthFromRequest(c.raw as Request); + if (c.raw === undefined || c.raw === null) { + throw new Exception("Request/Headers/Context is not available in mcp context", 400); + } + + const user = await auth.authenticator?.resolveAuthFromRequest(c.raw as any); if (!this.ctx.guard.granted(permission, user)) { throw new Exception( diff --git a/app/src/modules/mcp/$record.ts b/app/src/modules/mcp/$record.ts index b752cfd..cbc1856 100644 --- a/app/src/modules/mcp/$record.ts +++ b/app/src/modules/mcp/$record.ts @@ -42,7 +42,7 @@ export class RecordToolSchema< } private getNewSchema(fallback: s.Schema = this.additionalProperties) { - return this[opts].new_schema ?? fallback; + return this[opts].new_schema ?? this.additionalProperties ?? fallback; } private toolGet(node: s.Node>) { @@ -122,7 +122,7 @@ export class RecordToolSchema< }), }, async (params, ctx: AppToolHandlerCtx) => { - const configs = ctx.context.app.toJSON(); + const configs = ctx.context.app.toJSON(true); const config = getPath(configs, node.instancePath); const [module_name, ...rest] = node.instancePath; @@ -134,12 +134,16 @@ export class RecordToolSchema< .mutateConfig(module_name as any) .patch([...rest, params.key], params.value); + const newConfig = getPath(ctx.context.app.toJSON(), node.instancePath); + return ctx.json({ success: true, module: module_name, - config: params.return_config - ? ctx.context.app.module[module_name as any].config - : undefined, + action: { + type: "add", + key: params.key, + }, + config: params.return_config ? newConfig : undefined, }); }, ); @@ -154,7 +158,7 @@ export class RecordToolSchema< key: s.string({ description: "key to update", }), - value: this.getNewSchema(s.object({})), + value: this.mcp.getCleanSchema(this.getNewSchema(s.object({}))), return_config: s .boolean({ default: false, @@ -164,7 +168,7 @@ export class RecordToolSchema< }), }, async (params, ctx: AppToolHandlerCtx) => { - const configs = ctx.context.app.toJSON(params.secrets); + const configs = ctx.context.app.toJSON(true); const config = getPath(configs, node.instancePath); const [module_name, ...rest] = node.instancePath; @@ -176,12 +180,16 @@ export class RecordToolSchema< .mutateConfig(module_name as any) .patch([...rest, params.key], params.value); + const newConfig = getPath(ctx.context.app.toJSON(), node.instancePath); + return ctx.json({ success: true, module: module_name, - config: params.return_config - ? ctx.context.app.module[module_name as any].config - : undefined, + action: { + type: "update", + key: params.key, + }, + config: params.return_config ? newConfig : undefined, }); }, ); @@ -205,7 +213,7 @@ export class RecordToolSchema< }), }, async (params, ctx: AppToolHandlerCtx) => { - const configs = ctx.context.app.toJSON(); + const configs = ctx.context.app.toJSON(true); const config = getPath(configs, node.instancePath); const [module_name, ...rest] = node.instancePath; @@ -217,12 +225,16 @@ export class RecordToolSchema< .mutateConfig(module_name as any) .remove([...rest, params.key].join(".")); + const newConfig = getPath(ctx.context.app.toJSON(), node.instancePath); + return ctx.json({ success: true, module: module_name, - config: params.return_config - ? ctx.context.app.module[module_name as any].config - : undefined, + action: { + type: "remove", + key: params.key, + }, + config: params.return_config ? newConfig : undefined, }); }, ); diff --git a/app/src/modules/mcp/McpSchemaHelper.ts b/app/src/modules/mcp/McpSchemaHelper.ts index a8b8b08..686e7ff 100644 --- a/app/src/modules/mcp/McpSchemaHelper.ts +++ b/app/src/modules/mcp/McpSchemaHelper.ts @@ -43,16 +43,18 @@ export class McpSchemaHelper { public name: string, public options: McpToolOptions & AdditionalOptions, ) { - this.cleanSchema = this.getCleanSchema(); + this.cleanSchema = this.getCleanSchema(this.schema as s.ObjectSchema); } - private getCleanSchema() { + getCleanSchema(schema: s.ObjectSchema) { + if (schema.type !== "object") return schema; + const props = excludePropertyTypes( - this.schema as any, + schema as any, (i) => isPlainObject(i) && mcpSchemaSymbol in i, ); - const schema = s.strictObject(props); - return rescursiveClean(schema, { + const _schema = s.strictObject(props); + return rescursiveClean(_schema, { removeRequired: true, removeDefault: false, }) as s.ObjectSchema; diff --git a/bun.lock b/bun.lock index 438e194..6152514 100644 --- a/bun.lock +++ b/bun.lock @@ -35,7 +35,7 @@ "hono": "4.8.3", "json-schema-library": "10.0.0-rc7", "json-schema-to-ts": "^3.1.1", - "jsonv-ts": "^0.7.4", + "jsonv-ts": "^0.7.5", "kysely": "0.27.6", "lodash-es": "^4.17.21", "oauth4webapi": "^2.11.1", @@ -2516,7 +2516,7 @@ "jsonpointer": ["jsonpointer@5.0.1", "", {}, "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ=="], - "jsonv-ts": ["jsonv-ts@0.7.4", "", { "optionalDependencies": { "hono": "*" }, "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-SDx7Nt1kku6mAefrMffIdA9INqJnRLDJVooQOlstDmn0SvmTEHNAPifB+S14RR3f+Lep1T+WUeUdrHADrZsnYA=="], + "jsonv-ts": ["jsonv-ts@0.7.5", "", { "optionalDependencies": { "hono": "*" }, "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-/FXLINo/mbMLVFD4zjNRFfWe5D9oBsc2H9Fy/KLgmdGdhgUo9T/xbVteGWBVQSPg+P2hPdbVgaKFWgvDPk4qVw=="], "jsonwebtoken": ["jsonwebtoken@9.0.2", "", { "dependencies": { "jws": "^3.2.2", "lodash.includes": "^4.3.0", "lodash.isboolean": "^3.0.3", "lodash.isinteger": "^4.0.4", "lodash.isnumber": "^3.0.3", "lodash.isplainobject": "^4.0.6", "lodash.isstring": "^4.0.1", "lodash.once": "^4.0.0", "ms": "^2.1.1", "semver": "^7.5.4" } }, "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ=="], diff --git a/docs/mcp.json b/docs/mcp.json index 064ebd4..e286be7 100644 --- a/docs/mcp.json +++ b/docs/mcp.json @@ -1920,7 +1920,1084 @@ }, "value": { "type": "object", - "properties": {} + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string", + "enum": [ + "regular", + "system", + "generated" + ], + "default": "regular" + }, + "config": { + "type": "object", + "default": {}, + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "name_singular": { + "type": "string" + }, + "description": { + "type": "string" + }, + "sort_field": { + "type": "string", + "default": "id" + }, + "sort_dir": { + "type": "string", + "enum": [ + "asc", + "desc" + ], + "default": "asc" + }, + "primary_format": { + "type": "string", + "enum": [ + "integer", + "uuid" + ] + } + } + }, + "fields": { + "type": "object", + "default": {}, + "additionalProperties": { + "anyOf": [ + { + "type": "object", + "title": "primary", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "name": { + "type": "string" + }, + "type": { + "const": "primary" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "format": { + "type": "string", + "enum": [ + "integer", + "uuid" + ], + "default": "integer" + }, + "required": { + "type": "boolean", + "default": false + }, + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "fillable": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete" + ] + } + } + ] + }, + "hidden": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete", + "form", + "table", + "submit" + ] + } + } + ] + }, + "virtual": { + "type": "boolean" + }, + "default_value": {} + } + } + } + }, + { + "type": "object", + "title": "text", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "name": { + "type": "string" + }, + "type": { + "const": "text" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "default_value": { + "type": "string" + }, + "minLength": { + "type": "number" + }, + "maxLength": { + "type": "number" + }, + "pattern": { + "type": "string" + }, + "html_config": { + "type": "object", + "properties": { + "element": { + "type": "string" + }, + "props": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { + "type": "string", + "title": "String" + }, + { + "type": "number", + "title": "Number" + } + ] + } + } + } + }, + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "fillable": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete" + ] + } + } + ] + }, + "hidden": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete", + "form", + "table", + "submit" + ] + } + } + ] + }, + "virtual": { + "type": "boolean" + } + } + } + } + }, + { + "type": "object", + "title": "number", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "name": { + "type": "string" + }, + "type": { + "const": "number" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "default_value": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "maximum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "multipleOf": { + "type": "number" + }, + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "fillable": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete" + ] + } + } + ] + }, + "hidden": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete", + "form", + "table", + "submit" + ] + } + } + ] + }, + "virtual": { + "type": "boolean" + } + } + } + } + }, + { + "type": "object", + "title": "boolean", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "name": { + "type": "string" + }, + "type": { + "const": "boolean" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "default_value": { + "type": "boolean" + }, + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "fillable": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete" + ] + } + } + ] + }, + "hidden": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete", + "form", + "table", + "submit" + ] + } + } + ] + }, + "virtual": { + "type": "boolean" + } + } + } + } + }, + { + "type": "object", + "title": "date", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "name": { + "type": "string" + }, + "type": { + "const": "date" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "date", + "datetime", + "week" + ], + "default": "date" + }, + "timezone": { + "type": "string" + }, + "min_date": { + "type": "string" + }, + "max_date": { + "type": "string" + }, + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "fillable": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete" + ] + } + } + ] + }, + "hidden": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete", + "form", + "table", + "submit" + ] + } + } + ] + }, + "virtual": { + "type": "boolean" + }, + "default_value": {} + } + } + } + }, + { + "type": "object", + "title": "enum", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "name": { + "type": "string" + }, + "type": { + "const": "enum" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "default_value": { + "type": "string" + }, + "options": { + "anyOf": [ + { + "type": "object", + "required": [ + "type", + "values" + ], + "properties": { + "type": { + "const": "strings" + }, + "values": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + { + "type": "object", + "required": [ + "type", + "values" + ], + "properties": { + "type": { + "const": "objects" + }, + "values": { + "type": "array", + "items": { + "type": "object", + "required": [ + "label", + "value" + ], + "properties": { + "label": { + "type": "string" + }, + "value": { + "type": "string" + } + } + } + } + } + } + ] + }, + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "fillable": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete" + ] + } + } + ] + }, + "hidden": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete", + "form", + "table", + "submit" + ] + } + } + ] + }, + "virtual": { + "type": "boolean" + } + } + } + } + }, + { + "type": "object", + "title": "json", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "name": { + "type": "string" + }, + "type": { + "const": "json" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "default_value": {}, + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "fillable": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete" + ] + } + } + ] + }, + "hidden": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete", + "form", + "table", + "submit" + ] + } + } + ] + }, + "virtual": { + "type": "boolean" + } + } + } + } + }, + { + "type": "object", + "title": "jsonschema", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "name": { + "type": "string" + }, + "type": { + "const": "jsonschema" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "schema": { + "type": "object" + }, + "ui_schema": { + "type": "object" + }, + "default_from_schema": { + "type": "boolean" + }, + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "fillable": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete" + ] + } + } + ] + }, + "hidden": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete", + "form", + "table", + "submit" + ] + } + } + ] + }, + "virtual": { + "type": "boolean" + }, + "default_value": {} + } + } + } + }, + { + "type": "object", + "title": "relation", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "name": { + "type": "string" + }, + "type": { + "const": "relation" + }, + "config": { + "type": "object", + "additionalProperties": false, + "required": [ + "reference", + "target" + ], + "properties": { + "reference": { + "type": "string" + }, + "target": { + "type": "string" + }, + "target_field": { + "type": "string", + "default": "id" + }, + "target_field_type": { + "type": "string", + "enum": [ + "text", + "integer" + ], + "default": "integer" + }, + "on_delete": { + "type": "string", + "enum": [ + "cascade", + "set null", + "set default", + "restrict", + "no action" + ], + "default": "set null" + }, + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "fillable": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete" + ] + } + } + ] + }, + "hidden": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete", + "form", + "table", + "submit" + ] + } + } + ] + }, + "virtual": { + "type": "boolean" + }, + "default_value": {} + } + } + } + }, + { + "type": "object", + "title": "media", + "additionalProperties": false, + "required": [ + "type" + ], + "properties": { + "name": { + "type": "string" + }, + "type": { + "const": "media" + }, + "config": { + "type": "object", + "additionalProperties": false, + "properties": { + "entity": { + "type": "string" + }, + "min_items": { + "type": "number" + }, + "max_items": { + "type": "number" + }, + "mime_types": { + "type": "array", + "items": { + "type": "string" + } + }, + "label": { + "type": "string" + }, + "description": { + "type": "string" + }, + "required": { + "type": "boolean", + "default": false + }, + "fillable": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete" + ] + } + } + ] + }, + "hidden": { + "anyOf": [ + { + "type": "boolean", + "title": "Boolean" + }, + { + "type": "array", + "title": "Context", + "uniqueItems": true, + "items": { + "type": "string", + "enum": [ + "create", + "read", + "update", + "delete", + "form", + "table", + "submit" + ] + } + } + ] + }, + "virtual": { + "type": "boolean" + }, + "default_value": {} + } + } + } + } + ] + } + } + } }, "return_config": { "type": "boolean", @@ -2001,196 +3078,33 @@ "description": "key to add" }, "value": { - "anyOf": [ - { - "type": "object", - "title": "1:1", - "additionalProperties": false, - "properties": { - "type": { - "const": "1:1" - }, - "source": { - "type": "string" - }, - "target": { - "type": "string" - }, - "config": { - "type": "object", - "additionalProperties": false, - "properties": { - "sourceCardinality": { - "type": "number" - }, - "with_limit": { - "type": "number", - "default": 5 - }, - "fieldConfig": { - "type": "object", - "properties": { - "label": { - "type": "string" - } - }, - "required": [ - "label" - ] - }, - "mappedBy": { - "type": "string" - }, - "inversedBy": { - "type": "string" - }, - "required": { - "type": "boolean" - } - } - } - }, - "required": [ - "type", - "source", - "target" + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "1:1", + "n:1", + "m:n", + "poly" ] }, - { - "type": "object", - "title": "n:1", - "additionalProperties": false, - "properties": { - "type": { - "const": "n:1" - }, - "source": { - "type": "string" - }, - "target": { - "type": "string" - }, - "config": { - "type": "object", - "additionalProperties": false, - "properties": { - "sourceCardinality": { - "type": "number" - }, - "with_limit": { - "type": "number", - "default": 5 - }, - "fieldConfig": { - "type": "object", - "properties": { - "label": { - "type": "string" - } - }, - "required": [ - "label" - ] - }, - "mappedBy": { - "type": "string" - }, - "inversedBy": { - "type": "string" - }, - "required": { - "type": "boolean" - } - } - } - }, - "required": [ - "type", - "source", - "target" - ] + "source": { + "type": "string" }, - { - "type": "object", - "title": "m:n", - "additionalProperties": false, - "properties": { - "type": { - "const": "m:n" - }, - "source": { - "type": "string" - }, - "target": { - "type": "string" - }, - "config": { - "type": "object", - "additionalProperties": false, - "properties": { - "connectionTable": { - "type": "string" - }, - "connectionTableMappedName": { - "type": "string" - }, - "mappedBy": { - "type": "string" - }, - "inversedBy": { - "type": "string" - }, - "required": { - "type": "boolean" - } - } - } - }, - "required": [ - "type", - "source", - "target" - ] + "target": { + "type": "string" }, - { + "config": { "type": "object", - "title": "poly", - "additionalProperties": false, - "properties": { - "type": { - "const": "poly" - }, - "source": { - "type": "string" - }, - "target": { - "type": "string" - }, - "config": { - "type": "object", - "additionalProperties": false, - "properties": { - "targetCardinality": { - "type": "number" - }, - "mappedBy": { - "type": "string" - }, - "inversedBy": { - "type": "string" - }, - "required": { - "type": "boolean" - } - } - } - }, - "required": [ - "type", - "source", - "target" - ] + "properties": {} } + }, + "required": [ + "type", + "source", + "target" ] }, "return_config": { @@ -2221,7 +3135,28 @@ }, "value": { "type": "object", - "properties": {} + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "1:1", + "n:1", + "m:n", + "poly" + ] + }, + "source": { + "type": "string" + }, + "target": { + "type": "string" + }, + "config": { + "type": "object", + "properties": {} + } + } }, "return_config": { "type": "boolean", @@ -2582,6 +3517,10 @@ "type": { "type": "string" }, + "enabled": { + "type": "boolean", + "default": true + }, "config": { "type": "object", "properties": {} @@ -2626,15 +3565,15 @@ "type": { "type": "string" }, + "enabled": { + "type": "boolean", + "default": true + }, "config": { "type": "object", "properties": {} } - }, - "required": [ - "type", - "config" - ] + } }, "return_config": { "type": "boolean", @@ -2761,7 +3700,21 @@ }, "value": { "type": "object", - "properties": {} + "additionalProperties": false, + "properties": { + "permissions": { + "type": "array", + "items": { + "type": "string" + } + }, + "is_default": { + "type": "boolean" + }, + "implicit_allow": { + "type": "boolean" + } + } }, "return_config": { "type": "boolean",