Skip to content

Commit

Permalink
Merge pull request #5196 from reactioncommerce/feat-aldeed-4268-colle…
Browse files Browse the repository at this point in the history
…ctions-config

feat: define collections using registerPlugin
  • Loading branch information
mikemurray authored Jun 10, 2019
2 parents e51ba31 + c9fae52 commit 860dec0
Show file tree
Hide file tree
Showing 110 changed files with 3,550 additions and 3,766 deletions.
21 changes: 14 additions & 7 deletions imports/collections/rawCollections.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import { MongoInternals } from "meteor/mongo";
import { NoMeteorMedia } from "/imports/plugins/core/files/server";
import defineCollections from "/imports/node-app/core/util/defineCollections";
const collections = {};

const collections = { Media: NoMeteorMedia };

const { db } = MongoInternals.defaultRemoteCollectionDriver().mongo;
defineCollections(db, collections);
/**
* @summary Use this to set the raw collections after all plugins
* have been registered.
* @param {Object} registeredCollections Collections map
* @return {undefined}
*/
export function setCollections(registeredCollections) {
for (const name in registeredCollections) {
if ({}.hasOwnProperty.call(registeredCollections, name)) {
collections[name] = registeredCollections[name];
}
}
}

export default collections;
118 changes: 109 additions & 9 deletions imports/node-app/core/ReactionNodeApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { PubSub } from "apollo-server";
import { merge } from "lodash";
import mongodb, { MongoClient } from "mongodb";
import appEvents from "./util/appEvents";
import collectionIndex from "/imports/utils/collectionIndex";
import createApolloServer from "./createApolloServer";
import defineCollections from "./util/defineCollections";
import getRootUrl from "/imports/plugins/core/core/server/util/getRootUrl";
import getAbsoluteUrl from "/imports/plugins/core/core/server/util/getAbsoluteUrl";

