public commit

This commit is contained in:
dswbx
2024-11-16 12:01:47 +01:00
commit 90f80c4280
582 changed files with 49291 additions and 0 deletions

View 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;
}
};
}

View 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 };
};

View 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
};
}

View 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.");
}
}
}
};
}

View 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 };
}

View 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()
};
}