Skip to content

Commit

Permalink
Merge pull request #1 from commercetools/order-syncer-scripts
Browse files Browse the repository at this point in the history
Order syncer scripts
  • Loading branch information
leungkinghin-ct authored Oct 16, 2023
2 parents 614d5b6 + 7033ce9 commit caaf13f
Show file tree
Hide file tree
Showing 16 changed files with 6,843 additions and 4 deletions.
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@ rad.json
#License-file
*.flf
#Test results file
TestResults.xml
TestResults.xml
# Dependency directories
node_modules/
# environment variables
tax-calculator/.env
order-syncer/.env
.env
6,403 changes: 6,402 additions & 1 deletion order-syncer/package-lock.json

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions order-syncer/src/clients/build.client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ClientBuilder } from '@commercetools/sdk-client-v2';
import { authMiddlewareOptions } from '../middlewares/auth.middleware.js';
import { httpMiddlewareOptions } from '../middlewares/http.middleware.js';
import { readConfiguration } from '../utils/config.util.js';

/**
* Create a new client builder.
* This code creates a new client builder that can be used to make API calls
*/
export const createClient = () =>
new ClientBuilder()
.withProjectKey(readConfiguration().projectKey)
.withClientCredentialsFlow(authMiddlewareOptions)
.withHttpMiddleware(httpMiddlewareOptions)
.build();
29 changes: 29 additions & 0 deletions order-syncer/src/clients/create.client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { createApiBuilderFromCtpClient } from '@commercetools/platform-sdk';
import { createClient } from './build.client.js';
import { readConfiguration } from '../utils/config.util.js';

/**
* Create client with apiRoot
* apiRoot can now be used to build requests to the Composable Commerce API
*/
export const createApiRoot = ((root) => () => {
if (root) {
return root;
}

root = createApiBuilderFromCtpClient(createClient()).withProjectKey({
projectKey: readConfiguration().projectKey,
});

return root;
})();

/**
* Example code to get the Project details
* This code has the same effect as sending a GET
* request to the commercetools Composable Commerce API without any endpoints.
*
*/
export const getProject = async () => {
return await createApiRoot().get().execute();
};
54 changes: 54 additions & 0 deletions order-syncer/src/connectors/action.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
export async function deleteChangedOrderSubscription(apiRoot, ctpOrderChangeSubscriptionKey) {
const {
body: { results: subscriptions },
} = await apiRoot
.subscriptions()
.get({
queryArgs: {
where: `key = "${ctpOrderChangeSubscriptionKey}"`,
},
})
.execute();

if (subscriptions.length > 0) {
const subscription = subscriptions[0];

await apiRoot
.subscriptions()
.withKey({ key: ctpOrderChangeSubscriptionKey })
.delete({
queryArgs: {
version: subscription.version,
},
})
.execute();
}
}

export async function createChangedOrderSubscription(
apiRoot,
topicName,
projectId,
ctpOrderChangeSubscriptionKey
) {
await deleteChangedOrderSubscription(apiRoot, ctpOrderChangeSubscriptionKey);

await apiRoot
.subscriptions()
.post({
body: {
key: ctpOrderChangeSubscriptionKey,
destination: {
type: 'GoogleCloudPubSub',
topic: topicName,
projectId,
},
changes: [
{
resourceTypeId: 'order',
},
],
},
})
.execute();
}
27 changes: 27 additions & 0 deletions order-syncer/src/connectors/post-deploy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { createApiRoot } from '../clients/create.client.js';
import { createChangedOrderSubscription } from './action.js';

const CONNECT_GCP_TOPIC_NAME_KEY = 'CONNECT_GCP_TOPIC_NAME';
const CONNECT_GCP_PROJECT_ID_KEY = 'CONNECT_GCP_PROJECT_ID';
const CTP_ORDER_CHANGE_SUBSCRIPTION_KEY = 'CTP_ORDER_CHANGE_SUBSCRIPTION_KEY';

async function postDeploy(properties) {
const topicName = properties.get(CONNECT_GCP_TOPIC_NAME_KEY);
const projectId = properties.get(CONNECT_GCP_PROJECT_ID_KEY);
const ctpOrderChangeSubscriptionKey = properties.get(CTP_ORDER_CHANGE_SUBSCRIPTION_KEY);

const apiRoot = createApiRoot();
await createChangedOrderSubscription(apiRoot, topicName, projectId, ctpOrderChangeSubscriptionKey);
}

async function run() {
try {
const properties = new Map(Object.entries(process.env));
await postDeploy(properties);
} catch (error) {
process.stderr.write(`Post-deploy failed: ${error.message}\n`);
process.exitCode = 1;
}
}

run();
23 changes: 23 additions & 0 deletions order-syncer/src/connectors/pre-undeploy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createApiRoot } from '../clients/create.client.js';

import { deleteChangedOrderSubscription } from './actions.js';

