8000 Feature/event bus enhancement by Asterioxer · Pull Request #6873 · reactioncommerce/reaction · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Feature/event bus enhancement #6873

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

Open
wants to merge 2 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
Mailchimp Open Commerce (formerly Reaction Commerce)
</h1>

<p>
A modern, API-first, and headless commerce platform built with Node.js, MongoDB, and GraphQL. It integrates smoothly with tools like npm, Docker, and Kubernetes.
</p>

<h4 align="center">
<a href="https://mailchimp.com/developer/open-commerce/">Open Commerce Website</a> |
<a href="https://twitter.com/getreaction">Twitter</a> |
Expand All @@ -18,16 +22,16 @@
# Features

<table>
<tr><td><strong>Fast</strong></td><td>Returns data in split seconds, and faster queries mean faster web pages</td></tr>
<tr><td><strong>Fast</strong></td><td>Returns data in milliseconds, and faster queries mean faster web pages</td></tr>
<tr><td><strong>Proven</strong></td><td>Open Commerce fuels sites doing 10's of thousands of orders per day with 100's of thousands of products</td></tr>
<tr><td><strong>Composable</strong></td><td>A flexible plugin system allows you to pick and choose which integrations work best for you</td></tr>
<tr><td><strong>Multi-tenant</strong></td><td>Host multiple shops in the same installation</td></tr>
<tr><td><strong>Scalable</strong></td><td>Start out with a single server and scale up to hundreds</td></tr>
<tr><td><strong>Flexible Products</strong></td><td>Allows Products, with options and variants to fit a wide variety of needs</td></tr>
<tr><td><strong>Flexible Products</strong></td><td>Supports products with customizable options and variants to meet a wide range of business needs.</td></tr>
<tr><td><strong>Inventory</strong></td><td>Track inventory, allow or disallow backorders and more</td></tr>
<tr><td><strong>Shipping</strong></td><td>Integrate with a shipping rate provider or build your own custom table</td></tr>
<tr><td><strong>Taxes</strong></td><td>Integrate with a tax rate provider or build your own custom tax table</td></tr>
<tr><td><strong>Fulfillment</strong></td><td>Flexible fulfillment system allows you create your own fulfillment methods</td></tr>
<tr><td><strong>Fulfillment</strong></td><td>Flexible fulfillment system allows you create self-customized fulfillment methods</td></tr>
<tr><td><strong>Order Tracking</strong></td><td>View and manage your orders in the included admin system</td></tr>
<tr><td><strong>Emails</strong></td><td>Customizable templates for Order confirmations and more</td></tr>
<tr><td><strong>Open</strong></td><td>Fully open source. Never be locked in again</td></tr>
Expand Down Expand Up @@ -187,6 +191,9 @@ Find a bug, a typo, or something that’s not documented well? We’d love for y
We love your pull requests! Check out our [`Good First Issue`](https://github.com/reactioncommerce/reaction/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) and [`Help Wanted`](https://github.com/reactioncommerce/reaction/issues?q=label%3A%22help+wanted%22) tags for good issues to tackle.
Check out our [contributors guide](CONTRIBUTING.md) for more information

> ✅ This project welcomes contributions from developers of all skill levels!


### License

Reaction is [GNU GPLv3 Licensed](./LICENSE.md)
13 changes: 13 additions & 0 deletions packages/api-core/src/util/apiKeyManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import crypto from "crypto";

const apiKeys = {};

export function createApiKey(systemName) {
const apiKey = crypto.randomBytes(20).toString("hex");
apiKeys[apiKey] = { systemName, createdAt: new Date() };
return apiKey;
}

export function validateApiKey(key) {
return apiKeys.hasOwnProperty(key);
}
44 changes: 23 additions & 21 deletions packages/api-core/src/util/appEvents.js
< 8000 td class="blob-num blob-num-addition empty-cell">
Original file line number Diff line number Diff line change
@@ -1,31 +1,20 @@
import Logger from "@reactioncommerce/logger";
import SimpleSchema from "simpl-schema";
import { publishEvent } from "./pubSub"; // Import Redis Pub/Sub

/**
* This is a temporary events solution on our path to
* event streams and services. For now, some code relies
* on events happening synchronously and we need it to
* work in Fibers when running within Meteor.
*/
// Define schemas for validation
const eventSchemas = {
"orderCreated": new SimpleSchema({ orderId: String, userId: String, totalAmount: Number }),
"userRegistered": new SimpleSchema({ userId: String, email: String })
};

/**
* @summary calls each function in an array with args, one at a time
* @param {String} name Event name
* @param {Function[]} funcs List of functions to call
* @param {Array} args Arguments to pass to each function
* @returns {undefined} Promise that resolves with undefined after all
* functions in the list have been called
*/
async function synchronousPromiseLoop(name, funcs, args) {
const func = funcs.shift();

// One function failing should not prevent others from running,
// so catch and log
try {
await func(...args);
} catch (error) {
Logger.error(`Error in "${name}" consumer`, error);
}

if (funcs.length) {
await synchronousPromiseLoop(name, funcs, args);
}
Expand All @@ -48,18 +37,31 @@ class AppEvents {
async emit(name, ...args) {
if (this.stopped || !this.handlers[name]) return;

// Can't use forEach or map because we want each func to wait
// until the previous func promise resolves
// Validate event arguments if a schema exists
if (eventSchemas[name]) {
const validationContext = eventSchemas[name].newContext();
validationContext.validate(args[0]);

if (!validationContext.isValid()) {
Logger.error(`Validation failed for event "${name}":`, validationContext.validationErrors());
return;
}
}

// Publish event to Redis Pub/Sub
publishEvent(name, args[0]);

// Execute local handlers
await synchronousPromiseLoop(name, this.handlers[name].slice(0), args);
}

on(name, func) {
if (!this.handlers[name]) {
this.handlers[name] = [];
}

this.handlers[name].push(func);
}
}

export default new AppEvents();
export { AppEvents };
28 changes: 28 additions & 0 deletions packages/api-core/src/util/pubSub.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import redis from "redis";

const redisUrl = process.env.REDIS_URL || "redis://localhost:6379";

export const redisClient = redis.createClient({ url: redisUrl });

redisClient.on("error", (err) => console.error("[RedisClient] Error:", err));

redisClient.connect();

/**
* Publish an event to Redis Pub/Sub
*/
export function publishEvent(eventType, payload) {
redisClient.publish(eventType, JSON.stringify(payload));
}

/**
* Subscribe to events in Redis
*/
export function subscribeEvent(eventType, handler) {
const subscriber = redisClient.duplicate();
subscriber.connect().then(() => {
subscriber.subscribe(eventType, (message) => {
handler(JSON.parse(message));
});
});
}
31 changes: 31 additions & 0 deletions packages/api-core/src/util/webhookManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import fetch from "node-fetch";
import crypto from "crypto";

const registeredWebhooks = [];

export function registerWebhook(eventType, url, apiKey) {
registeredWebhooks.push({ eventType, url, apiKey });
}

export function triggerWebhooks(eventType, payload) {
registeredWebhooks.filter(wh => wh.eventType === eventType).forEach(wh => {
sendWebhookRequest(wh, payload);
});
}

function sendWebhookRequest(webhook, payload) {
fetch(webhook.url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": webhook.apiKey,
"x-signature": signPayload(payload),
},
body: JSON.stringify(payload),
}).catch(err => console.error(`[WebhookManager] Error: ${err.message}`));
}

function signPayload(payload) {
const secret = process.env.WEBHOOK_SECRET || "defaultSecret";
return crypto.createHmac("sha256", secret).update(JSON.stringify(payload)).digest("hex");
}
0