Expand Down Expand Up @@ -33,7 +33,12 @@ export default class ReactionNodeApp {
app: this,
appEvents,
collections: this.collections,
getFunctionsOfType: (type) => this.functionsByType[type] || [],
getFunctionsOfType: (type) => {
if (this.options.functionsByType && Array.isArray(this.options.functionsByType[type])) {
return this.options.functionsByType[type];
}
return this.functionsByType[type] || [];
},
// In a large production app, you may want to use an external pub-sub system.
// See https://www.apollographql.com/docs/apollo-server/features/subscriptions.html#PubSub-Implementations
// We may eventually bind this directly to Kafka.
Expand Down Expand Up @@ -72,12 +77,64 @@ export default class ReactionNodeApp {
this.mongodb = options.mongodb || mongodb;
}

/**
* @summary Use this method to provide the MongoDB database instance.
* A side effect is that `this.collections`/`this.context.collections`
* will have all collections available on it after this is called.
* @param {Database} db MongoDB library database instance
* @return {undefined}
*/
setMongoDatabase(db) {
this.db = db;
defineCollections(this.db, this.collections);

// Loop through all registered plugins
for (const pluginName in this.registeredPlugins) {
if ({}.hasOwnProperty.call(this.registeredPlugins, pluginName)) {
const pluginConfig = this.registeredPlugins[pluginName];

// If a plugin config has `collections` key
if (pluginConfig.collections) {
// Loop through `collections` object keys
for (const collectionKey in pluginConfig.collections) {
if ({}.hasOwnProperty.call(pluginConfig.collections, collectionKey)) {
const collectionConfig = pluginConfig.collections[collectionKey];

// Validate that the `collections` key value is an object and has `name`
if (!collectionConfig || typeof collectionConfig.name !== "string" || collectionConfig.name.length === 0) {
throw new Error(`In registerPlugin, collection "${collectionKey}" needs a name property`);
}

// Validate that the `collections` key hasn't already been taken by another plugin
if (this.collections[collectionKey]) {
throw new Error(`Plugin ${pluginName} defines a collection with key "${collectionKey}" in registerPlugin,` +
" but another plugin has already defined a collection with that key");
}

// Add the collection instance to `context.collections`
this.collections[collectionKey] = this.db.collection(collectionConfig.name);

// If the collection config has `indexes` key, define all requested indexes
if (Array.isArray(collectionConfig.indexes)) {
for (const indexArgs of collectionConfig.indexes) {
collectionIndex(this.collections[collectionKey], ...indexArgs);
}
}
}
}
}
}
}
}

async connectToMongo({ mongoUrl }) {
/**
* @summary Given a MongoDB URL, creates a connection to it, sets `this.mongoClient`,
* calls `this.setMongoDatabase` with the database instance, and then
* resolves the Promise.
* @param {Object} options Options object
* @param {String} options.mongoUrl MongoDB connection URL
* @return {Promise<undefined>} Nothing
*/
async connectToMongo({ mongoUrl } = {}) {
const lastSlash = mongoUrl.lastIndexOf("/");
const dbUrl = mongoUrl.slice(0, lastSlash);
const dbName = mongoUrl.slice(lastSlash + 1);
Expand All @@ -101,6 +158,12 @@ export default class ReactionNodeApp {
return this.mongoClient.close();
}

/**
* @summary Calls all `registerPluginHandler` type functions from all registered
* plugins, and then calls all `startup` type functions in series, in the order
* in which they were registered.
* @return {Promise<undefined>} Nothing
*/
async runServiceStartup() {
// Call `functionsByType.registerPluginHandler` functions for every plugin that
// has supplied one, passing in all other plugins. Allows one plugin to check
Expand All @@ -126,6 +189,10 @@ export default class ReactionNodeApp {
}
}

/**
* @summary Creates the Apollo server and the Express app
* @return {undefined}
*/
initServer() {
const { addCallMeteorMethod, debug, httpServer } = this.options;
const { resolvers, schemas } = this.graphQL;
Expand All @@ -150,10 +217,23 @@ export default class ReactionNodeApp {
this.httpServer = httpServer || createServer(this.expressApp);
}

async startServer({ port }) {
/**
* @summary Creates the Apollo server and the Express app, if necessary,
* and then starts it listening on `port`.
* @param {Object} options Options object
* @param {Number} [options.port] Port to listen on. If not provided,
* the server will be created but will not listen.
* @return {Promise<undefined>} Nothing
*/
async startServer({ port } = {}) {
if (!this.httpServer) this.initServer();

return new Promise((resolve, reject) => {
if (!port) {
resolve();
return;
}

try {
// To also listen for WebSocket connections for GraphQL
// subs, this needs to be `this.httpServer.listen`
Expand All @@ -180,7 +260,18 @@ export default class ReactionNodeApp {
});
}

async start({ mongoUrl, port }) {
/**
* @summary Starts the entire app. Connects to `mongoUrl`, builds the
* `context.collections`, runs plugin startup code, creates the
* Apollo server and the Express app as necessary, and then starts
* the server listening on `port` if `port` is provided.
* @param {Object} options Options object
* @param {String} options.mongoUrl MongoDB connection URL
* @param {Number} [options.port] Port to listen on. If not provided,
* the server will be created but will not listen.
* @return {Promise<undefined>} Nothing
*/
async start({ mongoUrl, port } = {}) {
// (1) Connect to MongoDB database
await this.connectToMongo({ mongoUrl });

Expand All @@ -191,6 +282,11 @@ export default class ReactionNodeApp {
await this.startServer({ port });
}

/**
* @summary Stops the entire app. Closes the MongoDB connection and
* stops the Express server listening.
* @return {Promise<undefined>} Nothing
*/
async stop() {
// (1) Disconnect from MongoDB database
await this.disconnectFromMongo();
Expand All @@ -199,9 +295,13 @@ export default class ReactionNodeApp {
await this.stopServer();
}

// Plugins should call this to register everything they provide.
// This is a non-Meteor replacement for the old `Reaction.registerPackage`.
async registerPlugin(plugin) {
/**
* @summary Plugins should call this to register everything they provide.
* This is a non-Meteor replacement for the old `Reaction.registerPackage`.
* @param {Object} plugin Plugin configuration object
* @return {Promise<undefined>} Nothing
*/
async registerPlugin(plugin = {}) {
if (typeof plugin.name !== "string" || plugin.name.length === 0) {
throw new Error("Plugin configuration passed to registerPlugin must have 'name' field");
}
Expand Down
40 changes: 0 additions & 40 deletions imports/node-app/core/util/defineCollections.js

This file was deleted.

25 changes: 0 additions & 25 deletions imports/node-app/devserver/filesStartup.js

This file was deleted.

4 changes: 0 additions & 4 deletions imports/node-app/devserver/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import mutations from "./mutations";
import queries from "./queries";
import resolvers from "./resolvers";
import schemas from "./schemas";
import filesStartup from "./filesStartup";
import registerPlugins from "./registerPlugins";
import "./extendSchemas";

Expand All @@ -20,9 +19,6 @@ const app = new ReactionNodeApp({
queries,
rootUrl: ROOT_URL
},
functionsByType: {
startup: [filesStartup]
},
graphQL: {
graphiql: true,
resolvers,
Expand Down
35 changes: 35 additions & 0 deletions imports/node-app/devserver/registerPlugins.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
import registerAccountsPlugin from "/imports/plugins/core/accounts/server/no-meteor/register";
import registerCartPlugin from "/imports/plugins/core/cart/server/no-meteor/register";
import registerCatalogPlugin from "/imports/plugins/core/catalog/server/no-meteor/register";
import registerCorePlugin from "/imports/plugins/core/core/server/no-meteor/register";
import registerDiscountsPlugin from "/imports/plugins/core/discounts/server/no-meteor/register";
import registerFilesPlugin from "/imports/plugins/core/files/server/no-meteor/register";
import registerI18nPlugin from "/imports/plugins/core/i18n/server/no-meteor/register";
import registerInventoryPlugin from "/imports/plugins/core/inventory/server/no-meteor/register";
import registerNavigationPlugin from "/imports/plugins/core/navigation/server/no-meteor/register";
import registerOrdersPlugin from "/imports/plugins/core/orders/server/no-meteor/register";
import registerProductPlugin from "/imports/plugins/core/product/server/no-meteor/register";
import registerSettingsPlugin from "/imports/plugins/core/settings/server/register";
import registerTagsPlugin from "/imports/plugins/core/tags/server/no-meteor/register";
import registerTemplatesPlugin from "/imports/plugins/core/templates/server/no-meteor/register";
import registerNotificationsPlugin from "/imports/plugins/included/notifications/server/no-meteor/register";
import registerShippingRatesPlugin from "/imports/plugins/included/shipping-rates/server/no-meteor/register";
import registerSimpleInventoryPlugin from "/imports/plugins/included/simple-inventory/server/no-meteor/register";
import registerSimplePricingPlugin from "/imports/plugins/included/simple-pricing/server/no-meteor/register";
import registerSurchargesPlugin from "/imports/plugins/included/surcharges/server/no-meteor/register";
import registerTaxesRatesPlugin from "/imports/plugins/included/taxes-rates/server/no-meteor/register";

/**
* @summary A function in which you should call `register` function for each API plugin,
Expand All @@ -10,8 +26,27 @@ import registerSimplePricingPlugin from "/imports/plugins/included/simple-pricin
* @return {Promise<null>} Null
*/
export default async function registerPlugins(app) {
// Core
await registerCorePlugin(app);
await registerAccountsPlugin(app);
await registerCartPlugin(app);
await registerCatalogPlugin(app);
await registerDiscountsPlugin(app);
await registerFilesPlugin(app);
await registerI18nPlugin(app);
await registerInventoryPlugin(app);
await registerNavigationPlugin(app);
await registerOrdersPlugin(app);
await registerProductPlugin(app);
await registerSettingsPlugin(app);
await registerTagsPlugin(app);
await registerTemplatesPlugin(app);

// // Included
await registerNotificationsPlugin(app);
await registerShippingRatesPlugin(app);
await registerSimpleInventoryPlugin(app);
await registerSimplePricingPlugin(app);
await registerSurchargesPlugin(app);
await registerTaxesRatesPlugin(app);
}
Loading

0 comments on commit 860dec0

Please sign in to comment.