Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Indiekit class #781

Merged
merged 9 commits into from
Nov 25, 2024
1 change: 1 addition & 0 deletions helpers/config/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export const testConfig = async (options) => {
},
plugins: ["@indiekit-test/store", ...(options.plugins || [])],
publication: {
categories: options?.publication?.categories,
me: options?.publication?.me || "https://website.example",
...(options.usePostTypes && { postTypes }),
},
Expand Down
4 changes: 2 additions & 2 deletions packages/endpoint-posts/lib/middleware/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { check, checkSchema } from "express-validator";

export const validate = {
async form(request, response, next) {
const { application } = request.app.locals;
const { validationSchemas } = request.app.locals;
const validations = [];

for (const schema of [application.validationSchemas]) {
for (const schema of [Object.fromEntries(validationSchemas)]) {
validations.push(checkSchema(schema));
}

Expand Down
19 changes: 0 additions & 19 deletions packages/indiekit/config/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,13 @@ import process from "node:process";

export const defaultConfig = {
application: {
endpoints: [],
localesAvailable: [
"de",
"en",
"es",
"es-419",
"fr",
"hi",
"id",
"nl",
"pl",
"pt",
"sr",
"sv",
"zh-Hans-CN",
],
mongodbUrl: process.env.MONGO_URL || false,
name: "Indiekit",
port: process.env.PORT || "3000",
postTypes: {},
stores: [],
themeColor: "#04f",
themeColorScheme: "automatic",
timeZone: "UTC",
ttl: 604_800, // 7 days
validationSchemas: {},
},
plugins: [
"@indiekit/endpoint-auth",
Expand Down
15 changes: 15 additions & 0 deletions packages/indiekit/config/locales.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export const locales = new Set([
"de",
"en",
"es",
"es-419",
"fr",
"hi",
"id",
"nl",
"pl",
"pt",
"sr",
"sv",
"zh-Hans-CN",
]);
65 changes: 37 additions & 28 deletions packages/indiekit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import makeDebug from "debug";
import { default as Keyv } from "keyv";
import { default as KeyvMongo } from "@keyv/mongo";
import { expressConfig } from "./config/express.js";
import { locales } from "./config/locales.js";
import { getCategories } from "./lib/categories.js";
import { getIndiekitConfig } from "./lib/config.js";
import { getLocaleCatalog } from "./lib/locale-catalog.js";
Expand All @@ -25,11 +26,18 @@ export const Indiekit = class {
*/
constructor(config) {
this.config = config;
this.collections = new Map();
this.application = this.config.application;
this.package = package_;
this.plugins = this.config.plugins;
this.publication = this.config.publication;

this.collections = new Map();
this.endpoints = new Set();
this.installedPlugins = new Set();
this.locales = locales;
this.mongodbUrl = config.application.mongodbUrl;
this.postTypes = new Map();
this.stores = new Set();
this.validationSchemas = new Map();
}

static async initialize(options = {}) {
Expand All @@ -51,22 +59,24 @@ export const Indiekit = class {
}

addEndpoint(endpoint) {
this.application.endpoints.push(endpoint);
this.endpoints.add(endpoint);
debug(`Added endpoint: ${endpoint.name}`);
}

addPostType(type, postType) {
if (postType.config) {
this.application.postTypes[type] = {
...this.application.postTypes[type],
this.postTypes.set(type, {
...this.postTypes.get(type),
...postType.config,
};
});
}

if (postType.validationSchemas) {
this.application.validationSchemas = {
...this.application.validationSchemas,
...postType.validationSchemas,
};
for (const [field, schema] of Object.entries(
postType.validationSchemas,
)) {
this.validationSchemas.set(field, schema);
}
}
}

Expand All @@ -76,7 +86,7 @@ export const Indiekit = class {
}

addStore(store) {
this.application.stores.push(store);
this.stores.add(store);
debug(`Added content store: ${store.name}`);
}

Expand All @@ -93,9 +103,7 @@ export const Indiekit = class {
}

async connectMongodbClient() {
const mongodbClientOrError = await getMongodbClient(
this.application.mongodbUrl,
);
const mongodbClientOrError = await getMongodbClient(this.mongodbUrl);

if (mongodbClientOrError?.client) {
this.mongodbClient = mongodbClientOrError.client;
Expand All @@ -115,7 +123,7 @@ export const Indiekit = class {

get cache() {
return this.mongodbClient
? new Keyv(new KeyvMongo(this.application.mongodbUrl))
? new Keyv(new KeyvMongo(this.mongodbUrl))
: false;
}

Expand All @@ -134,27 +142,26 @@ export const Indiekit = class {
return false;
}

async bootstrap() {
debug(`Bootstrap: check for required configuration options`);
// Check for required configuration options
get localeCatalog() {
return getLocaleCatalog(this);
}

async installPlugins() {
await getInstalledPlugins(this);
}

async updatePublicationConfig() {
if (!this.publication.me) {
console.error("No publication URL in configuration");
console.info("https://getindiekit.com/configuration/publication#me");
process.exit();
}

// Update application configuration
this.application.installedPlugins = await getInstalledPlugins(this);
this.application.localeCatalog = await getLocaleCatalog(this.application);

// Update publication configuration
this.publication.categories = await getCategories(this);
this.publication.mediaStore = getMediaStore(this);
this.publication.postTemplate = getPostTemplate(this.publication);
this.publication.postTypes = getPostTypes(this);
this.publication.store = getStore(this);

return this;
}

stop(server, name) {
Expand All @@ -169,9 +176,11 @@ export const Indiekit = class {

async server(options = {}) {
await this.connectMongodbClient();
const config = await this.bootstrap();
const app = expressConfig(config);
let { name, port } = config.application;
await this.installPlugins();
await this.updatePublicationConfig();

const app = expressConfig(this);
let { name, port } = this.config.application;
const { version } = this.package;
port = options.port || port;

Expand Down
10 changes: 4 additions & 6 deletions packages/indiekit/lib/controllers/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import path from "node:path";
import { getPackageData } from "../utils.js";

export const list = (request, response) => {
const { application } = response.app.locals;
const { installedPlugins } = response.app.locals;

const plugins = application.installedPlugins.map((plugin) => {
const plugins = [...installedPlugins].map((plugin) => {
const _package = getPackageData(plugin.filePath);
plugin.photo = {
srcOnError: "/assets/plug-in.svg",
Expand All @@ -29,12 +29,10 @@ export const list = (request, response) => {
};

export const view = (request, response) => {
const { application } = response.app.locals;
const { installedPlugins } = response.app.locals;
const { pluginId } = request.params;

const plugin = application.installedPlugins.find(
(plugin) => plugin.id === pluginId,
);
const plugin = installedPlugins.find((plugin) => plugin.id === pluginId);
plugin.package = getPackageData(plugin.filePath);

response.render("plugins/view", {
Expand Down
8 changes: 4 additions & 4 deletions packages/indiekit/lib/locale-catalog.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ const require = createRequire(import.meta.url);

/**
* Add catalog of localised strings to application configuration
* @param {object} application - Application config
* @param {object} Indiekit - Indiekit instance
* @returns {object} Catalog of localised strings
*/
export const getLocaleCatalog = (application) => {
export const getLocaleCatalog = (Indiekit) => {
const catalog = new Map();

for (const locale of application.localesAvailable) {
for (const locale of Indiekit.locales) {
const translations = [
// Application translations
require(`../locales/${locale}.json`),
Expand All @@ -23,7 +23,7 @@ export const getLocaleCatalog = (application) => {
];

// Plug-in translations
for (const plugin of application.installedPlugins) {
for (const plugin of Indiekit.installedPlugins) {
const localePath = path.join(plugin.filePath, `locales/${locale}.json`);
try {
translations.push(require(localePath));
Expand Down
4 changes: 2 additions & 2 deletions packages/indiekit/lib/middleware/internationalisation.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import i18n from "i18n";
export const internationalisation = (Indiekit) =>
function (request, response, next) {
try {
const { application } = Indiekit;
const { application, localeCatalog } = Indiekit;

i18n.configure({
cookie: "locale",
defaultLocale: "en",
indent: " ",
objectNotation: true,
queryParameter: "lang",
staticCatalog: Object.fromEntries(application.localeCatalog),
staticCatalog: Object.fromEntries(localeCatalog),
});

i18n.init(request, response);
Expand Down
12 changes: 10 additions & 2 deletions packages/indiekit/lib/middleware/locals.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@ export const locals = (Indiekit) =>
application,
collections,
database,
installedPlugins,
mongodbClientError,
publication,
validationSchemas,
} = Indiekit;

// Application
Expand Down Expand Up @@ -55,21 +57,27 @@ export const locals = (Indiekit) =>
// Application navigation
// Only update if serving HTML to prevent wrong session link being shown
if (request.accepts("html")) {
application.navigation = getNavigation(application, request, response);
application.navigation = getNavigation(Indiekit, request, response);
}

// Application shortcuts
application.shortcuts = getShortcuts(application, response);
application.shortcuts = getShortcuts(Indiekit, response);

// Application endpoints
request.app.locals.application = {
...application,
...getEndpointUrls(application, request),
};

// Installed plug-ins
request.app.locals.installedPlugins = installedPlugins;

// Publication
request.app.locals.publication = publication;

// Publication
request.app.locals.validationSchemas = validationSchemas;

// Persist scope and token
request.app.locals.scope =
request.app.locals.scope || request.session.scope;
Expand Down
13 changes: 11 additions & 2 deletions packages/indiekit/lib/navigation.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
export const getNavigation = (application, request, response) => {
/**
* Get navigation items
* @param {object} Indiekit - Indiekit instance
* @param {import("express").Request} request - Request
* @param {import("express").Response} response - Response
* @returns {object} Shortcuts
*/
export const getNavigation = (Indiekit, request, response) => {
const { application, endpoints } = Indiekit;

// Default navigation items
let navigation = [
request.session.access_token
Expand All @@ -21,7 +30,7 @@ export const getNavigation = (application, request, response) => {
}

// Add navigation items from endpoint plug-ins
for (const endpoint of application.endpoints) {
for (const endpoint of endpoints) {
if (endpoint.navigationItems) {
const navigationItems = Array.isArray(endpoint.navigationItems)
? endpoint.navigationItems
Expand Down
17 changes: 6 additions & 11 deletions packages/indiekit/lib/plugins.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,8 @@ const require = createRequire(import.meta.url);
/**
* Add plug-ins to application configuration
* @param {object} Indiekit - Indiekit instance
* @returns {Promise<Array>} Installed plug-ins
*/
export const getInstalledPlugins = async (Indiekit) => {
const installedPlugins = [];

export async function getInstalledPlugins(Indiekit) {
for await (const pluginName of Indiekit.config.plugins) {
const { default: IndiekitPlugin } = await import(pluginName);
const plugin = new IndiekitPlugin(Indiekit.config[pluginName]);
Expand All @@ -23,21 +20,19 @@ export const getInstalledPlugins = async (Indiekit) => {
// Register plug-in functions
if (plugin.init) {
await plugin.init(Indiekit);
installedPlugins.push(plugin);
Indiekit.installedPlugins.add(plugin);
}
}

return installedPlugins;
};
}

/**
* Get installed plug-in
* @param {object} application - Application configuration
* @param {Set} installedPlugins - Installed plug-ins
* @param {string} pluginName - Plug-in Name
* @returns {string} Plug-in ID
*/
export const getInstalledPlugin = (application, pluginName) => {
return application.installedPlugins.find(
export const getInstalledPlugin = (installedPlugins, pluginName) => {
return [...installedPlugins].find(
(plugin) => plugin.id === getPluginId(pluginName),
);
};
Expand Down
Loading
Loading