mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-16 12:37:20 +00:00
Release 0.16 (#196)
* initial refactor * fixes * test secrets extraction * updated lock * fix secret schema * updated schemas, fixed tests, skipping flow tests for now * added validator for rjsf, hook form via standard schema * removed @sinclair/typebox * remove unneeded vite dep * fix jsonv literal on Field.tsx * fix schema import path * fix schema modals * fix schema modals * fix json field form, replaced auth form * initial waku * finalize waku example * fix jsonv-ts version * fix schema updates with falsy values * fix media api to respect options' init, improve types * checking media controller test * checking media controller test * checking media controller test * clean up mediacontroller test * added cookie option `partitioned`, as well as cors `origin` to be array, option to enable `credentials` (#214) * added cookie option `partitioned`, as well as cors `origin` to be array, option to enable `credentials` * fix server test * fix data api (updated jsonv-ts) * enhance cloudflare image optimization plugin with new options and explain endpoint (#215) * feat: add ability to serve static by using dynamic imports (#197) * feat: add ability to serve static by using dynamic imports * serveStaticViaImport: make manifest optional * serveStaticViaImport: add error log * refactor/imports (#217) * refactored core and core/utils imports * refactored core and core/utils imports * refactored media imports * refactored auth imports * refactored data imports * updated package json exports, fixed mm config * fix tests * feat/deno (#219) * update bun version * fix module manager's em reference * add basic deno example * finalize * docs: fumadocs migration (#185) * feat(docs): initialize documentation structure with Fumadocs * feat(docs): remove home route and move /docs route to /route * feat(docs): add redirect to /start page * feat(docs): migrate Getting Started chapters * feat(docs): migrate Usage and Extending chapters * feat(callout): add CalloutCaution, CalloutDanger, CalloutInfo, and CalloutPositive * feat(layout): add Discord and GitHub links to documentation layout * feat(docs): add integration chapters draft * feat(docs): add modules chapters draft * refactor(mdx-components): remove unused Icon import * refactor(StackBlitz): enhance type safety by using unknown instead of any * refactor(layout): update navigation mode to 'top' in layout configuration * feat(docs): add @iconify/react package * docs(mdx-components): add Icon component to MDX components list * feat(docs): update Next.js integration guide * feat(docs): update React Router integration guide * feat(docs): update Astro integration guide * feat(docs): update Vite integration guide * fix(docs): update package manager initialization commands * feat(docs): migrate Modules chapters * chore(docs): update package.json with new devDependencies * feat(docs): migrate Integration Runtimes chapters * feat(docs): update Database usage chapter * feat(docs): restructure documentation paths * chore(docs): clean up unused imports and files in documentation * style(layout): revert navigation mode to previous state * fix(docs): routing for documentation structure * feat(openapi): add API documentation generation from OpenAPI schema * feat(docs): add icons to documentation pages * chore(dependencies): remove unused content-collections packages * fix(types): fix type error for attachFile in source.ts * feat(redirects): update root redirect destination to '/start' * feat(search): add static search functionality * chore(dependencies): update fumadocs-core and fumadocs-ui to latest versions * feat(search): add Powered by Orama link * feat(generate-openapi): add error handling for missing OpenAPI schema * feat(scripts): add OpenAPI generation to build process * feat(config): enable dynamic redirects and rewrites in development mode * feat(layout): add GitHub token support for improved API rate limits * feat(redirects): add 301 redirects for cloudflare pages * feat(docs): add Vercel redirects configuration * feat(config): enable standalone output for development environment * chore(layout): adjust layout settings * refactor(package): clean up ajv dependency versions * feat(docs): add twoslash support * refactor(layout): update DocsLayout import and navigation configuration * chore(layout): clean up layout.tsx by commenting out GithubInfo * fix(Search): add locale to search initialization * chore(package): update fumadocs and orama to latest versions * docs: add menu items descriptions * feat(layout): add GitHub URL to the layout component * feat(docs): add AutoTypeTable component to MDX components * feat(app): implement AutoTypeTable rendering for AppEvents type * docs(layout): switch callouts back to default components * fix(config): use __filename and __dirname for module paths * docs: add note about node.js 22 requirement * feat(styles): add custom color variables for light and dark themes * docs: add S3 setup instructions for media module * docs: fix typos and indentation in media module docs * docs: add local media adapter example for Node.js * docs(media): add S3/R2 URL format examples and fix typo * docs: add cross-links to initial config and seeding sections * indent numbered lists content, clarified media serve locations * fix mediacontroller tests * feat(layout): add AnimatedGridPattern component for dynamic background * style(layout): configure fancy ToC style ('clerk') * fix(AnimatedGridPattern): correct strokeDasharray type * docs: actualize docs * feat: add favicon * style(cloudflare): format code examples * feat(layout): add Github and Discord footer icons * feat(footer): add SVG social media icons for GitHub and Discord * docs: adjusted auto type table, added llm functions * added static deployment to cloudflare workers * docs: change cf redirects to proxy *.mdx instead of redirecting --------- Co-authored-by: dswbx <dennis.senn@gmx.ch> Co-authored-by: cameronapak <cameronandrewpak@gmail.com> * build: improve build script * add missing exports, fix EntityTypescript imports * media: Dropzone: add programmatic upload, additional events, loading state * schema object: disable extended defaults to allow empty config values * Feat/new docs deploy (#224) * test * try fixing pm * try fixing pm * fix docs on imports, export events correctly --------- Co-authored-by: Tim Seriakov <59409712+timseriakov@users.noreply.github.com> Co-authored-by: cameronapak <cameronandrewpak@gmail.com>
This commit is contained in:
@@ -1,11 +1,10 @@
|
||||
import type { App } from "App";
|
||||
import type { App, SafeUser } from "bknd";
|
||||
import { type Context, type Env, Hono } from "hono";
|
||||
import * as middlewares from "modules/middlewares";
|
||||
import type { SafeUser } from "auth";
|
||||
import type { EntityManager } from "data";
|
||||
import { s } from "core/object/schema";
|
||||
import type { EntityManager } from "data/entities";
|
||||
import { s } from "bknd/utils";
|
||||
|
||||
export type ServerEnv = Env & {
|
||||
export interface ServerEnv extends Env {
|
||||
Variables: {
|
||||
app: App;
|
||||
// to prevent resolving auth multiple times
|
||||
@@ -17,7 +16,22 @@ export type ServerEnv = Env & {
|
||||
};
|
||||
html?: string;
|
||||
};
|
||||
};
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
/* export type ServerEnv = Env & {
|
||||
Variables: {
|
||||
app: App;
|
||||
// to prevent resolving auth multiple times
|
||||
auth?: {
|
||||
resolved: boolean;
|
||||
registered: boolean;
|
||||
skip: boolean;
|
||||
user?: SafeUser;
|
||||
};
|
||||
html?: string;
|
||||
};
|
||||
}; */
|
||||
|
||||
export class Controller {
|
||||
protected middlewares = middlewares;
|
||||
@@ -49,7 +63,7 @@ export class Controller {
|
||||
return c.notFound();
|
||||
}
|
||||
|
||||
protected getEntitiesEnum(em: EntityManager<any>) {
|
||||
protected getEntitiesEnum(em: EntityManager<any>): s.StringSchema {
|
||||
const entities = em.entities.map((e) => e.name);
|
||||
// @todo: current workaround to allow strings (sometimes building is not fast enough to get the entities)
|
||||
return entities.length > 0 ? s.anyOf([s.string({ enum: entities }), s.string()]) : s.string();
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import type { Guard } from "auth";
|
||||
import { type DebugLogger, SchemaObject } from "core";
|
||||
import type { EventManager } from "core/events";
|
||||
import type { Static, TSchema } from "core/utils";
|
||||
import type { Connection, EntityManager } from "data";
|
||||
import type { Connection } from "data/connection";
|
||||
import type { EntityManager } from "data/entities";
|
||||
import type { Hono } from "hono";
|
||||
import type { ServerEnv } from "modules/Controller";
|
||||
import type { ModuleHelper } from "./ModuleHelper";
|
||||
import { SchemaObject } from "core/object/SchemaObject";
|
||||
import type { DebugLogger } from "core/utils/DebugLogger";
|
||||
import type { Guard } from "auth/authorize/Guard";
|
||||
|
||||
type PartialRec<T> = { [P in keyof T]?: PartialRec<T[P]> };
|
||||
|
||||
export type ModuleBuildContext = {
|
||||
connection: Connection;
|
||||
@@ -18,13 +21,13 @@ export type ModuleBuildContext = {
|
||||
helper: ModuleHelper;
|
||||
};
|
||||
|
||||
export abstract class Module<Schema extends TSchema = TSchema, ConfigSchema = Static<Schema>> {
|
||||
export abstract class Module<Schema extends object = object> {
|
||||
private _built = false;
|
||||
private _schema: SchemaObject<ReturnType<(typeof this)["getSchema"]>>;
|
||||
private _listener: any = () => null;
|
||||
|
||||
constructor(
|
||||
initial?: Partial<Static<Schema>>,
|
||||
initial?: PartialRec<Schema>,
|
||||
protected _ctx?: ModuleBuildContext,
|
||||
) {
|
||||
this._schema = new SchemaObject(this.getSchema(), initial, {
|
||||
@@ -47,7 +50,7 @@ export abstract class Module<Schema extends TSchema = TSchema, ConfigSchema = St
|
||||
ctx_reload_required: boolean;
|
||||
};
|
||||
|
||||
onBeforeUpdate(from: ConfigSchema, to: ConfigSchema): ConfigSchema | Promise<ConfigSchema> {
|
||||
onBeforeUpdate(from: Schema, to: Schema): Schema | Promise<Schema> {
|
||||
return to;
|
||||
}
|
||||
|
||||
@@ -75,11 +78,13 @@ export abstract class Module<Schema extends TSchema = TSchema, ConfigSchema = St
|
||||
return undefined;
|
||||
}
|
||||
|
||||
get configDefault(): Static<ReturnType<(typeof this)["getSchema"]>> {
|
||||
return this._schema.default();
|
||||
//get configDefault(): s.Static<ReturnType<(typeof this)["getSchema"]>> {
|
||||
get configDefault(): Schema {
|
||||
return this._schema.default() as any;
|
||||
}
|
||||
|
||||
get config(): Static<ReturnType<(typeof this)["getSchema"]>> {
|
||||
//get config(): s.Static<ReturnType<(typeof this)["getSchema"]>> {
|
||||
get config(): Schema {
|
||||
return this._schema.get();
|
||||
}
|
||||
|
||||
@@ -130,7 +135,8 @@ export abstract class Module<Schema extends TSchema = TSchema, ConfigSchema = St
|
||||
}
|
||||
}
|
||||
|
||||
toJSON(secrets?: boolean): Static<ReturnType<(typeof this)["getSchema"]>> {
|
||||
//toJSON(secrets?: boolean): s.Static<ReturnType<(typeof this)["getSchema"]>> {
|
||||
toJSON(secrets?: boolean): Schema {
|
||||
return this.config;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import type { PrimaryFieldType } from "core";
|
||||
import { $console } from "core/utils";
|
||||
import type { PrimaryFieldType } from "bknd";
|
||||
import { $console, isPlainObject, encodeSearch } from "bknd/utils";
|
||||
import { isDebug } from "core/env";
|
||||
import { encodeSearch } from "core/utils/reqres";
|
||||
import type { ApiFetcher } from "Api";
|
||||
|
||||
export type { PrimaryFieldType };
|
||||
@@ -95,7 +94,11 @@ export abstract class ModuleApi<Options extends BaseModuleApiOptions = BaseModul
|
||||
let body: any = _init?.body;
|
||||
if (_init && "body" in _init && ["POST", "PATCH", "PUT"].includes(method)) {
|
||||
const requestContentType = (headers.get("Content-Type") as string) ?? undefined;
|
||||
if (!requestContentType || requestContentType.startsWith("application/json")) {
|
||||
if (
|
||||
!requestContentType ||
|
||||
requestContentType.startsWith("application/json") ||
|
||||
isPlainObject(body) // @todo: not entirely sure about this
|
||||
) {
|
||||
body = JSON.stringify(_init.body);
|
||||
headers.set("Content-Type", "application/json");
|
||||
}
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
import {
|
||||
type EntityIndex,
|
||||
type EntityRelation,
|
||||
type Field,
|
||||
type em as prototypeEm,
|
||||
FieldPrototype,
|
||||
make,
|
||||
Entity,
|
||||
entityTypes,
|
||||
} from "data";
|
||||
import { type em as prototypeEm, FieldPrototype, make } from "data/prototype";
|
||||
import { Entity } from "data/entities";
|
||||
import type { EntityIndex, Field } from "data/fields";
|
||||
import { entityTypes } from "data/entities/Entity";
|
||||
import { isEqual } from "lodash-es";
|
||||
import type { ModuleBuildContext } from "./Module";
|
||||
import type { EntityRelation } from "data/relations";
|
||||
|
||||
export class ModuleHelper {
|
||||
constructor(protected ctx: Omit<ModuleBuildContext, "helper">) {}
|
||||
|
||||
@@ -1,18 +1,11 @@
|
||||
import { Guard } from "auth";
|
||||
import { BkndError, DebugLogger, env } from "core";
|
||||
import { $console } from "core/utils";
|
||||
import { mark, stripMark, $console, s, objectEach, transformObject } from "bknd/utils";
|
||||
import { Guard } from "auth/authorize/Guard";
|
||||
import { env } from "core/env";
|
||||
import { BkndError } from "core/errors";
|
||||
import { DebugLogger } from "core/utils/DebugLogger";
|
||||
import { EventManager, Event } from "core/events";
|
||||
import * as $diff from "core/object/diff";
|
||||
import {
|
||||
Default,
|
||||
type Static,
|
||||
StringEnum,
|
||||
mark,
|
||||
objectEach,
|
||||
stripMark,
|
||||
transformObject,
|
||||
} from "core/utils";
|
||||
import type { Connection, Schema } from "data";
|
||||
import type { Connection } from "data/connection";
|
||||
import { EntityManager } from "data/entities/EntityManager";
|
||||
import * as proto from "data/prototype";
|
||||
import { TransformPersistFailedException } from "data/errors";
|
||||
@@ -27,9 +20,7 @@ import { AppFlows } from "../flows/AppFlows";
|
||||
import { AppMedia } from "../media/AppMedia";
|
||||
import type { ServerEnv } from "./Controller";
|
||||
import { Module, type ModuleBuildContext } from "./Module";
|
||||
import * as tbbox from "@sinclair/typebox";
|
||||
import { ModuleHelper } from "./ModuleHelper";
|
||||
const { Type } = tbbox;
|
||||
|
||||
export type { ModuleBuildContext };
|
||||
|
||||
@@ -54,7 +45,7 @@ export type ModuleSchemas = {
|
||||
};
|
||||
|
||||
export type ModuleConfigs = {
|
||||
[K in keyof ModuleSchemas]: Static<ModuleSchemas[K]>;
|
||||
[K in keyof ModuleSchemas]: s.Static<ModuleSchemas[K]>;
|
||||
};
|
||||
type PartialRec<T> = { [P in keyof T]?: PartialRec<T[P]> };
|
||||
|
||||
@@ -102,25 +93,25 @@ export type ConfigTable<Json = ModuleConfigs> = {
|
||||
updated_at?: Date;
|
||||
};
|
||||
|
||||
const configJsonSchema = Type.Union([
|
||||
const configJsonSchema = s.anyOf([
|
||||
getDefaultSchema(),
|
||||
Type.Array(
|
||||
Type.Object({
|
||||
t: StringEnum(["a", "r", "e"]),
|
||||
p: Type.Array(Type.Union([Type.String(), Type.Number()])),
|
||||
o: Type.Optional(Type.Any()),
|
||||
n: Type.Optional(Type.Any()),
|
||||
s.array(
|
||||
s.strictObject({
|
||||
t: s.string({ enum: ["a", "r", "e"] }),
|
||||
p: s.array(s.anyOf([s.string(), s.number()])),
|
||||
o: s.any().optional(),
|
||||
n: s.any().optional(),
|
||||
}),
|
||||
),
|
||||
]);
|
||||
export const __bknd = proto.entity(TABLE_NAME, {
|
||||
version: proto.number().required(),
|
||||
type: proto.enumm({ enum: ["config", "diff", "backup"] }).required(),
|
||||
json: proto.jsonSchema({ schema: configJsonSchema }).required(),
|
||||
json: proto.jsonSchema({ schema: configJsonSchema.toJSON() }).required(),
|
||||
created_at: proto.datetime(),
|
||||
updated_at: proto.datetime(),
|
||||
});
|
||||
type ConfigTable2 = Schema<typeof __bknd>;
|
||||
type ConfigTable2 = proto.Schema<typeof __bknd>;
|
||||
interface T_INTERNAL_EM {
|
||||
__bknd: ConfigTable2;
|
||||
}
|
||||
@@ -276,7 +267,9 @@ export class ModuleManager {
|
||||
ctx(rebuild?: boolean): ModuleBuildContext {
|
||||
if (rebuild) {
|
||||
this.rebuildServer();
|
||||
this.em = new EntityManager([], this.connection, [], [], this.emgr);
|
||||
this.em = this.em
|
||||
? this.em.clear()
|
||||
: new EntityManager([], this.connection, [], [], this.emgr);
|
||||
this.guard = new Guard();
|
||||
}
|
||||
|
||||
@@ -669,7 +662,7 @@ export class ModuleManager {
|
||||
|
||||
return result;
|
||||
} catch (e) {
|
||||
$console.error(`[Safe Mutate] failed "${name}":`, String(e));
|
||||
$console.error(`[Safe Mutate] failed "${name}":`, e);
|
||||
|
||||
// revert to previous config & rebuild using original listener
|
||||
this.revertModules();
|
||||
@@ -740,8 +733,14 @@ export function getDefaultSchema() {
|
||||
|
||||
export function getDefaultConfig(): ModuleConfigs {
|
||||
const config = transformObject(MODULES, (module) => {
|
||||
return Default(module.prototype.getSchema(), {});
|
||||
return module.prototype.getSchema().template(
|
||||
{},
|
||||
{
|
||||
withOptional: true,
|
||||
withExtendedOptional: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
return config as any;
|
||||
return structuredClone(config) as any;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Permission } from "core";
|
||||
import { Permission } from "core/security/Permission";
|
||||
|
||||
export const accessAdmin = new Permission("system.access.admin");
|
||||
export const accessApi = new Permission("system.access.api");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MediaAdapterRegistry } from "media";
|
||||
import { MediaAdapterRegistry } from "media/media-registry";
|
||||
|
||||
const registries = {
|
||||
media: MediaAdapterRegistry,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
/** @jsxImportSource hono/jsx */
|
||||
|
||||
import type { App } from "App";
|
||||
import { config, isDebug } from "core";
|
||||
import { $console } from "core/utils";
|
||||
import { isDebug } from "core/env";
|
||||
import { config } from "core/config";
|
||||
import { $console } from "bknd/utils";
|
||||
import { addFlashMessage } from "core/server/flash";
|
||||
import { html } from "hono/html";
|
||||
import { Fragment } from "hono/jsx";
|
||||
|
||||
@@ -1,37 +1,29 @@
|
||||
import { Exception, isDebug } from "core";
|
||||
import { type Static, StringEnum, $console } from "core/utils";
|
||||
import { Exception } from "core/errors";
|
||||
import { isDebug } from "core/env";
|
||||
import { $console, s } from "bknd/utils";
|
||||
import { cors } from "hono/cors";
|
||||
import { Module } from "modules/Module";
|
||||
import * as tbbox from "@sinclair/typebox";
|
||||
import { AuthException } from "auth/errors";
|
||||
const { Type } = tbbox;
|
||||
|
||||
const serverMethods = ["GET", "POST", "PATCH", "PUT", "DELETE"];
|
||||
const serverMethods = ["GET", "POST", "PATCH", "PUT", "DELETE"] as const;
|
||||
|
||||
export const serverConfigSchema = Type.Object(
|
||||
{
|
||||
cors: Type.Object(
|
||||
{
|
||||
origin: Type.String({ default: "*" }),
|
||||
allow_methods: Type.Array(StringEnum(serverMethods), {
|
||||
default: serverMethods,
|
||||
uniqueItems: true,
|
||||
}),
|
||||
allow_headers: Type.Array(Type.String(), {
|
||||
default: ["Content-Type", "Content-Length", "Authorization", "Accept"],
|
||||
}),
|
||||
},
|
||||
{ default: {}, additionalProperties: false },
|
||||
),
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
},
|
||||
);
|
||||
export const serverConfigSchema = s.strictObject({
|
||||
cors: s.strictObject({
|
||||
origin: s.string({ default: "*" }),
|
||||
allow_methods: s.array(s.string({ enum: serverMethods }), {
|
||||
default: serverMethods,
|
||||
uniqueItems: true,
|
||||
}),
|
||||
allow_headers: s.array(s.string(), {
|
||||
default: ["Content-Type", "Content-Length", "Authorization", "Accept"],
|
||||
}),
|
||||
allow_credentials: s.boolean({ default: true }),
|
||||
}),
|
||||
});
|
||||
|
||||
export type AppServerConfig = Static<typeof serverConfigSchema>;
|
||||
export type AppServerConfig = s.Static<typeof serverConfigSchema>;
|
||||
|
||||
export class AppServer extends Module<typeof serverConfigSchema> {
|
||||
export class AppServer extends Module<AppServerConfig> {
|
||||
override getRestrictedPaths() {
|
||||
return [];
|
||||
}
|
||||
@@ -45,12 +37,14 @@ export class AppServer extends Module<typeof serverConfigSchema> {
|
||||
}
|
||||
|
||||
override async build() {
|
||||
const origin = this.config.cors.origin ?? "";
|
||||
this.client.use(
|
||||
"*",
|
||||
cors({
|
||||
origin: this.config.cors.origin,
|
||||
origin: origin.includes(",") ? origin.split(",").map((o) => o.trim()) : origin,
|
||||
allowMethods: this.config.cors.allow_methods,
|
||||
allowHeaders: this.config.cors.allow_headers,
|
||||
credentials: this.config.cors.allow_credentials,
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -2,14 +2,18 @@
|
||||
|
||||
import type { App } from "App";
|
||||
import {
|
||||
$console,
|
||||
TypeInvalidError,
|
||||
datetimeStringLocal,
|
||||
datetimeStringUTC,
|
||||
getTimezone,
|
||||
getTimezoneOffset,
|
||||
} from "core/utils";
|
||||
import { getRuntimeKey } from "core/utils";
|
||||
$console,
|
||||
getRuntimeKey,
|
||||
SecretSchema,
|
||||
jsc,
|
||||
s,
|
||||
describeRoute,
|
||||
InvalidSchemaError,
|
||||
} from "bknd/utils";
|
||||
import type { Context, Hono } from "hono";
|
||||
import { Controller } from "modules/Controller";
|
||||
import { openAPISpecs } from "jsonv-ts/hono";
|
||||
@@ -19,11 +23,10 @@ import {
|
||||
type ModuleConfigs,
|
||||
type ModuleSchemas,
|
||||
type ModuleKey,
|
||||
getDefaultConfig,
|
||||
} from "modules/ModuleManager";
|
||||
import * as SystemPermissions from "modules/permissions";
|
||||
import { jsc, s, describeRoute } from "core/object/schema";
|
||||
import { getVersion } from "core/env";
|
||||
|
||||
export type ConfigUpdate<Key extends ModuleKey = ModuleKey> = {
|
||||
success: true;
|
||||
module: Key;
|
||||
@@ -103,7 +106,7 @@ export class SystemController extends Controller {
|
||||
} catch (e) {
|
||||
$console.error("config update error", e);
|
||||
|
||||
if (e instanceof TypeInvalidError) {
|
||||
if (e instanceof InvalidSchemaError) {
|
||||
return c.json(
|
||||
{ success: false, type: "type-invalid", errors: e.errors },
|
||||
{ status: 400 },
|
||||
@@ -233,11 +236,13 @@ export class SystemController extends Controller {
|
||||
permission(SystemPermissions.schemaRead),
|
||||
jsc(
|
||||
"query",
|
||||
s.partialObject({
|
||||
config: s.boolean(),
|
||||
secrets: s.boolean(),
|
||||
fresh: s.boolean(),
|
||||
}),
|
||||
s
|
||||
.object({
|
||||
config: s.boolean(),
|
||||
secrets: s.boolean(),
|
||||
fresh: s.boolean(),
|
||||
})
|
||||
.partial(),
|
||||
),
|
||||
async (c) => {
|
||||
const module = c.req.param("module") as ModuleKey | undefined;
|
||||
@@ -321,7 +326,21 @@ export class SystemController extends Controller {
|
||||
local: datetimeStringLocal(),
|
||||
utc: datetimeStringUTC(),
|
||||
},
|
||||
origin: new URL(c.req.raw.url).origin,
|
||||
plugins: Array.from(this.app.plugins.keys()),
|
||||
walk: {
|
||||
auth: [
|
||||
...c
|
||||
.get("app")
|
||||
.getSchema()
|
||||
.auth.walk({ data: c.get("app").toJSON(true).auth }),
|
||||
]
|
||||
.filter((n) => n.schema instanceof SecretSchema)
|
||||
.map((n) => ({
|
||||
...n,
|
||||
schema: n.schema.constructor.name,
|
||||
})),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -1,313 +0,0 @@
|
||||
import type { ModuleConfigs } from "modules/ModuleManager";
|
||||
import type { OpenAPIV3 as OAS } from "openapi-types";
|
||||
import * as tbbox from "@sinclair/typebox";
|
||||
const { Type } = tbbox;
|
||||
|
||||
function prefixPaths(paths: OAS.PathsObject, prefix: string): OAS.PathsObject {
|
||||
const result: OAS.PathsObject = {};
|
||||
for (const [path, pathItem] of Object.entries(paths)) {
|
||||
result[`${prefix}${path}`] = pathItem;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function systemRoutes(config: ModuleConfigs): { paths: OAS.Document["paths"] } {
|
||||
const tags = ["system"];
|
||||
const paths: OAS.PathsObject = {
|
||||
"/ping": {
|
||||
get: {
|
||||
summary: "Ping",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Pong",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: Type.Object({
|
||||
pong: Type.Boolean({ default: true }),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tags,
|
||||
},
|
||||
},
|
||||
"/config": {
|
||||
get: {
|
||||
summary: "Get config",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Config",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: Type.Object({
|
||||
version: Type.Number() as any,
|
||||
server: Type.Object({}),
|
||||
data: Type.Object({}),
|
||||
auth: Type.Object({}),
|
||||
flows: Type.Object({}),
|
||||
media: Type.Object({}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tags,
|
||||
},
|
||||
},
|
||||
"/schema": {
|
||||
get: {
|
||||
summary: "Get config",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Config",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: Type.Object({
|
||||
version: Type.Number() as any,
|
||||
schema: Type.Object({
|
||||
server: Type.Object({}),
|
||||
data: Type.Object({}),
|
||||
auth: Type.Object({}),
|
||||
flows: Type.Object({}),
|
||||
media: Type.Object({}),
|
||||
}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tags,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return { paths: prefixPaths(paths, "/api/system") };
|
||||
}
|
||||
|
||||
function dataRoutes(config: ModuleConfigs): { paths: OAS.Document["paths"] } {
|
||||
const schemas = {
|
||||
entityData: Type.Object({
|
||||
id: Type.Number() as any,
|
||||
}),
|
||||
};
|
||||
const repoManyResponses: OAS.ResponsesObject = {
|
||||
"200": {
|
||||
description: "List of entities",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: Type.Array(schemas.entityData),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const repoSingleResponses: OAS.ResponsesObject = {
|
||||
"200": {
|
||||
description: "Entity",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: schemas.entityData,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const params = {
|
||||
entity: {
|
||||
name: "entity",
|
||||
in: "path",
|
||||
required: true,
|
||||
schema: Type.String(),
|
||||
},
|
||||
entityId: {
|
||||
name: "id",
|
||||
in: "path",
|
||||
required: true,
|
||||
schema: Type.Number() as any,
|
||||
},
|
||||
};
|
||||
|
||||
const tags = ["data"];
|
||||
const paths: OAS.PathsObject = {
|
||||
"/entity/{entity}": {
|
||||
get: {
|
||||
summary: "List entities",
|
||||
parameters: [params.entity],
|
||||
responses: repoManyResponses,
|
||||
tags,
|
||||
},
|
||||
post: {
|
||||
summary: "Create entity",
|
||||
parameters: [params.entity],
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: Type.Object({}),
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: repoSingleResponses,
|
||||
tags,
|
||||
},
|
||||
},
|
||||
"/entity/{entity}/{id}": {
|
||||
get: {
|
||||
summary: "Get entity",
|
||||
parameters: [params.entity, params.entityId],
|
||||
responses: repoSingleResponses,
|
||||
tags,
|
||||
},
|
||||
patch: {
|
||||
summary: "Update entity",
|
||||
parameters: [params.entity, params.entityId],
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: Type.Object({}),
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: repoSingleResponses,
|
||||
tags,
|
||||
},
|
||||
delete: {
|
||||
summary: "Delete entity",
|
||||
parameters: [params.entity, params.entityId],
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Entity deleted",
|
||||
},
|
||||
},
|
||||
tags,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return { paths: prefixPaths(paths, config.data.basepath!) };
|
||||
}
|
||||
|
||||
function authRoutes(config: ModuleConfigs): { paths: OAS.Document["paths"] } {
|
||||
const schemas = {
|
||||
user: Type.Object({
|
||||
id: Type.String(),
|
||||
email: Type.String(),
|
||||
name: Type.String(),
|
||||
}),
|
||||
};
|
||||
|
||||
const tags = ["auth"];
|
||||
const paths: OAS.PathsObject = {
|
||||
"/password/login": {
|
||||
post: {
|
||||
summary: "Login",
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: Type.Object({
|
||||
email: Type.String(),
|
||||
password: Type.String(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
"200": {
|
||||
description: "User",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: Type.Object({
|
||||
user: schemas.user,
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tags,
|
||||
},
|
||||
},
|
||||
"/password/register": {
|
||||
post: {
|
||||
summary: "Register",
|
||||
requestBody: {
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: Type.Object({
|
||||
email: Type.String(),
|
||||
password: Type.String(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
"200": {
|
||||
description: "User",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: Type.Object({
|
||||
user: schemas.user,
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tags,
|
||||
},
|
||||
},
|
||||
"/me": {
|
||||
get: {
|
||||
summary: "Get me",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "User",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: Type.Object({
|
||||
user: schemas.user,
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tags,
|
||||
},
|
||||
},
|
||||
"/strategies": {
|
||||
get: {
|
||||
summary: "Get auth strategies",
|
||||
responses: {
|
||||
"200": {
|
||||
description: "Strategies",
|
||||
content: {
|
||||
"application/json": {
|
||||
schema: Type.Object({
|
||||
strategies: Type.Object({}),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
tags,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return { paths: prefixPaths(paths, config.auth.basepath!) };
|
||||
}
|
||||
|
||||
export function generateOpenAPI(config: ModuleConfigs): OAS.Document {
|
||||
const system = systemRoutes(config);
|
||||
const data = dataRoutes(config);
|
||||
const auth = authRoutes(config);
|
||||
|
||||
return {
|
||||
openapi: "3.1.0",
|
||||
info: {
|
||||
title: "bknd API",
|
||||
version: "0.0.0",
|
||||
},
|
||||
paths: {
|
||||
...system.paths,
|
||||
...data.paths,
|
||||
...auth.paths,
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user