mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-15 20:17:22 +00:00
added onBeforeUpdate listener + auto create a secret on auth enable
This commit is contained in:
@@ -65,11 +65,11 @@ describe("SchemaObject", async () => {
|
|||||||
expect(m.get()).toEqual({ methods: ["GET", "PATCH"] });
|
expect(m.get()).toEqual({ methods: ["GET", "PATCH"] });
|
||||||
|
|
||||||
// array values are fully overwritten, whether accessed by index ...
|
// array values are fully overwritten, whether accessed by index ...
|
||||||
m.patch("methods[0]", "POST");
|
await m.patch("methods[0]", "POST");
|
||||||
expect(m.get()).toEqual({ methods: ["POST"] });
|
expect(m.get().methods[0]).toEqual("POST");
|
||||||
|
|
||||||
// or by path!
|
// or by path!
|
||||||
m.patch("methods", ["GET", "DELETE"]);
|
await m.patch("methods", ["GET", "DELETE"]);
|
||||||
expect(m.get()).toEqual({ methods: ["GET", "DELETE"] });
|
expect(m.get()).toEqual({ methods: ["GET", "DELETE"] });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -93,15 +93,15 @@ describe("SchemaObject", async () => {
|
|||||||
expect(m.get()).toEqual({ s: { a: "b", b: { c: "d" } } });
|
expect(m.get()).toEqual({ s: { a: "b", b: { c: "d" } } });
|
||||||
|
|
||||||
// expect no change, because the default then applies
|
// expect no change, because the default then applies
|
||||||
m.remove("s.a");
|
await m.remove("s.a");
|
||||||
expect(m.get()).toEqual({ s: { a: "b", b: { c: "d" } } });
|
expect(m.get()).toEqual({ s: { a: "b", b: { c: "d" } } });
|
||||||
|
|
||||||
// adding another path, and then deleting it
|
// adding another path, and then deleting it
|
||||||
m.patch("s.c", "d");
|
await m.patch("s.c", "d");
|
||||||
expect(m.get()).toEqual({ s: { a: "b", b: { c: "d" }, c: "d" } } as any);
|
expect(m.get()).toEqual({ s: { a: "b", b: { c: "d" }, c: "d" } } as any);
|
||||||
|
|
||||||
// now it should be removed without applying again
|
// now it should be removed without applying again
|
||||||
m.remove("s.c");
|
await m.remove("s.c");
|
||||||
expect(m.get()).toEqual({ s: { a: "b", b: { c: "d" } } });
|
expect(m.get()).toEqual({ s: { a: "b", b: { c: "d" } } });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -113,14 +113,14 @@ describe("SchemaObject", async () => {
|
|||||||
);
|
);
|
||||||
expect(m.get()).toEqual({ methods: ["GET", "PATCH"] });
|
expect(m.get()).toEqual({ methods: ["GET", "PATCH"] });
|
||||||
|
|
||||||
m.set({ methods: ["GET", "POST"] });
|
await m.set({ methods: ["GET", "POST"] });
|
||||||
expect(m.get()).toEqual({ methods: ["GET", "POST"] });
|
expect(m.get()).toEqual({ methods: ["GET", "POST"] });
|
||||||
|
|
||||||
// wrong type
|
// wrong type
|
||||||
expect(() => m.set({ methods: [1] as any })).toThrow();
|
expect(() => m.set({ methods: [1] as any })).toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
test("listener", async () => {
|
test("listener: onUpdate", async () => {
|
||||||
let called = false;
|
let called = false;
|
||||||
let result: any;
|
let result: any;
|
||||||
const m = new SchemaObject(
|
const m = new SchemaObject(
|
||||||
@@ -142,6 +142,30 @@ describe("SchemaObject", async () => {
|
|||||||
expect(result).toEqual({ methods: ["GET", "POST"] });
|
expect(result).toEqual({ methods: ["GET", "POST"] });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("listener: onBeforeUpdate", async () => {
|
||||||
|
let called = false;
|
||||||
|
const m = new SchemaObject(
|
||||||
|
Type.Object({
|
||||||
|
methods: Type.Array(Type.String(), { default: ["GET", "PATCH"] })
|
||||||
|
}),
|
||||||
|
undefined,
|
||||||
|
{
|
||||||
|
onBeforeUpdate: async (from, to) => {
|
||||||
|
await new Promise((r) => setTimeout(r, 10));
|
||||||
|
called = true;
|
||||||
|
to.methods.push("OPTIONS");
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await m.set({ methods: ["GET", "POST"] });
|
||||||
|
expect(called).toBe(true);
|
||||||
|
expect(result).toEqual({ methods: ["GET", "POST", "OPTIONS"] });
|
||||||
|
const [, result2] = await m.patch("methods", ["GET", "POST"]);
|
||||||
|
expect(result2).toEqual({ methods: ["GET", "POST", "OPTIONS"] });
|
||||||
|
});
|
||||||
|
|
||||||
test("throwIfRestricted", async () => {
|
test("throwIfRestricted", async () => {
|
||||||
const m = new SchemaObject(Type.Object({}), undefined, {
|
const m = new SchemaObject(Type.Object({}), undefined, {
|
||||||
restrictPaths: ["a.b"]
|
restrictPaths: ["a.b"]
|
||||||
@@ -175,9 +199,9 @@ describe("SchemaObject", async () => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(() => m.patch("s.b.c", "e")).toThrow();
|
expect(m.patch("s.b.c", "e")).rejects.toThrow();
|
||||||
expect(m.bypass().patch("s.b.c", "e")).toBeDefined();
|
expect(m.bypass().patch("s.b.c", "e")).resolves.toBeDefined();
|
||||||
expect(() => m.patch("s.b.c", "f")).toThrow();
|
expect(m.patch("s.b.c", "f")).rejects.toThrow();
|
||||||
expect(m.get()).toEqual({ s: { a: "b", b: { c: "e" } } });
|
expect(m.get()).toEqual({ s: { a: "b", b: { c: "e" } } });
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -222,7 +246,7 @@ describe("SchemaObject", async () => {
|
|||||||
overwritePaths: [/^entities\..*\.fields\..*\.config/]
|
overwritePaths: [/^entities\..*\.fields\..*\.config/]
|
||||||
});
|
});
|
||||||
|
|
||||||
m.patch("entities.some.fields.a", { type: "string", config: { another: "one" } });
|
await m.patch("entities.some.fields.a", { type: "string", config: { another: "one" } });
|
||||||
|
|
||||||
expect(m.get()).toEqual({
|
expect(m.get()).toEqual({
|
||||||
entities: {
|
entities: {
|
||||||
@@ -251,7 +275,7 @@ describe("SchemaObject", async () => {
|
|||||||
overwritePaths: [/^entities\..*\.fields\..*\.config\.html_config$/]
|
overwritePaths: [/^entities\..*\.fields\..*\.config\.html_config$/]
|
||||||
});
|
});
|
||||||
|
|
||||||
m.patch("entities.test", {
|
await m.patch("entities.test", {
|
||||||
fields: {
|
fields: {
|
||||||
content: {
|
content: {
|
||||||
type: "text"
|
type: "text"
|
||||||
@@ -296,7 +320,7 @@ describe("SchemaObject", async () => {
|
|||||||
|
|
||||||
expect(m.patch("desc", "entities.users.config.sort_dir")).rejects.toThrow();
|
expect(m.patch("desc", "entities.users.config.sort_dir")).rejects.toThrow();
|
||||||
|
|
||||||
m.patch("entities.test", {
|
await m.patch("entities.test", {
|
||||||
fields: {
|
fields: {
|
||||||
content: {
|
content: {
|
||||||
type: "text"
|
type: "text"
|
||||||
@@ -304,7 +328,7 @@ describe("SchemaObject", async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
m.patch("entities.users.config", {
|
await m.patch("entities.users.config", {
|
||||||
sort_dir: "desc"
|
sort_dir: "desc"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -19,10 +19,23 @@ describe("AppAuth", () => {
|
|||||||
await auth.build();
|
await auth.build();
|
||||||
|
|
||||||
const config = auth.toJSON();
|
const config = auth.toJSON();
|
||||||
expect(config.jwt.secret).toBeUndefined();
|
expect(config.jwt).toBeUndefined();
|
||||||
expect(config.strategies.password.config).toBeUndefined();
|
expect(config.strategies.password.config).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("enabling auth: generate secret", async () => {
|
||||||
|
const auth = new AppAuth(undefined, ctx);
|
||||||
|
await auth.build();
|
||||||
|
|
||||||
|
const oldConfig = auth.toJSON(true);
|
||||||
|
//console.log(oldConfig);
|
||||||
|
await auth.schema().patch("enabled", true);
|
||||||
|
await auth.build();
|
||||||
|
const newConfig = auth.toJSON(true);
|
||||||
|
//console.log(newConfig);
|
||||||
|
expect(newConfig.jwt.secret).not.toBe(oldConfig.jwt.secret);
|
||||||
|
});
|
||||||
|
|
||||||
test("creates user on register", async () => {
|
test("creates user on register", async () => {
|
||||||
const auth = new AppAuth(
|
const auth = new AppAuth(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ export type CloudflareBkndConfig<Env = any> = {
|
|||||||
forceHttps?: boolean;
|
forceHttps?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// @todo: move to App
|
||||||
export type BkndConfig<Env = any> = {
|
export type BkndConfig<Env = any> = {
|
||||||
app: CreateAppConfig | ((env: Env) => CreateAppConfig);
|
app: CreateAppConfig | ((env: Env) => CreateAppConfig);
|
||||||
setAdminHtml?: boolean;
|
setAdminHtml?: boolean;
|
||||||
|
|||||||
@@ -1,16 +1,9 @@
|
|||||||
import { type AuthAction, Authenticator, type ProfileExchange, Role, type Strategy } from "auth";
|
import { type AuthAction, Authenticator, type ProfileExchange, Role, type Strategy } from "auth";
|
||||||
import { Exception } from "core";
|
import { Exception } from "core";
|
||||||
import { transformObject } from "core/utils";
|
import { type Static, secureRandomString, transformObject } from "core/utils";
|
||||||
import {
|
import { type Entity, EntityIndex, type EntityManager } from "data";
|
||||||
type Entity,
|
|
||||||
EntityIndex,
|
|
||||||
type EntityManager,
|
|
||||||
EnumField,
|
|
||||||
type Field,
|
|
||||||
type Mutator
|
|
||||||
} from "data";
|
|
||||||
import { type FieldSchema, entity, enumm, make, text } from "data/prototype";
|
import { type FieldSchema, entity, enumm, make, text } from "data/prototype";
|
||||||
import { cloneDeep, mergeWith, omit, pick } from "lodash-es";
|
import { pick } from "lodash-es";
|
||||||
import { Module } from "modules/Module";
|
import { Module } from "modules/Module";
|
||||||
import { AuthController } from "./api/AuthController";
|
import { AuthController } from "./api/AuthController";
|
||||||
import { type AppAuthSchema, STRATEGIES, authConfigSchema } from "./auth-schema";
|
import { type AppAuthSchema, STRATEGIES, authConfigSchema } from "./auth-schema";
|
||||||
@@ -22,10 +15,25 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AuthSchema = Static<typeof authConfigSchema>;
|
||||||
|
|
||||||
export class AppAuth extends Module<typeof authConfigSchema> {
|
export class AppAuth extends Module<typeof authConfigSchema> {
|
||||||
private _authenticator?: Authenticator;
|
private _authenticator?: Authenticator;
|
||||||
cache: Record<string, any> = {};
|
cache: Record<string, any> = {};
|
||||||
|
|
||||||
|
override async onBeforeUpdate(from: AuthSchema, to: AuthSchema) {
|
||||||
|
const defaultSecret = authConfigSchema.properties.jwt.properties.secret.default;
|
||||||
|
|
||||||
|
if (!from.enabled && to.enabled) {
|
||||||
|
if (to.jwt.secret === defaultSecret) {
|
||||||
|
console.warn("No JWT secret provided, generating a random one");
|
||||||
|
to.jwt.secret = secureRandomString(64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
|
||||||
override async build() {
|
override async build() {
|
||||||
if (!this.config.enabled) {
|
if (!this.config.enabled) {
|
||||||
this.setBuilt();
|
this.setBuilt();
|
||||||
@@ -46,14 +54,15 @@ export class AppAuth extends Module<typeof authConfigSchema> {
|
|||||||
return new STRATEGIES[strategy.type].cls(strategy.config as any);
|
return new STRATEGIES[strategy.type].cls(strategy.config as any);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Could not build strategy ${String(name)} with config ${JSON.stringify(strategy.config)}`
|
`Could not build strategy ${String(
|
||||||
|
name
|
||||||
|
)} with config ${JSON.stringify(strategy.config)}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const { fields, ...jwt } = this.config.jwt;
|
|
||||||
this._authenticator = new Authenticator(strategies, this.resolveUser.bind(this), {
|
this._authenticator = new Authenticator(strategies, this.resolveUser.bind(this), {
|
||||||
jwt
|
jwt: this.config.jwt
|
||||||
});
|
});
|
||||||
|
|
||||||
this.registerEntities();
|
this.registerEntities();
|
||||||
@@ -124,7 +133,11 @@ export class AppAuth extends Module<typeof authConfigSchema> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async login(strategy: Strategy, identifier: string, profile: ProfileExchange) {
|
private async login(strategy: Strategy, identifier: string, profile: ProfileExchange) {
|
||||||
console.log("--- trying to login", { strategy: strategy.getName(), identifier, profile });
|
/*console.log("--- trying to login", {
|
||||||
|
strategy: strategy.getName(),
|
||||||
|
identifier,
|
||||||
|
profile
|
||||||
|
});*/
|
||||||
if (!("email" in profile)) {
|
if (!("email" in profile)) {
|
||||||
throw new Exception("Profile must have email");
|
throw new Exception("Profile must have email");
|
||||||
}
|
}
|
||||||
@@ -263,17 +276,9 @@ export class AppAuth extends Module<typeof authConfigSchema> {
|
|||||||
return this.configDefault;
|
return this.configDefault;
|
||||||
}
|
}
|
||||||
|
|
||||||
const obj = {
|
return {
|
||||||
...this.config,
|
...this.config,
|
||||||
...this.authenticator.toJSON(secrets)
|
...this.authenticator.toJSON(secrets)
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
|
||||||
...obj,
|
|
||||||
jwt: {
|
|
||||||
...obj.jwt,
|
|
||||||
fields: this.config.jwt.fields
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,15 +51,7 @@ export const authConfigSchema = Type.Object(
|
|||||||
enabled: Type.Boolean({ default: false }),
|
enabled: Type.Boolean({ default: false }),
|
||||||
basepath: Type.String({ default: "/api/auth" }),
|
basepath: Type.String({ default: "/api/auth" }),
|
||||||
entity_name: Type.String({ default: "users" }),
|
entity_name: Type.String({ default: "users" }),
|
||||||
jwt: Type.Composite(
|
jwt: jwtConfig,
|
||||||
[
|
|
||||||
jwtConfig,
|
|
||||||
Type.Object({
|
|
||||||
fields: Type.Array(Type.String(), { default: ["id", "email", "role"] })
|
|
||||||
})
|
|
||||||
],
|
|
||||||
{ default: {}, additionalProperties: false }
|
|
||||||
),
|
|
||||||
strategies: Type.Optional(
|
strategies: Type.Optional(
|
||||||
StringRecord(strategiesSchema, {
|
StringRecord(strategiesSchema, {
|
||||||
title: "Strategies",
|
title: "Strategies",
|
||||||
|
|||||||
@@ -41,10 +41,11 @@ export interface UserPool<Fields = "id" | "email" | "username"> {
|
|||||||
export const jwtConfig = Type.Object(
|
export const jwtConfig = Type.Object(
|
||||||
{
|
{
|
||||||
// @todo: autogenerate a secret if not present. But it must be persisted from AppAuth
|
// @todo: autogenerate a secret if not present. But it must be persisted from AppAuth
|
||||||
secret: Type.String({ default: "secret" }),
|
secret: Type.String({ default: "" }),
|
||||||
alg: Type.Optional(Type.String({ enum: ["HS256"], default: "HS256" })),
|
alg: Type.Optional(Type.String({ enum: ["HS256"], default: "HS256" })),
|
||||||
expiresIn: Type.Optional(Type.String()),
|
expiresIn: Type.Optional(Type.String()),
|
||||||
issuer: Type.Optional(Type.String())
|
issuer: Type.Optional(Type.String()),
|
||||||
|
fields: Type.Array(Type.String(), { default: ["id", "email", "role"] })
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
default: {},
|
default: {},
|
||||||
@@ -74,11 +75,6 @@ export class Authenticator<Strategies extends Record<string, Strategy> = Record<
|
|||||||
this.userResolver = userResolver ?? (async (a, s, i, p) => p as any);
|
this.userResolver = userResolver ?? (async (a, s, i, p) => p as any);
|
||||||
this.strategies = strategies as Strategies;
|
this.strategies = strategies as Strategies;
|
||||||
this.config = parse(authenticatorConfig, config ?? {});
|
this.config = parse(authenticatorConfig, config ?? {});
|
||||||
|
|
||||||
/*const secret = String(this.config.jwt.secret);
|
|
||||||
if (secret === "secret" || secret.length === 0) {
|
|
||||||
this.config.jwt.secret = randomString(64, true);
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async resolve(
|
async resolve(
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ import {
|
|||||||
|
|
||||||
export type SchemaObjectOptions<Schema extends TObject> = {
|
export type SchemaObjectOptions<Schema extends TObject> = {
|
||||||
onUpdate?: (config: Static<Schema>) => void | Promise<void>;
|
onUpdate?: (config: Static<Schema>) => void | Promise<void>;
|
||||||
|
onBeforeUpdate?: (
|
||||||
|
from: Static<Schema>,
|
||||||
|
to: Static<Schema>
|
||||||
|
) => Static<Schema> | Promise<Static<Schema>>;
|
||||||
restrictPaths?: string[];
|
restrictPaths?: string[];
|
||||||
overwritePaths?: (RegExp | string)[];
|
overwritePaths?: (RegExp | string)[];
|
||||||
forceParse?: boolean;
|
forceParse?: boolean;
|
||||||
@@ -45,6 +49,13 @@ export class SchemaObject<Schema extends TObject> {
|
|||||||
return this._default;
|
return this._default;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async onBeforeUpdate(from: Static<Schema>, to: Static<Schema>): Promise<Static<Schema>> {
|
||||||
|
if (this.options?.onBeforeUpdate) {
|
||||||
|
return this.options.onBeforeUpdate(from, to);
|
||||||
|
}
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
|
||||||
get(options?: { stripMark?: boolean }): Static<Schema> {
|
get(options?: { stripMark?: boolean }): Static<Schema> {
|
||||||
if (options?.stripMark) {
|
if (options?.stripMark) {
|
||||||
return stripMark(this._config);
|
return stripMark(this._config);
|
||||||
@@ -58,8 +69,10 @@ export class SchemaObject<Schema extends TObject> {
|
|||||||
forceParse: true,
|
forceParse: true,
|
||||||
skipMark: this.isForceParse()
|
skipMark: this.isForceParse()
|
||||||
});
|
});
|
||||||
this._value = valid;
|
const updatedConfig = noEmit ? valid : await this.onBeforeUpdate(this._config, valid);
|
||||||
this._config = Object.freeze(valid);
|
|
||||||
|
this._value = updatedConfig;
|
||||||
|
this._config = Object.freeze(updatedConfig);
|
||||||
|
|
||||||
if (noEmit !== true) {
|
if (noEmit !== true) {
|
||||||
await this.options?.onUpdate?.(this._config);
|
await this.options?.onUpdate?.(this._config);
|
||||||
@@ -134,7 +147,7 @@ export class SchemaObject<Schema extends TObject> {
|
|||||||
overwritePaths.length > 1
|
overwritePaths.length > 1
|
||||||
? overwritePaths.filter((k) =>
|
? overwritePaths.filter((k) =>
|
||||||
overwritePaths.some((k2) => {
|
overwritePaths.some((k2) => {
|
||||||
console.log("keep?", { k, k2 }, k2 !== k && k2.startsWith(k));
|
//console.log("keep?", { k, k2 }, k2 !== k && k2.startsWith(k));
|
||||||
return k2 !== k && k2.startsWith(k);
|
return k2 !== k && k2.startsWith(k);
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -27,3 +27,9 @@ export async function checksum(s: any) {
|
|||||||
const o = typeof s === "string" ? s : JSON.stringify(s);
|
const o = typeof s === "string" ? s : JSON.stringify(s);
|
||||||
return await digest("SHA-1", o);
|
return await digest("SHA-1", o);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function secureRandomString(length: number): string {
|
||||||
|
const array = new Uint8Array(length);
|
||||||
|
crypto.getRandomValues(array);
|
||||||
|
return Array.from(array, (byte) => String.fromCharCode(33 + (byte % 94))).join("");
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export type ModuleBuildContext = {
|
|||||||
guard: Guard;
|
guard: Guard;
|
||||||
};
|
};
|
||||||
|
|
||||||
export abstract class Module<Schema extends TSchema = TSchema> {
|
export abstract class Module<Schema extends TSchema = TSchema, ConfigSchema = Static<Schema>> {
|
||||||
private _built = false;
|
private _built = false;
|
||||||
private _schema: SchemaObject<ReturnType<(typeof this)["getSchema"]>>;
|
private _schema: SchemaObject<ReturnType<(typeof this)["getSchema"]>>;
|
||||||
private _listener: any = () => null;
|
private _listener: any = () => null;
|
||||||
@@ -28,10 +28,15 @@ export abstract class Module<Schema extends TSchema = TSchema> {
|
|||||||
await this._listener(c);
|
await this._listener(c);
|
||||||
},
|
},
|
||||||
restrictPaths: this.getRestrictedPaths(),
|
restrictPaths: this.getRestrictedPaths(),
|
||||||
overwritePaths: this.getOverwritePaths()
|
overwritePaths: this.getOverwritePaths(),
|
||||||
|
onBeforeUpdate: this.onBeforeUpdate.bind(this)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onBeforeUpdate(from: ConfigSchema, to: ConfigSchema): ConfigSchema | Promise<ConfigSchema> {
|
||||||
|
return to;
|
||||||
|
}
|
||||||
|
|
||||||
setListener(listener: (c: ReturnType<(typeof this)["getSchema"]>) => void | Promise<void>) {
|
setListener(listener: (c: ReturnType<(typeof this)["getSchema"]>) => void | Promise<void>) {
|
||||||
this._listener = listener;
|
this._listener = listener;
|
||||||
return this;
|
return this;
|
||||||
@@ -92,7 +97,8 @@ export abstract class Module<Schema extends TSchema = TSchema> {
|
|||||||
},
|
},
|
||||||
forceParse: this.useForceParse(),
|
forceParse: this.useForceParse(),
|
||||||
restrictPaths: this.getRestrictedPaths(),
|
restrictPaths: this.getRestrictedPaths(),
|
||||||
overwritePaths: this.getOverwritePaths()
|
overwritePaths: this.getOverwritePaths(),
|
||||||
|
onBeforeUpdate: this.onBeforeUpdate.bind(this)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -66,10 +66,16 @@ export class SystemController implements ClassController {
|
|||||||
console.error(e);
|
console.error(e);
|
||||||
|
|
||||||
if (e instanceof TypeInvalidError) {
|
if (e instanceof TypeInvalidError) {
|
||||||
return c.json({ success: false, errors: e.errors }, { status: 400 });
|
return c.json(
|
||||||
|
{ success: false, type: "type-invalid", errors: e.errors },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (e instanceof Error) {
|
||||||
|
return c.json({ success: false, type: "error", error: e.message }, { status: 500 });
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.json({ success: false }, { status: 500 });
|
return c.json({ success: false, type: "unknown" }, { status: 500 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { set } from "lodash-es";
|
import { type NotificationData, notifications } from "@mantine/notifications";
|
||||||
import type { ModuleConfigs } from "../../../modules";
|
import type { ModuleConfigs } from "../../../modules";
|
||||||
import type { AppQueryClient } from "../utils/AppQueryClient";
|
import type { AppQueryClient } from "../utils/AppQueryClient";
|
||||||
|
|
||||||
@@ -12,6 +12,25 @@ export type TSchemaActions = ReturnType<typeof getSchemaActions>;
|
|||||||
export function getSchemaActions({ client, setSchema }: SchemaActionsProps) {
|
export function getSchemaActions({ client, setSchema }: SchemaActionsProps) {
|
||||||
const baseUrl = client.baseUrl;
|
const baseUrl = client.baseUrl;
|
||||||
const token = client.auth().state()?.token;
|
const token = client.auth().state()?.token;
|
||||||
|
|
||||||
|
async function displayError(action: string, module: string, res: Response, path?: string) {
|
||||||
|
const notification_data: NotificationData = {
|
||||||
|
id: "schema-error-" + [action, module, path].join("-"),
|
||||||
|
title: `Config update failed${path ? ": " + path : ""}`,
|
||||||
|
message: "Failed to complete config update",
|
||||||
|
color: "red",
|
||||||
|
position: "top-right",
|
||||||
|
withCloseButton: true,
|
||||||
|
autoClose: false
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const { error } = (await res.json()) as any;
|
||||||
|
notifications.show({ ...notification_data, message: error });
|
||||||
|
} catch (e) {
|
||||||
|
notifications.show(notification_data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
set: async <Module extends keyof ModuleConfigs>(
|
set: async <Module extends keyof ModuleConfigs>(
|
||||||
module: keyof ModuleConfigs,
|
module: keyof ModuleConfigs,
|
||||||
@@ -46,6 +65,8 @@ export function getSchemaActions({ client, setSchema }: SchemaActionsProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return data.success;
|
return data.success;
|
||||||
|
} else {
|
||||||
|
await displayError("set", module, res);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -80,6 +101,8 @@ export function getSchemaActions({ client, setSchema }: SchemaActionsProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return data.success;
|
return data.success;
|
||||||
|
} else {
|
||||||
|
await displayError("patch", module, res, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -114,6 +137,8 @@ export function getSchemaActions({ client, setSchema }: SchemaActionsProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return data.success;
|
return data.success;
|
||||||
|
} else {
|
||||||
|
await displayError("overwrite", module, res, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -149,6 +174,8 @@ export function getSchemaActions({ client, setSchema }: SchemaActionsProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return data.success;
|
return data.success;
|
||||||
|
} else {
|
||||||
|
await displayError("add", module, res, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -182,6 +209,8 @@ export function getSchemaActions({ client, setSchema }: SchemaActionsProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return data.success;
|
return data.success;
|
||||||
|
} else {
|
||||||
|
await displayError("remove", module, res, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { readFile } from "node:fs/promises";
|
import { readFile } from "node:fs/promises";
|
||||||
import { serveStatic } from "@hono/node-server/serve-static";
|
import { serveStatic } from "@hono/node-server/serve-static";
|
||||||
import { createClient } from "@libsql/client/node";
|
import { createClient } from "@libsql/client/node";
|
||||||
import { App, type BkndConfig } from "./src";
|
import { App, type BkndConfig, type CreateAppConfig } from "./src";
|
||||||
import { LibsqlConnection } from "./src/data";
|
import { LibsqlConnection } from "./src/data";
|
||||||
import { StorageLocalAdapter } from "./src/media/storage/adapters/StorageLocalAdapter";
|
import { StorageLocalAdapter } from "./src/media/storage/adapters/StorageLocalAdapter";
|
||||||
import { registries } from "./src/modules/registries";
|
import { registries } from "./src/modules/registries";
|
||||||
@@ -26,14 +26,14 @@ window.__vite_plugin_react_preamble_installed__ = true
|
|||||||
|
|
||||||
function createApp(config: BkndConfig, env: any) {
|
function createApp(config: BkndConfig, env: any) {
|
||||||
const create_config = typeof config.app === "function" ? config.app(env) : config.app;
|
const create_config = typeof config.app === "function" ? config.app(env) : config.app;
|
||||||
return App.create(create_config);
|
return App.create(create_config as CreateAppConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
function setAppBuildListener(app: App, config: BkndConfig, html: string) {
|
function setAppBuildListener(app: App, config: BkndConfig, html: string) {
|
||||||
app.emgr.on(
|
app.emgr.on(
|
||||||
"app-built",
|
"app-built",
|
||||||
async () => {
|
async () => {
|
||||||
await config.onBuilt?.(app);
|
await config.onBuilt?.(app as any);
|
||||||
app.module.server.setAdminHtml(html);
|
app.module.server.setAdminHtml(html);
|
||||||
app.module.server.client.get("/assets/!*", serveStatic({ root: "./" }));
|
app.module.server.client.get("/assets/!*", serveStatic({ root: "./" }));
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user