mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-19 13:56:04 +00:00
public commit
This commit is contained in:
190
app/src/ui/client/schema/actions.ts
Normal file
190
app/src/ui/client/schema/actions.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
import { set } from "lodash-es";
|
||||
import type { ModuleConfigs } from "../../../modules";
|
||||
import type { AppQueryClient } from "../utils/AppQueryClient";
|
||||
|
||||
export type SchemaActionsProps = {
|
||||
client: AppQueryClient;
|
||||
setSchema: React.Dispatch<React.SetStateAction<any>>;
|
||||
};
|
||||
|
||||
export type TSchemaActions = ReturnType<typeof getSchemaActions>;
|
||||
|
||||
export function getSchemaActions({ client, setSchema }: SchemaActionsProps) {
|
||||
const baseUrl = client.baseUrl;
|
||||
const token = client.auth().state()?.token;
|
||||
return {
|
||||
set: async <Module extends keyof ModuleConfigs>(
|
||||
module: keyof ModuleConfigs,
|
||||
value: ModuleConfigs[Module],
|
||||
force?: boolean
|
||||
) => {
|
||||
const res = await fetch(
|
||||
`${baseUrl}/api/system/config/set/${module}?force=${force ? 1 : 0}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify(value)
|
||||
}
|
||||
);
|
||||
if (res.ok) {
|
||||
const data = (await res.json()) as any;
|
||||
console.log("update config set", module, data);
|
||||
if (data.success) {
|
||||
setSchema((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
config: {
|
||||
...prev.config,
|
||||
[module]: data.config
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return data.success;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
patch: async <Module extends keyof ModuleConfigs>(
|
||||
module: keyof ModuleConfigs,
|
||||
path: string,
|
||||
value: any
|
||||
): Promise<boolean> => {
|
||||
const res = await fetch(`${baseUrl}/api/system/config/patch/${module}/${path}`, {
|
||||
method: "PATCH",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify(value)
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = (await res.json()) as any;
|
||||
console.log("update config patch", module, path, data);
|
||||
if (data.success) {
|
||||
setSchema((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
config: {
|
||||
...prev.config,
|
||||
[module]: data.config
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return data.success;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
overwrite: async <Module extends keyof ModuleConfigs>(
|
||||
module: keyof ModuleConfigs,
|
||||
path: string,
|
||||
value: any
|
||||
) => {
|
||||
const res = await fetch(`${baseUrl}/api/system/config/overwrite/${module}/${path}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify(value)
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = (await res.json()) as any;
|
||||
console.log("update config overwrite", module, path, data);
|
||||
if (data.success) {
|
||||
setSchema((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
config: {
|
||||
...prev.config,
|
||||
[module]: data.config
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return data.success;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
add: async <Module extends keyof ModuleConfigs>(
|
||||
module: keyof ModuleConfigs,
|
||||
path: string,
|
||||
value: any
|
||||
) => {
|
||||
const res = await fetch(`${baseUrl}/api/system/config/add/${module}/${path}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify(value)
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = (await res.json()) as any;
|
||||
console.log("update config add", module, data);
|
||||
|
||||
if (data.success) {
|
||||
setSchema((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
config: {
|
||||
...prev.config,
|
||||
[module]: data.config
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return data.success;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
remove: async <Module extends keyof ModuleConfigs>(
|
||||
module: keyof ModuleConfigs,
|
||||
path: string
|
||||
) => {
|
||||
const res = await fetch(`${baseUrl}/api/system/config/remove/${module}/${path}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = (await res.json()) as any;
|
||||
console.log("update config remove", module, data);
|
||||
|
||||
if (data.success) {
|
||||
setSchema((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
config: {
|
||||
...prev.config,
|
||||
[module]: data.config
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return data.success;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
107
app/src/ui/client/schema/auth/use-auth.ts
Normal file
107
app/src/ui/client/schema/auth/use-auth.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { Api } from "Api";
|
||||
import type { AuthResponse } from "auth";
|
||||
import type { AppAuthSchema } from "auth/auth-schema";
|
||||
import type { ApiResponse } from "modules/ModuleApi";
|
||||
import { useEffect, useState } from "react";
|
||||
import {
|
||||
createClient,
|
||||
createOrUseClient,
|
||||
queryClient,
|
||||
useBaseUrl,
|
||||
useClient
|
||||
} from "../../ClientProvider";
|
||||
|
||||
type LoginData = {
|
||||
email: string;
|
||||
password: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
type UseAuth = {
|
||||
data: (AuthResponse & { verified: boolean }) | undefined;
|
||||
user: AuthResponse["user"] | undefined;
|
||||
token: AuthResponse["token"] | undefined;
|
||||
verified: boolean;
|
||||
login: (data: LoginData) => Promise<ApiResponse<AuthResponse>>;
|
||||
register: (data: LoginData) => Promise<ApiResponse<AuthResponse>>;
|
||||
logout: () => void;
|
||||
verify: () => void;
|
||||
setToken: (token: string) => void;
|
||||
};
|
||||
|
||||
// @todo: needs to use a specific auth endpoint to get strategy information
|
||||
export const useAuth = (options?: { baseUrl?: string }): UseAuth => {
|
||||
const ctxBaseUrl = useBaseUrl();
|
||||
//const client = useClient();
|
||||
const client = createOrUseClient(options?.baseUrl ? options?.baseUrl : ctxBaseUrl);
|
||||
const authState = client.auth().state();
|
||||
const [authData, setAuthData] = useState<UseAuth["data"]>(authState);
|
||||
const verified = authState?.verified ?? false;
|
||||
|
||||
async function login(input: LoginData) {
|
||||
const res = await client.auth().login(input);
|
||||
if (res.res.ok && res.data && "user" in res.data) {
|
||||
setAuthData(res.data);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
async function register(input: LoginData) {
|
||||
const res = await client.auth().register(input);
|
||||
if (res.res.ok && res.data && "user" in res.data) {
|
||||
setAuthData(res.data);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function setToken(token: string) {
|
||||
setAuthData(client.auth().setToken(token) as any);
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
await client.auth().logout();
|
||||
setAuthData(undefined);
|
||||
queryClient.clear();
|
||||
}
|
||||
|
||||
async function verify() {
|
||||
await client.auth().verify();
|
||||
setAuthData(client.auth().state());
|
||||
}
|
||||
|
||||
return {
|
||||
data: authData,
|
||||
user: authData?.user,
|
||||
token: authData?.token,
|
||||
verified,
|
||||
login,
|
||||
register,
|
||||
logout,
|
||||
setToken,
|
||||
verify
|
||||
};
|
||||
};
|
||||
|
||||
export const useAuthStrategies = (options?: { baseUrl?: string }): {
|
||||
strategies: AppAuthSchema["strategies"];
|
||||
loading: boolean;
|
||||
} => {
|
||||
const [strategies, setStrategies] = useState<AppAuthSchema["strategies"]>();
|
||||
const ctxBaseUrl = useBaseUrl();
|
||||
const api = new Api({
|
||||
host: options?.baseUrl ? options?.baseUrl : ctxBaseUrl,
|
||||
tokenStorage: "localStorage"
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
const res = await api.auth.strategies();
|
||||
console.log("res", res);
|
||||
if (res.res.ok) {
|
||||
setStrategies(res.body.strategies);
|
||||
}
|
||||
})();
|
||||
}, [options?.baseUrl]);
|
||||
|
||||
return { strategies, loading: !strategies };
|
||||
};
|
||||
33
app/src/ui/client/schema/auth/use-bknd-auth.ts
Normal file
33
app/src/ui/client/schema/auth/use-bknd-auth.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { useBknd } from "ui/client";
|
||||
|
||||
export function useBkndAuth() {
|
||||
//const client = useClient();
|
||||
const { config, app, schema, actions: bkndActions } = useBknd();
|
||||
|
||||
const actions = {
|
||||
roles: {
|
||||
add: async (name: string, data: any = {}) => {
|
||||
console.log("add role", name, data);
|
||||
return await bkndActions.add("auth", `roles.${name}`, data);
|
||||
},
|
||||
patch: async (name: string, data: any) => {
|
||||
console.log("patch role", name, data);
|
||||
return await bkndActions.patch("auth", `roles.${name}`, data);
|
||||
},
|
||||
delete: async (name: string) => {
|
||||
console.log("delete role", name);
|
||||
if (window.confirm(`Are you sure you want to delete the role "${name}"?`)) {
|
||||
return await bkndActions.remove("auth", `roles.${name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const $auth = {};
|
||||
|
||||
return {
|
||||
$auth,
|
||||
config: config.auth,
|
||||
schema: schema.auth,
|
||||
actions
|
||||
};
|
||||
}
|
||||
115
app/src/ui/client/schema/data/use-bknd-data.ts
Normal file
115
app/src/ui/client/schema/data/use-bknd-data.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { Type, TypeInvalidError, parse, transformObject } from "core/utils";
|
||||
import type { Entity } from "data";
|
||||
import { AppData } from "data/AppData";
|
||||
import {
|
||||
type TAppDataEntity,
|
||||
type TAppDataEntityFields,
|
||||
type TAppDataField,
|
||||
type TAppDataRelation,
|
||||
entitiesSchema,
|
||||
entityFields,
|
||||
fieldsSchema,
|
||||
relationsSchema
|
||||
} from "data/data-schema";
|
||||
import { useBknd } from "ui/client";
|
||||
import type { TSchemaActions } from "ui/client/schema/actions";
|
||||
|
||||
export function useBkndData() {
|
||||
const { config, app, schema, actions: bkndActions } = useBknd();
|
||||
|
||||
// @todo: potentially store in ref, so it doesn't get recomputed? or use memo?
|
||||
const entities = transformObject(config.data.entities ?? {}, (entity, name) => {
|
||||
return AppData.constructEntity(name, entity);
|
||||
});
|
||||
|
||||
const actions = {
|
||||
entity: {
|
||||
add: async (name: string, data: TAppDataEntity) => {
|
||||
console.log("create entity", { data });
|
||||
const validated = parse(entitiesSchema, data, {
|
||||
skipMark: true,
|
||||
forceParse: true
|
||||
});
|
||||
console.log("validated", validated);
|
||||
// @todo: check for existing?
|
||||
return await bkndActions.add("data", `entities.${name}`, validated);
|
||||
},
|
||||
patch: (entityName: string) => {
|
||||
const entity = entities[entityName];
|
||||
if (!entity) {
|
||||
throw new Error(`Entity "${entityName}" not found`);
|
||||
}
|
||||
|
||||
return {
|
||||
config: async (partial: Partial<TAppDataEntity["config"]>): Promise<boolean> => {
|
||||
console.log("patch config", entityName, partial);
|
||||
return await bkndActions.patch("data", `entities.${entityName}.config`, partial);
|
||||
},
|
||||
fields: entityFieldActions(bkndActions, entityName)
|
||||
};
|
||||
}
|
||||
},
|
||||
relations: {
|
||||
add: async (relation: TAppDataRelation) => {
|
||||
console.log("create relation", { relation });
|
||||
const name = crypto.randomUUID();
|
||||
const validated = parse(Type.Union(relationsSchema), relation, {
|
||||
skipMark: true,
|
||||
forceParse: true
|
||||
});
|
||||
console.log("validated", validated);
|
||||
return await bkndActions.add("data", `relations.${name}`, validated);
|
||||
}
|
||||
}
|
||||
};
|
||||
const $data = {
|
||||
entity: (name: string) => entities[name]
|
||||
};
|
||||
|
||||
return {
|
||||
$data,
|
||||
entities,
|
||||
relations: app.relations,
|
||||
config: config.data,
|
||||
schema: schema.data,
|
||||
actions
|
||||
};
|
||||
}
|
||||
|
||||
function entityFieldActions(bkndActions: TSchemaActions, entityName: string) {
|
||||
return {
|
||||
add: async (name: string, field: TAppDataField) => {
|
||||
console.log("create field", { name, field });
|
||||
const validated = parse(fieldsSchema, field, {
|
||||
skipMark: true,
|
||||
forceParse: true
|
||||
});
|
||||
console.log("validated", validated);
|
||||
return await bkndActions.add("data", `entities.${entityName}.fields.${name}`, validated);
|
||||
},
|
||||
patch: () => null,
|
||||
set: async (fields: TAppDataEntityFields) => {
|
||||
console.log("set fields", entityName, fields);
|
||||
try {
|
||||
const validated = parse(entityFields, fields, {
|
||||
skipMark: true,
|
||||
forceParse: true
|
||||
});
|
||||
const res = await bkndActions.overwrite(
|
||||
"data",
|
||||
`entities.${entityName}.fields`,
|
||||
validated
|
||||
);
|
||||
console.log("res", res);
|
||||
//bkndActions.set("data", "entities", fields);
|
||||
} catch (e) {
|
||||
console.error("error", e);
|
||||
if (e instanceof TypeInvalidError) {
|
||||
alert("Error updating fields: " + e.firstToString());
|
||||
} else {
|
||||
alert("An error occured, check console. There will be nice error handling soon.");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
23
app/src/ui/client/schema/flows/use-flows.ts
Normal file
23
app/src/ui/client/schema/flows/use-flows.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { type Static, parse } from "core/utils";
|
||||
import { type TAppFlowSchema, flowSchema } from "flows/flows-schema";
|
||||
import { useBknd } from "../../BkndProvider";
|
||||
import { useClient } from "../../ClientProvider";
|
||||
|
||||
export function useFlows() {
|
||||
const client = useClient();
|
||||
const { config, app, actions: bkndActions } = useBknd();
|
||||
|
||||
const actions = {
|
||||
flow: {
|
||||
create: async (name: string, data: TAppFlowSchema) => {
|
||||
console.log("would create", name, data);
|
||||
const parsed = parse(flowSchema, data, { skipMark: true, forceParse: true });
|
||||
console.log("parsed", parsed);
|
||||
const res = await bkndActions.add("flows", `flows.${name}`, parsed);
|
||||
console.log("res", res);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return { flows: app.flows, config: config.flows, actions };
|
||||
}
|
||||
40
app/src/ui/client/schema/system/use-bknd-system.ts
Normal file
40
app/src/ui/client/schema/system/use-bknd-system.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { useBknd } from "ui/client";
|
||||
|
||||
export function useBkndSystem() {
|
||||
const { config, schema, actions: bkndActions } = useBknd();
|
||||
const theme = config.server.admin.color_scheme ?? "light";
|
||||
|
||||
const actions = {
|
||||
theme: {
|
||||
set: async (scheme: "light" | "dark") => {
|
||||
return await bkndActions.patch("server", "admin", {
|
||||
color_scheme: scheme
|
||||
});
|
||||
},
|
||||
toggle: async () => {
|
||||
return await bkndActions.patch("server", "admin", {
|
||||
color_scheme: theme === "light" ? "dark" : "light"
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
const $system = {};
|
||||
|
||||
return {
|
||||
$system,
|
||||
config: config.server,
|
||||
schema: schema.server,
|
||||
theme,
|
||||
actions
|
||||
};
|
||||
}
|
||||
|
||||
export function useBkndSystemTheme() {
|
||||
const $sys = useBkndSystem();
|
||||
|
||||
return {
|
||||
theme: $sys.theme,
|
||||
set: $sys.actions.theme.set,
|
||||
toggle: () => $sys.actions.theme.toggle()
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user