mirror of
https://github.com/bknd-io/bknd/
synced 2026-08-04 09:06:01 +00:00
init media adapter refactoring
This commit is contained in:
@@ -1,22 +1,15 @@
|
|||||||
import { describe, expect, test } from "bun:test";
|
import { describe, expect, test } from "bun:test";
|
||||||
import { registries } from "../../src";
|
|
||||||
import { createApp } from "core/test/utils";
|
import { createApp } from "core/test/utils";
|
||||||
import { em, entity, text } from "../../src/data";
|
import { em, entity, text } from "../../src/data";
|
||||||
import { StorageLocalAdapter } from "adapter/node/storage/StorageLocalAdapter";
|
import { StorageLocalAdapter } from "adapter/node/storage/StorageLocalAdapter";
|
||||||
import { AppMedia } from "../../src/media/AppMedia";
|
import { AppMedia } from "../../src/media/AppMedia";
|
||||||
import { mediaConfigSchema } from "../../src/media/media-schema";
|
|
||||||
import { moduleTestSuite } from "./module-test-suite";
|
import { moduleTestSuite } from "./module-test-suite";
|
||||||
|
|
||||||
describe("AppMedia", () => {
|
describe("AppMedia", () => {
|
||||||
test.only("...", () => {
|
|
||||||
const media = new AppMedia();
|
|
||||||
console.log(media.toJSON());
|
|
||||||
});
|
|
||||||
|
|
||||||
moduleTestSuite(AppMedia);
|
moduleTestSuite(AppMedia);
|
||||||
|
|
||||||
test("should allow additional fields", async () => {
|
test("should allow additional fields", async () => {
|
||||||
registries.media.register("local", StorageLocalAdapter);
|
//registries.media.register("local", StorageLocalAdapter);
|
||||||
|
|
||||||
const app = createApp({
|
const app = createApp({
|
||||||
initialConfig: {
|
initialConfig: {
|
||||||
@@ -37,6 +30,7 @@ describe("AppMedia", () => {
|
|||||||
}).toJSON(),
|
}).toJSON(),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
app.module.media.adapters.set("local", StorageLocalAdapter);
|
||||||
|
|
||||||
await app.build();
|
await app.build();
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import type { CliBkndConfig, CliCommand } from "cli/types";
|
|||||||
import { Option } from "commander";
|
import { Option } from "commander";
|
||||||
import { config } from "core";
|
import { config } from "core";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import { registries } from "modules/registries";
|
|
||||||
import c from "picocolors";
|
import c from "picocolors";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import {
|
import {
|
||||||
@@ -58,10 +57,11 @@ export const run: CliCommand = (program) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// automatically register local adapter
|
// automatically register local adapter
|
||||||
const local = StorageLocalAdapter.prototype.getName();
|
// @todo: add back
|
||||||
|
/* const local = StorageLocalAdapter.prototype.getName();
|
||||||
if (!registries.media.has(local)) {
|
if (!registries.media.has(local)) {
|
||||||
registries.media.register(local, StorageLocalAdapter);
|
registries.media.register(local, StorageLocalAdapter);
|
||||||
}
|
} */
|
||||||
|
|
||||||
type MakeAppConfig = {
|
type MakeAppConfig = {
|
||||||
connection?: CreateAppConfig["connection"];
|
connection?: CreateAppConfig["connection"];
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export class SchemaObject<Schema extends TSchema = TSchema> {
|
|||||||
this._default = _schema.template({}, { withOptional: true }) as any;
|
this._default = _schema.template({}, { withOptional: true }) as any;
|
||||||
this._value = parse(_schema, structuredClone(initial ?? {}), {
|
this._value = parse(_schema, structuredClone(initial ?? {}), {
|
||||||
withDefaults: true,
|
withDefaults: true,
|
||||||
withExtendedDefaults: true,
|
//withExtendedDefaults: true,
|
||||||
forceParse: this.isForceParse(),
|
forceParse: this.isForceParse(),
|
||||||
skipMark: this.isForceParse(),
|
skipMark: this.isForceParse(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ export type { ServerEnv } from "modules/Controller";
|
|||||||
export type { BkndConfig } from "bknd/adapter";
|
export type { BkndConfig } from "bknd/adapter";
|
||||||
|
|
||||||
export * as middlewares from "modules/middlewares";
|
export * as middlewares from "modules/middlewares";
|
||||||
export { registries } from "modules/registries";
|
|
||||||
|
|
||||||
export type { MediaFieldSchema } from "media/AppMedia";
|
export type { MediaFieldSchema } from "media/AppMedia";
|
||||||
export type { UserFieldSchema } from "auth/AppAuth";
|
export type { UserFieldSchema } from "auth/AppAuth";
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import type { AppEntity } from "core";
|
import type { AppEntity, Constructor } from "core";
|
||||||
import { $console } from "core/utils";
|
import { $console, objectTransform } from "core/utils";
|
||||||
import type { Entity, EntityManager } from "data";
|
import type { Entity, EntityManager } from "data";
|
||||||
import { type FileUploadedEventData, Storage, type StorageAdapter, MediaPermissions } from "media";
|
import { type FileUploadedEventData, Storage, type StorageAdapter, MediaPermissions } from "media";
|
||||||
import { Module } from "modules/Module";
|
import { Module } from "modules/Module";
|
||||||
import { type FieldSchema, em, entity } from "../data/prototype";
|
import { type FieldSchema, em, entity } from "../data/prototype";
|
||||||
import { MediaController } from "./api/MediaController";
|
import { MediaController } from "./api/MediaController";
|
||||||
import { buildMediaSchema, type mediaConfigSchema, registry, type TAppMediaConfig } from "./media-schema";
|
import { mediaConfigSchema, type TAppMediaConfig } from "./media-schema";
|
||||||
import { mediaFields } from "./media-entities";
|
import { mediaFields } from "./media-entities";
|
||||||
|
import { StorageS3Adapter } from "media/storage/adapters/s3/StorageS3Adapter";
|
||||||
|
import { StorageCloudinaryAdapter } from "media/storage/adapters/cloudinary/StorageCloudinaryAdapter";
|
||||||
|
import { s } from "core/object/schema";
|
||||||
|
|
||||||
export type MediaFieldSchema = FieldSchema<typeof AppMedia.mediaFields>;
|
export type MediaFieldSchema = FieldSchema<typeof AppMedia.mediaFields>;
|
||||||
declare module "core" {
|
declare module "core" {
|
||||||
@@ -15,10 +18,12 @@ declare module "core" {
|
|||||||
media: Media;
|
media: Media;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
type ClassThatImplements<T> = Constructor<T> & { prototype: T };
|
||||||
|
|
||||||
// @todo: current workaround to make it all required
|
// @todo: current workaround to make it all required
|
||||||
export class AppMedia extends Module<Required<TAppMediaConfig>> {
|
export class AppMedia extends Module<Required<TAppMediaConfig>> {
|
||||||
private _storage?: Storage;
|
private _storage?: Storage;
|
||||||
|
adapters: Map<string, ClassThatImplements<StorageAdapter>> = new Map();
|
||||||
|
|
||||||
override async build() {
|
override async build() {
|
||||||
if (!this.config.enabled) {
|
if (!this.config.enabled) {
|
||||||
@@ -26,6 +31,15 @@ export class AppMedia extends Module<Required<TAppMediaConfig>> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// register default adapters
|
||||||
|
if (!this.adapters.has("s3")) {
|
||||||
|
this.adapters.set("s3", StorageS3Adapter);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.adapters.has("cloudinary")) {
|
||||||
|
this.adapters.set("cloudinary", StorageCloudinaryAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.config.adapter) {
|
if (!this.config.adapter) {
|
||||||
console.info("No storage adapter provided, skip building media.");
|
console.info("No storage adapter provided, skip building media.");
|
||||||
return;
|
return;
|
||||||
@@ -35,7 +49,11 @@ export class AppMedia extends Module<Required<TAppMediaConfig>> {
|
|||||||
let adapter: StorageAdapter;
|
let adapter: StorageAdapter;
|
||||||
try {
|
try {
|
||||||
const { type, config } = this.config.adapter;
|
const { type, config } = this.config.adapter;
|
||||||
const cls = registry.get(type as any).cls;
|
const cls = this.adapters.get(type as any);
|
||||||
|
if (!cls) {
|
||||||
|
throw new Error(`Adapter ${type} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
adapter = new cls(config as any);
|
adapter = new cls(config as any);
|
||||||
|
|
||||||
this._storage = new Storage(adapter, this.config.storage, this.ctx.emgr);
|
this._storage = new Storage(adapter, this.config.storage, this.ctx.emgr);
|
||||||
@@ -59,7 +77,34 @@ export class AppMedia extends Module<Required<TAppMediaConfig>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getSchema() {
|
getSchema() {
|
||||||
return buildMediaSchema();
|
if (!this.adapters) {
|
||||||
|
return mediaConfigSchema;
|
||||||
|
}
|
||||||
|
const adapterSchemaObject = objectTransform(
|
||||||
|
Object.fromEntries(this.adapters.entries()),
|
||||||
|
(adapter, name) => {
|
||||||
|
const schema = adapter.prototype.getSchema();
|
||||||
|
if (!schema) {
|
||||||
|
throw new Error(`Adapter ${name} has no schema`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.strictObject(
|
||||||
|
{
|
||||||
|
type: s.literal(name),
|
||||||
|
config: schema,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: String(schema.title ?? name),
|
||||||
|
description: schema.description,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return s.strictObject({
|
||||||
|
...mediaConfigSchema.properties,
|
||||||
|
adapter: s.anyOf(Object.values(adapterSchemaObject)).optional(),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get basepath() {
|
get basepath() {
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
import { type Constructor, Registry } from "core";
|
|
||||||
|
|
||||||
export { guess as guessMimeType } from "./storage/mime-types-tiny";
|
export { guess as guessMimeType } from "./storage/mime-types-tiny";
|
||||||
export {
|
export {
|
||||||
Storage,
|
Storage,
|
||||||
@@ -15,7 +13,6 @@ import {
|
|||||||
StorageCloudinaryAdapter,
|
StorageCloudinaryAdapter,
|
||||||
} from "./storage/adapters/cloudinary/StorageCloudinaryAdapter";
|
} from "./storage/adapters/cloudinary/StorageCloudinaryAdapter";
|
||||||
import { type S3AdapterConfig, StorageS3Adapter } from "./storage/adapters/s3/StorageS3Adapter";
|
import { type S3AdapterConfig, StorageS3Adapter } from "./storage/adapters/s3/StorageS3Adapter";
|
||||||
import type { s } from "core/object/schema";
|
|
||||||
|
|
||||||
export { StorageAdapter };
|
export { StorageAdapter };
|
||||||
export { StorageS3Adapter, type S3AdapterConfig, StorageCloudinaryAdapter, type CloudinaryConfig };
|
export { StorageS3Adapter, type S3AdapterConfig, StorageCloudinaryAdapter, type CloudinaryConfig };
|
||||||
@@ -25,27 +22,4 @@ export * as MediaPermissions from "./media-permissions";
|
|||||||
export type { FileUploadedEventData } from "./storage/events";
|
export type { FileUploadedEventData } from "./storage/events";
|
||||||
export * from "./utils";
|
export * from "./utils";
|
||||||
|
|
||||||
type ClassThatImplements<T> = Constructor<T> & { prototype: T };
|
|
||||||
|
|
||||||
export const MediaAdapterRegistry = new Registry<{
|
|
||||||
cls: ClassThatImplements<StorageAdapter>;
|
|
||||||
schema: s.Schema;
|
|
||||||
}>((cls: ClassThatImplements<StorageAdapter>) => ({
|
|
||||||
cls,
|
|
||||||
schema: cls.prototype.getSchema() as s.Schema,
|
|
||||||
}))
|
|
||||||
.register("s3", StorageS3Adapter)
|
|
||||||
.register("cloudinary", StorageCloudinaryAdapter);
|
|
||||||
|
|
||||||
export const Adapters = {
|
|
||||||
s3: {
|
|
||||||
cls: StorageS3Adapter,
|
|
||||||
schema: StorageS3Adapter.prototype.getSchema(),
|
|
||||||
},
|
|
||||||
cloudinary: {
|
|
||||||
cls: StorageCloudinaryAdapter,
|
|
||||||
schema: StorageCloudinaryAdapter.prototype.getSchema(),
|
|
||||||
},
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export { adapterTestSuite } from "./storage/adapters/adapter-test-suite";
|
export { adapterTestSuite } from "./storage/adapters/adapter-test-suite";
|
||||||
|
|||||||
@@ -1,29 +1,6 @@
|
|||||||
import { objectTransform } from "core/utils";
|
|
||||||
import { Adapters } from "media";
|
|
||||||
import { registries } from "modules/registries";
|
|
||||||
import { s } from "core/object/schema";
|
import { s } from "core/object/schema";
|
||||||
|
|
||||||
export const ADAPTERS = {
|
export const mediaConfigSchema = s.strictObject(
|
||||||
...Adapters,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const registry = registries.media;
|
|
||||||
|
|
||||||
export function buildMediaSchema() {
|
|
||||||
const adapterSchemaObject = objectTransform(registry.all(), (adapter, name) => {
|
|
||||||
return s.strictObject(
|
|
||||||
{
|
|
||||||
type: s.literal(name),
|
|
||||||
config: adapter.schema,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: adapter.schema?.title ?? name,
|
|
||||||
description: adapter.schema?.description,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
return s.strictObject(
|
|
||||||
{
|
{
|
||||||
enabled: s.boolean({ default: false }),
|
enabled: s.boolean({ default: false }),
|
||||||
basepath: s.string({ default: "/api/media" }),
|
basepath: s.string({ default: "/api/media" }),
|
||||||
@@ -38,13 +15,15 @@ export function buildMediaSchema() {
|
|||||||
},
|
},
|
||||||
{ default: {} },
|
{ default: {} },
|
||||||
),
|
),
|
||||||
adapter: s.anyOf(Object.values(adapterSchemaObject)).optional(),
|
adapter: s
|
||||||
|
.strictObject({
|
||||||
|
type: s.string(),
|
||||||
|
config: s.any(),
|
||||||
|
})
|
||||||
|
.optional(),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
default: {},
|
default: {},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
export const mediaConfigSchema = buildMediaSchema();
|
|
||||||
export type TAppMediaConfig = s.Static<typeof mediaConfigSchema>;
|
export type TAppMediaConfig = s.Static<typeof mediaConfigSchema>;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import { AppMedia } from "../media/AppMedia";
|
|||||||
import type { ServerEnv } from "./Controller";
|
import type { ServerEnv } from "./Controller";
|
||||||
import { Module, type ModuleBuildContext } from "./Module";
|
import { Module, type ModuleBuildContext } from "./Module";
|
||||||
import { ModuleHelper } from "./ModuleHelper";
|
import { ModuleHelper } from "./ModuleHelper";
|
||||||
import { s, mark, stripMark } from "core/object/schema";
|
import { s } from "core/object/schema";
|
||||||
|
|
||||||
export type { ModuleBuildContext };
|
export type { ModuleBuildContext };
|
||||||
|
|
||||||
@@ -166,7 +166,8 @@ export class ModuleManager {
|
|||||||
if ("version" in options.initial) {
|
if ("version" in options.initial) {
|
||||||
const { version, ...initialConfig } = options.initial;
|
const { version, ...initialConfig } = options.initial;
|
||||||
this._version = version;
|
this._version = version;
|
||||||
initial = stripMark(initialConfig);
|
//initial = stripMark(initialConfig);
|
||||||
|
initial = initialConfig;
|
||||||
|
|
||||||
this._booted_with = "provided";
|
this._booted_with = "provided";
|
||||||
} else {
|
} else {
|
||||||
@@ -487,10 +488,11 @@ export class ModuleManager {
|
|||||||
// set version and config from fetched
|
// set version and config from fetched
|
||||||
this._version = result.version;
|
this._version = result.version;
|
||||||
|
|
||||||
if (this.options?.trustFetched === true) {
|
// @todo: add back?
|
||||||
|
/* if (this.options?.trustFetched === true) {
|
||||||
this.logger.log("trusting fetched config (mark)");
|
this.logger.log("trusting fetched config (mark)");
|
||||||
mark(result.json);
|
mark(result.json);
|
||||||
}
|
} */
|
||||||
|
|
||||||
// if version doesn't match, migrate before building
|
// if version doesn't match, migrate before building
|
||||||
if (this.version() !== CURRENT_VERSION) {
|
if (this.version() !== CURRENT_VERSION) {
|
||||||
@@ -732,7 +734,6 @@ export function getDefaultSchema() {
|
|||||||
export function getDefaultConfig(): ModuleConfigs {
|
export function getDefaultConfig(): ModuleConfigs {
|
||||||
const config = transformObject(MODULES, (module) => {
|
const config = transformObject(MODULES, (module) => {
|
||||||
return module.prototype.getSchema().template();
|
return module.prototype.getSchema().template();
|
||||||
//return Default(module.prototype.getSchema(), {});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return config as any;
|
return config as any;
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import { MediaAdapterRegistry } from "media";
|
|
||||||
|
|
||||||
const registries = {
|
|
||||||
media: MediaAdapterRegistry,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export { registries };
|
|
||||||
Reference in New Issue
Block a user