diff --git a/README.md b/README.md
index 299088c5bf6..762429a5095 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,10 @@
Mailchimp Open Commerce (formerly Reaction Commerce)
+
+ 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.
+
+
-Fast | Returns data in split seconds, and faster queries mean faster web pages |
+Fast | Returns data in milliseconds, and faster queries mean faster web pages |
Proven | Open Commerce fuels sites doing 10's of thousands of orders per day with 100's of thousands of products |
Composable | A flexible plugin system allows you to pick and choose which integrations work best for you |
Multi-tenant | Host multiple shops in the same installation |
Scalable | Start out with a single server and scale up to hundreds |
-Flexible Products | Allows Products, with options and variants to fit a wide variety of needs |
+Flexible Products | Supports products with customizable options and variants to meet a wide range of business needs. |
Inventory | Track inventory, allow or disallow backorders and more |
Shipping | Integrate with a shipping rate provider or build your own custom table |
Taxes | Integrate with a tax rate provider or build your own custom tax table |
-Fulfillment | Flexible fulfillment system allows you create your own fulfillment methods |
+Fulfillment | Flexible fulfillment system allows you create self-customized fulfillment methods |
Order Tracking | View and manage your orders in the included admin system |
Emails | Customizable templates for Order confirmations and more |
Open | Fully open source. Never be locked in again |
@@ -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)
diff --git a/packages/api-core/src/util/apiKeyManager.js b/packages/api-core/src/util/apiKeyManager.js
new file mode 100644
index 00000000000..feb29a880c7
--- /dev/null
+++ b/packages/api-core/src/util/apiKeyManager.js
@@ -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);
+}
diff --git a/packages/api-core/src/util/appEvents.js b/packages/api-core/src/util/appEvents.js
index d1723d98d9f..5ba995facf4 100644
--- a/packages/api-core/src/util/appEvents.js
+++ b/packages/api-core/src/util/appEvents.js
@@ -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);
}
@@ -48,8 +37,21 @@ 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);
}
@@ -57,9 +59,9 @@ class AppEvents {
if (!this.handlers[name]) {
this.handlers[name] = [];
}
-
this.handlers[name].push(func);
}
}
export default new AppEvents();
+export { AppEvents };
\ No newline at end of file
diff --git a/packages/api-core/src/util/pubSub.js b/packages/api-core/src/util/pubSub.js
new file mode 100644
index 00000000000..fadfc4d6687
--- /dev/null
+++ b/packages/api-core/src/util/pubSub.js
@@ -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));
+ });
+ });
+}
diff --git a/packages/api-core/src/util/webhookManager.js b/packages/api-core/src/util/webhookManager.js
new file mode 100644
index 00000000000..13ce5a3873b
--- /dev/null
+++ b/packages/api-core/src/util/webhookManager.js
@@ -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");
+}