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