mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-17 12:56:05 +00:00
added auth strategies migration, fixed rebuild of modules on migrations
This commit is contained in:
54
app/__test__/app/App.spec.ts
Normal file
54
app/__test__/app/App.spec.ts
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
import { describe, expect, mock, test } from "bun:test";
|
||||||
|
import type { ModuleBuildContext } from "../../src";
|
||||||
|
import { type App, createApp } from "../../src/App";
|
||||||
|
import * as proto from "../../src/data/prototype";
|
||||||
|
|
||||||
|
describe("App", () => {
|
||||||
|
test("seed includes ctx and app", async () => {
|
||||||
|
const called = mock(() => null);
|
||||||
|
await createApp({
|
||||||
|
options: {
|
||||||
|
seed: async ({ app, ...ctx }) => {
|
||||||
|
called();
|
||||||
|
expect(app).toBeDefined();
|
||||||
|
expect(ctx).toBeDefined();
|
||||||
|
expect(Object.keys(ctx)).toEqual([
|
||||||
|
"connection",
|
||||||
|
"server",
|
||||||
|
"em",
|
||||||
|
"emgr",
|
||||||
|
"guard",
|
||||||
|
"flags",
|
||||||
|
"logger",
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).build();
|
||||||
|
expect(called).toHaveBeenCalled();
|
||||||
|
|
||||||
|
const app = createApp({
|
||||||
|
initialConfig: {
|
||||||
|
data: proto
|
||||||
|
.em({
|
||||||
|
todos: proto.entity("todos", {
|
||||||
|
title: proto.text(),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.toJSON(),
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
//manager: { verbosity: 2 },
|
||||||
|
seed: async ({ app, ...ctx }: ModuleBuildContext & { app: App }) => {
|
||||||
|
await ctx.em.mutator("todos").insertOne({ title: "ctx" });
|
||||||
|
await app.getApi().data.createOne("todos", { title: "api" });
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await app.build();
|
||||||
|
|
||||||
|
const todos = await app.getApi().data.readMany("todos");
|
||||||
|
expect(todos.length).toBe(2);
|
||||||
|
expect(todos[0].title).toBe("ctx");
|
||||||
|
expect(todos[1].title).toBe("api");
|
||||||
|
});
|
||||||
|
});
|
||||||
66
app/__test__/modules/migrations/migrations.spec.ts
Normal file
66
app/__test__/modules/migrations/migrations.spec.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import { describe, expect, test } from "bun:test";
|
||||||
|
import { type InitialModuleConfigs, createApp } from "../../../src";
|
||||||
|
|
||||||
|
import type { Kysely } from "kysely";
|
||||||
|
import { getDummyConnection } from "../../helper";
|
||||||
|
import v7 from "./samples/v7.json";
|
||||||
|
|
||||||
|
// app expects migratable config to be present in database
|
||||||
|
async function createVersionedApp(config: InitialModuleConfigs) {
|
||||||
|
const { dummyConnection } = getDummyConnection();
|
||||||
|
|
||||||
|
if (!("version" in config)) throw new Error("config must have a version");
|
||||||
|
const { version, ...rest } = config;
|
||||||
|
|
||||||
|
const app = createApp({ connection: dummyConnection });
|
||||||
|
await app.build();
|
||||||
|
|
||||||
|
const qb = app.modules.ctx().connection.kysely as Kysely<any>;
|
||||||
|
const current = await qb
|
||||||
|
.selectFrom("__bknd")
|
||||||
|
.selectAll()
|
||||||
|
.where("type", "=", "config")
|
||||||
|
.executeTakeFirst();
|
||||||
|
|
||||||
|
await qb
|
||||||
|
.updateTable("__bknd")
|
||||||
|
.set("json", JSON.stringify(rest))
|
||||||
|
.set("version", 7)
|
||||||
|
.where("id", "=", current!.id)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
const app2 = createApp({
|
||||||
|
connection: dummyConnection,
|
||||||
|
});
|
||||||
|
await app2.build();
|
||||||
|
return app2;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("Migrations", () => {
|
||||||
|
/**
|
||||||
|
* updated auth strategies to have "enabled" prop
|
||||||
|
* by default, migration should make all available strategies enabled
|
||||||
|
*/
|
||||||
|
test("migration from 7 to 8", async () => {
|
||||||
|
expect(v7.version).toBe(7);
|
||||||
|
|
||||||
|
const app = await createVersionedApp(v7);
|
||||||
|
|
||||||
|
expect(app.version()).toBe(8);
|
||||||
|
expect(app.toJSON(true).auth.strategies.password.enabled).toBe(true);
|
||||||
|
|
||||||
|
const req = await app.server.request("/api/auth/password/register", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: "test@test.com",
|
||||||
|
password: "12345678",
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
expect(req.ok).toBe(true);
|
||||||
|
const res = await req.json();
|
||||||
|
expect(res.user.email).toBe("test@test.com");
|
||||||
|
});
|
||||||
|
});
|
||||||
638
app/__test__/modules/migrations/samples/v7.json
Normal file
638
app/__test__/modules/migrations/samples/v7.json
Normal file
@@ -0,0 +1,638 @@
|
|||||||
|
{
|
||||||
|
"version": 7,
|
||||||
|
"server": {
|
||||||
|
"admin": {
|
||||||
|
"basepath": "",
|
||||||
|
"logo_return_path": "/",
|
||||||
|
"color_scheme": "light"
|
||||||
|
},
|
||||||
|
"cors": {
|
||||||
|
"origin": "*",
|
||||||
|
"allow_methods": ["GET", "POST", "PATCH", "PUT", "DELETE"],
|
||||||
|
"allow_headers": [
|
||||||
|
"Content-Type",
|
||||||
|
"Content-Length",
|
||||||
|
"Authorization",
|
||||||
|
"Accept"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"basepath": "/api/data",
|
||||||
|
"entities": {
|
||||||
|
"products": {
|
||||||
|
"type": "regular",
|
||||||
|
"fields": {
|
||||||
|
"id": {
|
||||||
|
"type": "primary",
|
||||||
|
"config": {
|
||||||
|
"fillable": false,
|
||||||
|
"required": false,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"required": true,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"brand": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"required": true,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"currency": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"price": {
|
||||||
|
"type": "number",
|
||||||
|
"config": {
|
||||||
|
"required": true,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"price_compare": {
|
||||||
|
"type": "number",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": ["table"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"html_config": {
|
||||||
|
"element": "input"
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": ["table"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "date",
|
||||||
|
"config": {
|
||||||
|
"type": "date",
|
||||||
|
"required": false,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": ["table"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"html_config": {
|
||||||
|
"element": "textarea",
|
||||||
|
"props": {
|
||||||
|
"rows": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": false,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": ["table"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"images": {
|
||||||
|
"type": "media",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": ["update"],
|
||||||
|
"hidden": false,
|
||||||
|
"mime_types": [],
|
||||||
|
"virtual": true,
|
||||||
|
"entity": "products"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"identifier": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"type": "jsonschema",
|
||||||
|
"config": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"size": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": false,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": ["table"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"sort_field": "id",
|
||||||
|
"sort_dir": "asc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"media": {
|
||||||
|
"type": "system",
|
||||||
|
"fields": {
|
||||||
|
"id": {
|
||||||
|
"type": "primary",
|
||||||
|
"config": {
|
||||||
|
"fillable": false,
|
||||||
|
"required": false,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"path": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"required": true,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"folder": {
|
||||||
|
"type": "boolean",
|
||||||
|
"config": {
|
||||||
|
"default_value": false,
|
||||||
|
"hidden": true,
|
||||||
|
"fillable": ["create"],
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mime_type": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"type": "number",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scope": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"hidden": true,
|
||||||
|
"fillable": ["create"],
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"etag": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"modified_at": {
|
||||||
|
"type": "date",
|
||||||
|
"config": {
|
||||||
|
"type": "datetime",
|
||||||
|
"required": false,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"reference": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"entity_id": {
|
||||||
|
"type": "number",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"type": "json",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"sort_field": "id",
|
||||||
|
"sort_dir": "asc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"users": {
|
||||||
|
"type": "system",
|
||||||
|
"fields": {
|
||||||
|
"id": {
|
||||||
|
"type": "primary",
|
||||||
|
"config": {
|
||||||
|
"fillable": false,
|
||||||
|
"required": false,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"required": true,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"strategy": {
|
||||||
|
"type": "enum",
|
||||||
|
"config": {
|
||||||
|
"options": {
|
||||||
|
"type": "strings",
|
||||||
|
"values": ["password"]
|
||||||
|
},
|
||||||
|
"required": true,
|
||||||
|
"fillable": ["create"],
|
||||||
|
"hidden": ["update", "form"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"strategy_value": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"fillable": ["create"],
|
||||||
|
"hidden": ["read", "table", "update", "form"],
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"type": "enum",
|
||||||
|
"config": {
|
||||||
|
"options": {
|
||||||
|
"type": "strings",
|
||||||
|
"values": ["guest", "admin"]
|
||||||
|
},
|
||||||
|
"required": false,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"sort_field": "id",
|
||||||
|
"sort_dir": "asc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"product_likes": {
|
||||||
|
"type": "regular",
|
||||||
|
"fields": {
|
||||||
|
"id": {
|
||||||
|
"type": "primary",
|
||||||
|
"config": {
|
||||||
|
"fillable": false,
|
||||||
|
"required": false,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "date",
|
||||||
|
"config": {
|
||||||
|
"type": "date",
|
||||||
|
"required": false,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"users_id": {
|
||||||
|
"type": "relation",
|
||||||
|
"config": {
|
||||||
|
"label": "User",
|
||||||
|
"required": true,
|
||||||
|
"reference": "users",
|
||||||
|
"target": "users",
|
||||||
|
"target_field": "id",
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false,
|
||||||
|
"on_delete": "set null"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"products_id": {
|
||||||
|
"type": "relation",
|
||||||
|
"config": {
|
||||||
|
"label": "Product",
|
||||||
|
"required": true,
|
||||||
|
"reference": "products",
|
||||||
|
"target": "products",
|
||||||
|
"target_field": "id",
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false,
|
||||||
|
"on_delete": "set null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"name": "Product Likes",
|
||||||
|
"sort_field": "id",
|
||||||
|
"sort_dir": "asc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"boards": {
|
||||||
|
"type": "regular",
|
||||||
|
"fields": {
|
||||||
|
"id": {
|
||||||
|
"type": "primary",
|
||||||
|
"config": {
|
||||||
|
"fillable": false,
|
||||||
|
"required": false,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"private": {
|
||||||
|
"type": "boolean",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "text",
|
||||||
|
"config": {
|
||||||
|
"required": true,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"users_id": {
|
||||||
|
"type": "relation",
|
||||||
|
"config": {
|
||||||
|
"label": "Users",
|
||||||
|
"required": true,
|
||||||
|
"reference": "users",
|
||||||
|
"target": "users",
|
||||||
|
"target_field": "id",
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false,
|
||||||
|
"on_delete": "set null"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"images": {
|
||||||
|
"type": "media",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"fillable": ["update"],
|
||||||
|
"hidden": false,
|
||||||
|
"mime_types": [],
|
||||||
|
"virtual": true,
|
||||||
|
"entity": "boards",
|
||||||
|
"max_items": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cover": {
|
||||||
|
"type": "number",
|
||||||
|
"config": {
|
||||||
|
"default_value": 0,
|
||||||
|
"required": false,
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"sort_field": "id",
|
||||||
|
"sort_dir": "asc"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"boards_products": {
|
||||||
|
"type": "generated",
|
||||||
|
"fields": {
|
||||||
|
"id": {
|
||||||
|
"type": "primary",
|
||||||
|
"config": {
|
||||||
|
"fillable": false,
|
||||||
|
"required": false,
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"boards_id": {
|
||||||
|
"type": "relation",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"reference": "boards",
|
||||||
|
"target": "boards",
|
||||||
|
"target_field": "id",
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false,
|
||||||
|
"on_delete": "set null"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"products_id": {
|
||||||
|
"type": "relation",
|
||||||
|
"config": {
|
||||||
|
"required": false,
|
||||||
|
"reference": "products",
|
||||||
|
"target": "products",
|
||||||
|
"target_field": "id",
|
||||||
|
"fillable": true,
|
||||||
|
"hidden": false,
|
||||||
|
"on_delete": "set null"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"sort_field": "id",
|
||||||
|
"sort_dir": "asc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relations": {
|
||||||
|
"poly_products_media_images": {
|
||||||
|
"type": "poly",
|
||||||
|
"source": "products",
|
||||||
|
"target": "media",
|
||||||
|
"config": {
|
||||||
|
"mappedBy": "images"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"n1_product_likes_users": {
|
||||||
|
"type": "n:1",
|
||||||
|
"source": "product_likes",
|
||||||
|
"target": "users",
|
||||||
|
"config": {
|
||||||
|
"mappedBy": "",
|
||||||
|
"inversedBy": "",
|
||||||
|
"required": true,
|
||||||
|
"with_limit": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"n1_product_likes_products": {
|
||||||
|
"type": "n:1",
|
||||||
|
"source": "product_likes",
|
||||||
|
"target": "products",
|
||||||
|
"config": {
|
||||||
|
"mappedBy": "",
|
||||||
|
"inversedBy": "",
|
||||||
|
"required": true,
|
||||||
|
"with_limit": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"n1_boards_users": {
|
||||||
|
"type": "n:1",
|
||||||
|
"source": "boards",
|
||||||
|
"target": "users",
|
||||||
|
"config": {
|
||||||
|
"mappedBy": "",
|
||||||
|
"inversedBy": "",
|
||||||
|
"required": true,
|
||||||
|
"with_limit": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"poly_boards_media_images": {
|
||||||
|
"type": "poly",
|
||||||
|
"source": "boards",
|
||||||
|
"target": "media",
|
||||||
|
"config": {
|
||||||
|
"mappedBy": "images",
|
||||||
|
"targetCardinality": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mn_boards_products_boards_products,boards_products": {
|
||||||
|
"type": "m:n",
|
||||||
|
"source": "boards",
|
||||||
|
"target": "products",
|
||||||
|
"config": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indices": {
|
||||||
|
"idx_unique_media_path": {
|
||||||
|
"entity": "media",
|
||||||
|
"fields": ["path"],
|
||||||
|
"unique": true
|
||||||
|
},
|
||||||
|
"idx_media_reference": {
|
||||||
|
"entity": "media",
|
||||||
|
"fields": ["reference"],
|
||||||
|
"unique": false
|
||||||
|
},
|
||||||
|
"idx_unique_users_email": {
|
||||||
|
"entity": "users",
|
||||||
|
"fields": ["email"],
|
||||||
|
"unique": true
|
||||||
|
},
|
||||||
|
"idx_users_strategy": {
|
||||||
|
"entity": "users",
|
||||||
|
"fields": ["strategy"],
|
||||||
|
"unique": false
|
||||||
|
},
|
||||||
|
"idx_users_strategy_value": {
|
||||||
|
"entity": "users",
|
||||||
|
"fields": ["strategy_value"],
|
||||||
|
"unique": false
|
||||||
|
},
|
||||||
|
"idx_product_likes_unique_products_id_users_id": {
|
||||||
|
"entity": "product_likes",
|
||||||
|
"fields": ["products_id", "users_id"],
|
||||||
|
"unique": true
|
||||||
|
},
|
||||||
|
"idx_media_entity_id": {
|
||||||
|
"entity": "media",
|
||||||
|
"fields": ["entity_id"],
|
||||||
|
"unique": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"enabled": true,
|
||||||
|
"basepath": "/api/auth",
|
||||||
|
"entity_name": "users",
|
||||||
|
"allow_register": true,
|
||||||
|
"jwt": {
|
||||||
|
"secret": "...",
|
||||||
|
"alg": "HS256",
|
||||||
|
"fields": ["id", "email", "role"]
|
||||||
|
},
|
||||||
|
"cookie": {
|
||||||
|
"path": "/",
|
||||||
|
"sameSite": "lax",
|
||||||
|
"secure": true,
|
||||||
|
"httpOnly": true,
|
||||||
|
"expires": 604800,
|
||||||
|
"renew": true,
|
||||||
|
"pathSuccess": "/",
|
||||||
|
"pathLoggedOut": "/"
|
||||||
|
},
|
||||||
|
"strategies": {
|
||||||
|
"password": {
|
||||||
|
"type": "password",
|
||||||
|
"config": {
|
||||||
|
"hashing": "sha256"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"roles": {
|
||||||
|
"guest": {
|
||||||
|
"is_default": true,
|
||||||
|
"permissions": ["system.access.api", "data.entity.read"]
|
||||||
|
},
|
||||||
|
"admin": {
|
||||||
|
"implicit_allow": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"guard": {
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"media": {
|
||||||
|
"enabled": true,
|
||||||
|
"basepath": "/api/media",
|
||||||
|
"entity_name": "media",
|
||||||
|
"storage": {},
|
||||||
|
"adapter": {
|
||||||
|
"type": "s3",
|
||||||
|
"config": {
|
||||||
|
"access_key": "...",
|
||||||
|
"secret_access_key": "...",
|
||||||
|
"url": "https://some.r2.cloudflarestorage.com/some"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flows": {
|
||||||
|
"basepath": "/api/flows",
|
||||||
|
"flows": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,7 +33,7 @@ export const AppEvents = { AppConfigUpdatedEvent, AppBuiltEvent, AppFirstBoot }
|
|||||||
|
|
||||||
export type AppOptions = {
|
export type AppOptions = {
|
||||||
plugins?: AppPlugin[];
|
plugins?: AppPlugin[];
|
||||||
seed?: (ctx: ModuleBuildContext) => Promise<void>;
|
seed?: (ctx: ModuleBuildContext & { app: App }) => Promise<void>;
|
||||||
manager?: Omit<ModuleManagerOptions, "initial" | "onUpdated" | "seed">;
|
manager?: Omit<ModuleManagerOptions, "initial" | "onUpdated" | "seed">;
|
||||||
};
|
};
|
||||||
export type CreateAppConfig = {
|
export type CreateAppConfig = {
|
||||||
@@ -67,7 +67,6 @@ export class App {
|
|||||||
this.modules = new ModuleManager(connection, {
|
this.modules = new ModuleManager(connection, {
|
||||||
...(options?.manager ?? {}),
|
...(options?.manager ?? {}),
|
||||||
initial: _initialConfig,
|
initial: _initialConfig,
|
||||||
seed: options?.seed,
|
|
||||||
onUpdated: async (key, config) => {
|
onUpdated: async (key, config) => {
|
||||||
// if the EventManager was disabled, we assume we shouldn't
|
// if the EventManager was disabled, we assume we shouldn't
|
||||||
// respond to events, such as "onUpdated".
|
// respond to events, such as "onUpdated".
|
||||||
@@ -115,15 +114,18 @@ export class App {
|
|||||||
await Promise.all(this.plugins.map((plugin) => plugin(this)));
|
await Promise.all(this.plugins.map((plugin) => plugin(this)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$console.log("App built");
|
||||||
await this.emgr.emit(new AppBuiltEvent({ app: this }));
|
await this.emgr.emit(new AppBuiltEvent({ app: this }));
|
||||||
|
|
||||||
// first boot is set from ModuleManager when there wasn't a config table
|
// first boot is set from ModuleManager when there wasn't a config table
|
||||||
if (this.trigger_first_boot) {
|
if (this.trigger_first_boot) {
|
||||||
this.trigger_first_boot = false;
|
this.trigger_first_boot = false;
|
||||||
await this.emgr.emit(new AppFirstBoot({ app: this }));
|
await this.emgr.emit(new AppFirstBoot({ app: this }));
|
||||||
|
await this.options?.seed?.({
|
||||||
|
...this.modules.ctx(),
|
||||||
|
app: this,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
$console.log("App built");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mutateConfig<Module extends keyof Modules>(module: Module) {
|
mutateConfig<Module extends keyof Modules>(module: Module) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
type Strategy,
|
type Strategy,
|
||||||
} from "auth";
|
} from "auth";
|
||||||
import type { PasswordStrategy } from "auth/authenticate/strategies";
|
import type { PasswordStrategy } from "auth/authenticate/strategies";
|
||||||
import { type DB, Exception, type PrimaryFieldType } from "core";
|
import { $console, type DB, Exception, type PrimaryFieldType } from "core";
|
||||||
import { type Static, secureRandomString, transformObject } from "core/utils";
|
import { type Static, secureRandomString, transformObject } from "core/utils";
|
||||||
import type { Entity, EntityManager } from "data";
|
import type { Entity, EntityManager } from "data";
|
||||||
import { type FieldSchema, em, entity, enumm, text } from "data/prototype";
|
import { type FieldSchema, em, entity, enumm, text } from "data/prototype";
|
||||||
@@ -41,6 +41,12 @@ export class AppAuth extends Module<typeof authConfigSchema> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @todo: password strategy is required atm
|
||||||
|
if (!to.strategies?.password?.enabled) {
|
||||||
|
$console.warn("Password strategy cannot be disabled.");
|
||||||
|
to.strategies!.password!.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
return to;
|
return to;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,6 +95,9 @@ export class AppAuth extends Module<typeof authConfigSchema> {
|
|||||||
|
|
||||||
isStrategyEnabled(strategy: Strategy | string) {
|
isStrategyEnabled(strategy: Strategy | string) {
|
||||||
const name = typeof strategy === "string" ? strategy : strategy.getName();
|
const name = typeof strategy === "string" ? strategy : strategy.getName();
|
||||||
|
// for now, password is always active
|
||||||
|
if (name === "password") return true;
|
||||||
|
|
||||||
return this.config.strategies?.[name]?.enabled ?? false;
|
return this.config.strategies?.[name]?.enabled ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -252,14 +252,12 @@ export class DataController extends Controller {
|
|||||||
tb("param", Type.Object({ entity: Type.String() })),
|
tb("param", Type.Object({ entity: Type.String() })),
|
||||||
tb("query", querySchema),
|
tb("query", querySchema),
|
||||||
async (c) => {
|
async (c) => {
|
||||||
//console.log("request", c.req.raw);
|
|
||||||
const { entity } = c.req.param();
|
const { entity } = c.req.param();
|
||||||
if (!this.entityExists(entity)) {
|
if (!this.entityExists(entity)) {
|
||||||
console.warn("not found:", entity, definedEntities);
|
console.warn("not found:", entity, definedEntities);
|
||||||
return this.notFound(c);
|
return this.notFound(c);
|
||||||
}
|
}
|
||||||
const options = c.req.valid("query") as RepoQuery;
|
const options = c.req.valid("query") as RepoQuery;
|
||||||
//console.log("before", this.ctx.emgr.Events);
|
|
||||||
const result = await this.em.repository(entity).findMany(options);
|
const result = await this.em.repository(entity).findMany(options);
|
||||||
|
|
||||||
return c.json(this.repoResult(result), { status: result.data ? 200 : 404 });
|
return c.json(this.repoResult(result), { status: result.data ? 200 : 404 });
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Guard } from "auth";
|
import { Guard } from "auth";
|
||||||
import { BkndError, DebugLogger, withDisabledConsole } from "core";
|
import { $console, BkndError, DebugLogger, withDisabledConsole } from "core";
|
||||||
import { EventManager } from "core/events";
|
import { EventManager } from "core/events";
|
||||||
import { clone, diff } from "core/object/diff";
|
import { clone, diff } from "core/object/diff";
|
||||||
import {
|
import {
|
||||||
@@ -153,7 +153,6 @@ export class ModuleManager {
|
|||||||
this.modules = {} as Modules;
|
this.modules = {} as Modules;
|
||||||
this.emgr = new EventManager();
|
this.emgr = new EventManager();
|
||||||
this.logger = new DebugLogger(this.verbosity === Verbosity.log);
|
this.logger = new DebugLogger(this.verbosity === Verbosity.log);
|
||||||
const context = this.ctx(true);
|
|
||||||
let initial = {} as Partial<ModuleConfigs>;
|
let initial = {} as Partial<ModuleConfigs>;
|
||||||
|
|
||||||
if (options?.initial) {
|
if (options?.initial) {
|
||||||
@@ -169,15 +168,29 @@ export class ModuleManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in MODULES) {
|
this.createModules(initial);
|
||||||
const moduleConfig = key in initial ? initial[key] : {};
|
}
|
||||||
const module = new MODULES[key](moduleConfig, context) as Module;
|
|
||||||
module.setListener(async (c) => {
|
|
||||||
await this.onModuleConfigUpdated(key, c);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.modules[key] = module;
|
private createModules(initial: Partial<ModuleConfigs>) {
|
||||||
|
this.logger.context("createModules").log("creating modules");
|
||||||
|
try {
|
||||||
|
const context = this.ctx(true);
|
||||||
|
|
||||||
|
for (const key in MODULES) {
|
||||||
|
const moduleConfig = key in initial ? initial[key] : {};
|
||||||
|
const module = new MODULES[key](moduleConfig, context) as Module;
|
||||||
|
module.setListener(async (c) => {
|
||||||
|
await this.onModuleConfigUpdated(key, c);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.modules[key] = module;
|
||||||
|
}
|
||||||
|
this.logger.log("modules created");
|
||||||
|
} catch (e) {
|
||||||
|
this.logger.log("failed to create modules", e);
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
|
this.logger.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private get verbosity() {
|
private get verbosity() {
|
||||||
@@ -197,7 +210,7 @@ export class ModuleManager {
|
|||||||
if (this.options?.onUpdated) {
|
if (this.options?.onUpdated) {
|
||||||
await this.options.onUpdated(key as any, config);
|
await this.options.onUpdated(key as any, config);
|
||||||
} else {
|
} else {
|
||||||
this.buildModules();
|
await this.buildModules();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -368,15 +381,27 @@ export class ModuleManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async migrate() {
|
private async migrate() {
|
||||||
|
const state = {
|
||||||
|
success: false,
|
||||||
|
migrated: false,
|
||||||
|
version: {
|
||||||
|
before: this.version(),
|
||||||
|
after: this.version(),
|
||||||
|
},
|
||||||
|
};
|
||||||
this.logger.context("migrate").log("migrating?", this.version(), CURRENT_VERSION);
|
this.logger.context("migrate").log("migrating?", this.version(), CURRENT_VERSION);
|
||||||
|
|
||||||
if (this.version() < CURRENT_VERSION) {
|
if (this.version() < CURRENT_VERSION) {
|
||||||
|
state.version.before = this.version();
|
||||||
|
|
||||||
this.logger.log("there are migrations, verify version");
|
this.logger.log("there are migrations, verify version");
|
||||||
// sync __bknd table
|
// sync __bknd table
|
||||||
await this.syncConfigTable();
|
await this.syncConfigTable();
|
||||||
|
|
||||||
// modules must be built before migration
|
// modules must be built before migration
|
||||||
|
this.logger.log("building modules");
|
||||||
await this.buildModules({ graceful: true });
|
await this.buildModules({ graceful: true });
|
||||||
|
this.logger.log("modules built");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const state = await this.fetch();
|
const state = await this.fetch();
|
||||||
@@ -405,17 +430,27 @@ export class ModuleManager {
|
|||||||
version = _version;
|
version = _version;
|
||||||
configs = _configs;
|
configs = _configs;
|
||||||
|
|
||||||
this.setConfigs(configs);
|
|
||||||
|
|
||||||
this._version = version;
|
this._version = version;
|
||||||
|
state.version.after = version;
|
||||||
|
state.migrated = true;
|
||||||
|
this.ctx().flags.sync_required = true;
|
||||||
|
|
||||||
|
this.logger.log("setting configs");
|
||||||
|
this.createModules(configs);
|
||||||
|
await this.buildModules();
|
||||||
|
|
||||||
this.logger.log("migrated to", version);
|
this.logger.log("migrated to", version);
|
||||||
|
$console.log("Migrated config from", state.version.before, "to", state.version.after);
|
||||||
|
|
||||||
await this.save();
|
await this.save();
|
||||||
} else {
|
} else {
|
||||||
this.logger.log("no migrations needed");
|
this.logger.log("no migrations needed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.success = true;
|
||||||
this.logger.clear();
|
this.logger.clear();
|
||||||
|
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
private setConfigs(configs: ModuleConfigs): void {
|
private setConfigs(configs: ModuleConfigs): void {
|
||||||
@@ -480,10 +515,16 @@ export class ModuleManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// migrate to latest if needed
|
// migrate to latest if needed
|
||||||
await this.migrate();
|
this.logger.log("check migrate");
|
||||||
|
const migration = await this.migrate();
|
||||||
|
if (migration.success && migration.migrated) {
|
||||||
|
this.logger.log("skipping build after migration");
|
||||||
|
} else {
|
||||||
|
this.logger.log("trigger build modules");
|
||||||
|
await this.buildModules();
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.log("building");
|
this.logger.log("done");
|
||||||
await this.buildModules();
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,7 +537,7 @@ export class ModuleManager {
|
|||||||
reloaded: false,
|
reloaded: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.logger.log("buildModules() triggered", options, this._built);
|
this.logger.context("buildModules").log("triggered", options, this._built);
|
||||||
if (options?.graceful && this._built) {
|
if (options?.graceful && this._built) {
|
||||||
this.logger.log("skipping build (graceful)");
|
this.logger.log("skipping build (graceful)");
|
||||||
return state;
|
return state;
|
||||||
@@ -536,8 +577,10 @@ export class ModuleManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// reset all falgs
|
// reset all falgs
|
||||||
|
this.logger.log("resetting flags");
|
||||||
ctx.flags = Module.ctx_flags;
|
ctx.flags = Module.ctx_flags;
|
||||||
|
|
||||||
|
this.logger.clear();
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { _jsonp } from "core/utils";
|
import { _jsonp, transformObject } from "core/utils";
|
||||||
import { type Kysely, sql } from "kysely";
|
import { type Kysely, sql } from "kysely";
|
||||||
import { set } from "lodash-es";
|
import { set } from "lodash-es";
|
||||||
|
|
||||||
@@ -72,13 +72,25 @@ export const migrations: Migration[] = [
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
/*{
|
{
|
||||||
version: 8,
|
version: 8,
|
||||||
up: async (config, { db }) => {
|
up: async (config) => {
|
||||||
await db.deleteFrom(TABLE_NAME).where("type", "=", "diff").execute();
|
const strategies = transformObject(config.auth.strategies, (strategy) => {
|
||||||
return config;
|
return {
|
||||||
}
|
...strategy,
|
||||||
}*/
|
enabled: true,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
auth: {
|
||||||
|
...config.auth,
|
||||||
|
strategies: strategies,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const CURRENT_VERSION = migrations[migrations.length - 1]?.version ?? 0;
|
export const CURRENT_VERSION = migrations[migrations.length - 1]?.version ?? 0;
|
||||||
|
|||||||
@@ -238,7 +238,6 @@ export function FormContextOverride({
|
|||||||
...overrides,
|
...overrides,
|
||||||
...additional,
|
...additional,
|
||||||
};
|
};
|
||||||
console.log("context", context);
|
|
||||||
|
|
||||||
return <FormContext.Provider value={context}>{children}</FormContext.Provider>;
|
return <FormContext.Provider value={context}>{children}</FormContext.Provider>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ export default {
|
|||||||
if (firstStart) {
|
if (firstStart) {
|
||||||
console.log("[DB]", credentials);
|
console.log("[DB]", credentials);
|
||||||
firstStart = false;
|
firstStart = false;
|
||||||
console.log("\n\n[APP ROUTES]");
|
console.log("\n[APP ROUTES]");
|
||||||
showRoutes(app.server);
|
showRoutes(app.server);
|
||||||
console.log("-------\n\n");
|
console.log("-------\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user