diff --git a/app/__test__/app/repro.spec.ts b/app/__test__/app/repro.spec.ts new file mode 100644 index 0000000..0d025b9 --- /dev/null +++ b/app/__test__/app/repro.spec.ts @@ -0,0 +1,73 @@ +import { describe, expect, test } from "bun:test"; +import { createApp, registries } from "../../src"; +import * as proto from "../../src/data/prototype"; +import { StorageLocalAdapter } from "../../src/media/storage/adapters/StorageLocalAdapter"; + +describe("repros", async () => { + /** + * steps: + * 1. enable media + * 2. create 'test' entity + * 3. add media to 'test' + * + * There was an issue that AppData had old configs because of system entity "media" + */ + test("registers media entity correctly to relate to it", async () => { + registries.media.register("local", StorageLocalAdapter); + const app = createApp(); + await app.build(); + + { + // 1. enable media + const [, config] = await app.module.media.schema().patch("", { + enabled: true, + adapter: { + type: "local", + config: { + path: "./" + } + } + }); + + expect(config.enabled).toBe(true); + } + + { + // 2. create 'test' entity + await app.module.data.schema().patch( + "entities.test", + proto + .entity("test", { + content: proto.text() + }) + .toJSON() + ); + expect(app.em.entities.map((e) => e.name)).toContain("test"); + } + + { + await app.module.data.schema().patch("entities.test.fields.files", { + type: "media", + config: { + required: false, + fillable: ["update"], + hidden: false, + mime_types: [], + virtual: true, + entity: "test" + } + }); + + expect( + app.module.data.schema().patch("relations.000", { + type: "poly", + source: "test", + target: "media", + config: { mappedBy: "files" } + }) + ).resolves.toBeDefined(); + } + + expect(app.em.entities.map((e) => e.name)).toEqual(["media", "test"]); + }); +}); diff --git a/app/src/App.ts b/app/src/App.ts index 2df3e84..b98fc67 100644 --- a/app/src/App.ts +++ b/app/src/App.ts @@ -1,8 +1,5 @@ import type { CreateUserPayload } from "auth/AppAuth"; -import { auth } from "auth/middlewares"; -import { config } from "core"; import { Event } from "core/events"; -import { patternMatch } from "core/utils"; import { Connection, type LibSqlCredentials, LibsqlConnection } from "data"; import { type InitialModuleConfigs, diff --git a/app/src/core/object/SchemaObject.ts b/app/src/core/object/SchemaObject.ts index aad5b14..7c2e926 100644 --- a/app/src/core/object/SchemaObject.ts +++ b/app/src/core/object/SchemaObject.ts @@ -130,7 +130,10 @@ export class SchemaObject { //console.log("overwritePaths", this.options?.overwritePaths); if (this.options?.overwritePaths) { - const keys = getFullPathKeys(value).map((k) => path + "." + k); + const keys = getFullPathKeys(value).map((k) => { + // only prepend path if given + return path.length > 0 ? path + "." + k : k; + }); const overwritePaths = keys.filter((k) => { return this.options?.overwritePaths?.some((p) => { if (typeof p === "string") { diff --git a/app/src/core/utils/typebox/index.ts b/app/src/core/utils/typebox/index.ts index 2e08d7a..a793e33 100644 --- a/app/src/core/utils/typebox/index.ts +++ b/app/src/core/utils/typebox/index.ts @@ -115,6 +115,7 @@ export function parse( } else if (options?.onError) { options.onError(Errors(schema, data)); } else { + //console.warn("errors", JSON.stringify([...Errors(schema, data)], null, 2)); throw new TypeInvalidError(schema, data); } diff --git a/app/src/media/AppMedia.ts b/app/src/media/AppMedia.ts index c759479..564b008 100644 --- a/app/src/media/AppMedia.ts +++ b/app/src/media/AppMedia.ts @@ -53,6 +53,8 @@ export class AppMedia extends Module { index(media).on(["path"], true).on(["reference"]); }) ); + + this.setBuilt(); } catch (e) { console.error(e); throw new Error( diff --git a/app/src/modules/ModuleManager.ts b/app/src/modules/ModuleManager.ts index d5840c2..26f4d21 100644 --- a/app/src/modules/ModuleManager.ts +++ b/app/src/modules/ModuleManager.ts @@ -329,6 +329,9 @@ export class ModuleManager { } } + // re-apply configs to all modules (important for system entities) + this.setConfigs(configs); + // @todo: cleanup old versions? this.logger.clear(); diff --git a/app/src/ui/modules/data/components/schema/create-modal/step.create.tsx b/app/src/ui/modules/data/components/schema/create-modal/step.create.tsx index 371646e..470d214 100644 --- a/app/src/ui/modules/data/components/schema/create-modal/step.create.tsx +++ b/app/src/ui/modules/data/components/schema/create-modal/step.create.tsx @@ -8,7 +8,6 @@ import { } from "@tabler/icons-react"; import { ucFirst } from "core/utils"; import { useEffect, useState } from "react"; -import { TbCirclesRelation, TbSettings } from "react-icons/tb"; import { twMerge } from "tailwind-merge"; import { useBknd } from "ui/client/bknd"; import { useBkndData } from "ui/client/schema/data/use-bknd-data"; @@ -76,6 +75,10 @@ export function StepCreate() { try { const res = await item.run(); setStates((prev) => [...prev, res]); + if (res !== true) { + // make sure to break out + break; + } } catch (e) { setStates((prev) => [...prev, (e as any).message]); } @@ -147,12 +150,14 @@ const SummaryItem: React.FC = ({ }) => { const [expanded, handlers] = useDisclosure(initialExpanded); const error = typeof state !== "undefined" && state !== true; + const done = state === true; return (
diff --git a/app/src/ui/modules/data/components/schema/create-modal/templates/media/template.media.component.tsx b/app/src/ui/modules/data/components/schema/create-modal/templates/media/template.media.component.tsx index ea4d149..4038880 100644 --- a/app/src/ui/modules/data/components/schema/create-modal/templates/media/template.media.component.tsx +++ b/app/src/ui/modules/data/components/schema/create-modal/templates/media/template.media.component.tsx @@ -9,6 +9,7 @@ import { transformObject } from "core/utils"; import type { MediaFieldConfig } from "media/MediaField"; +import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { useBknd } from "ui/client/bknd"; import { MantineNumberInput } from "ui/components/form/hook-form-mantine/MantineNumberInput"; @@ -35,14 +36,15 @@ export function TemplateMediaComponent() { const { register, handleSubmit, - formState: { isValid }, - setValue, + formState: { isValid, errors }, watch, control } = useForm({ + mode: "onChange", resolver: typeboxResolver(schema), defaultValues: Default(schema, state.initial ?? {}) as TCreateModalMediaSchema }); + const [forbidden, setForbidden] = useState(false); const { config } = useBknd(); const media_enabled = config.media.enabled ?? false; @@ -51,13 +53,16 @@ export function TemplateMediaComponent() { name !== media_entity ? entity : undefined ); const data = watch(); + const forbidden_field_names = Object.keys(config.data.entities?.[data.entity]?.fields ?? {}); + + useEffect(() => { + setForbidden(forbidden_field_names.includes(data.name)); + }, [forbidden_field_names, data.name]); async function handleCreate() { - if (isValid) { - console.log("data", data); + if (isValid && !forbidden) { const { field, relation } = convert(media_entity, data); - console.log("state", { field, relation }); setState((prev) => ({ ...prev, fields: { create: [field] }, @@ -120,6 +125,13 @@ export function TemplateMediaComponent() { data.entity ? data.entity : "the entity" }.`} {...register("name")} + error={ + errors.name?.message + ? errors.name?.message + : forbidden + ? `Property "${data.name}" already exists on entity ${data.entity}` + : undefined + } />
{/*

step template media

@@ -129,7 +141,7 @@ export function TemplateMediaComponent() { ({ console.log("save:success", success); if (success) { if (options?.reloadOnSave) { - window.location.reload(); - //await actions.reload(); + //window.location.reload(); + await actions.reload(); + setSubmitting(false); } } else { setSubmitting(false); diff --git a/biome.json b/biome.json index 34a1fb1..3274f11 100644 --- a/biome.json +++ b/biome.json @@ -40,6 +40,7 @@ }, "linter": { "enabled": true, + "ignore": ["**/*.spec.ts"], "rules": { "recommended": true, "a11y": {