const CTP_ORDER_CHANGE_SUBSCRIPTION_KEY = 'CTP_ORDER_CHANGE_SUBSCRIPTION_KEY';

async function preUndeploy(properties) {
const apiRoot = createApiRoot();
const ctpOrderChangeSubscriptionKey = properties.get(CTP_ORDER_CHANGE_SUBSCRIPTION_KEY);
await deleteChangedOrderSubscription(apiRoot, ctpOrderChangeSubscriptionKey);
}

async function run() {
try {
const properties = new Map(Object.entries(process.env));
await preUndeploy(properties);
} catch (error) {
process.stderr.write(`Post-undeploy failed: ${error.message}\n`);
process.exitCode = 1;
}
}

run();
3 changes: 1 addition & 2 deletions order-syncer/src/controllers/sync.controller.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { logger } from '../utils/logger.utils.js';
import { logger } from '../utils/logger.util.js';

export const syncHandler = async (request, response) => {
try {

// TODO: implement
} catch (err) {
logger.error(err);
Expand Down
11 changes: 11 additions & 0 deletions order-syncer/src/errors/custom.error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class CustomError extends Error {
constructor(statusCode, message, errors) {
super(message);
this.statusCode = statusCode;
this.message = message;
if (errors) {
this.errors = errors;
}
}
}
export default CustomError;
14 changes: 14 additions & 0 deletions order-syncer/src/middlewares/auth.middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { readConfiguration } from '../utils/config.util.js';

/**
* Configure Middleware. Example only. Adapt on your own
*/
export const authMiddlewareOptions = {
host: `https://auth.${readConfiguration().region}.commercetools.com`,
projectKey: readConfiguration().projectKey,
credentials: {
clientId: readConfiguration().clientId,
clientSecret: readConfiguration().clientSecret,
},
scopes: [readConfiguration().scope ? readConfiguration().scope : 'default'],
};
24 changes: 24 additions & 0 deletions order-syncer/src/middlewares/error.middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import CustomError from '../errors/custom.error.js';

/**
* Middleware for error handling
* @param error The error object
* @param req The express request
* @param res The Express response
* @param next
* @returns
*/
export const errorMiddleware = (error, _req, res, _next) => {
if (error instanceof CustomError) {
if (typeof error.statusCode === 'number') {
res.status(error.statusCode).json({
message: error.message,
errors: error.errors,
});

return;
}
}

res.status(500).send('Internal server error');
};
8 changes: 8 additions & 0 deletions order-syncer/src/middlewares/http.middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { readConfiguration } from '../utils/config.util.js';

/**
* Configure Middleware. Example only. Adapt on your own
*/
export const httpMiddlewareOptions = {
host: `https://api.${readConfiguration().region}.commercetools.com`,
};
32 changes: 32 additions & 0 deletions order-syncer/src/utils/config.util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import CustomError from '../errors/custom.error.js';
import envValidators from '../validators/env-var.validator.js';
import { getValidateMessages } from '../validators/helpers.validator.js';

/**
* Read the configuration env vars
* (Add yours accordingly)
*
* @returns The configuration with the correct env vars
*/

export const readConfiguration = () => {
const envVars = {
clientId: process.env.CTP_CLIENT_ID,
clientSecret: process.env.CTP_CLIENT_SECRET,
projectKey: process.env.CTP_PROJECT_KEY,
scope: process.env.CTP_SCOPE,
region: process.env.CTP_REGION,
};

const validationErrors = getValidateMessages(envValidators, envVars);

if (validationErrors.length) {
throw new CustomError(
'InvalidEnvironmentVariablesError',
'Invalid Environment Variables please check your .env file',
validationErrors
);
}

return envVars;
};
File renamed without changes.
55 changes: 55 additions & 0 deletions order-syncer/src/validators/env-var.validator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import {
optional,
standardString,
standardKey,
region,
} from './helpers.validator.js';

/**
* Create here your own validators
*/
const envValidators = [
standardString(
['clientId'],
{
code: 'InValidClientId',
message: 'Client id should be 24 characters.',
referencedBy: 'environmentVariables',
},
{ min: 24, max: 24 }
),

standardString(
['clientSecret'],
{
code: 'InvalidClientSecret',
message: 'Client secret should be 32 characters.',
referencedBy: 'environmentVariables',
},
{ min: 32, max: 32 }
),

standardKey(['projectKey'], {
code: 'InvalidProjectKey',
message: 'Project key should be a valid string.',
referencedBy: 'environmentVariables',
}),

optional(standardString)(
['scope'],
{
code: 'InvalidScope',
message: 'Scope should be at least 2 characters long.',
referencedBy: 'environmentVariables',
},
{ min: 2, max: undefined }
),

region(['region'], {
code: 'InvalidRegion',
message: 'Not a valid region.',
referencedBy: 'environmentVariables',
}),
];

export default envValidators;
Loading

0 comments on commit caaf13f

Please sign in to comment.