Skip to content
This repository has been archived by the owner on Dec 9, 2024. It is now read-only.

Commit

Permalink
Setup/Port mindful pacakge from site org into base-cms
Browse files Browse the repository at this point in the history
  • Loading branch information
B77Mills committed Oct 29, 2024
1 parent ff12242 commit dcfb107
Show file tree
Hide file tree
Showing 7 changed files with 316 additions and 0 deletions.
94 changes: 94 additions & 0 deletions packages/mindful/api-client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
const debug = require('debug')('mindful:api');
const fetch = require('node-fetch');
const { get } = require('@parameter1/base-cms-object-path');
const GraphQLError = require('graphql');
const gql = require('graphql-tag');
const Joi = require('joi');

const { extractFragmentName, getOperationName } = require('./utils');

const schemas = {
constructor: Joi.object({
namespace: Joi.string().required(),
url: Joi.string().allow(null, ''),
}).required(),
};

/**
* @typedef ConstructorParams
* @prop {string} namespace
* @prop {string} [url]
*/
class MindfulApiClient {
/**
*
* @param {ConstructorParams} params
*/
constructor(params) {
/** @type {ConstructorParams} */
const { namespace, url } = Joi.attempt(params, schemas.constructor);
this.namespace = namespace;
this.url = url || 'https://graphql.mindfulcms.com/query';
}

/**
* @param {object} args
* @param {string} args._id
* @param {import("graphql").DocumentNode|string} fragment
*/
async getAdvertisingPostById({ _id }, fragment) {
const fragmentName = extractFragmentName({ fragment, throwOnEmpty: true });
return this.query({
query: gql`
query GetAdvertisingPostById($_id: ObjectID!) {
advertisingPostById(_id: $_id){
...${fragmentName}
}
}
${fragment}
`,
variables: { _id },
});
}

/**
* @param {object} params
* @param {import("graphql").DocumentNode} params.query
* @param {object} [params.variables]
*/
async query({ query, variables }) {
const { url, namespace } = this;
const method = 'post';
const headers = {
'content-type': 'application/json',
'x-namespace': namespace,
};

const body = JSON.stringify({
operationName: getOperationName(query),
variables,
query: get(query, 'loc.source.body', query),
});
const r = await fetch(url, {
method,
headers,
query,
variables,
body,
});

const res = await r.json();
const dbg = {
req: { url, ...{ method, headers, opts: { variables, query }} },
res: { headers: r.headers.raw(), body: res },
};
if (!r.ok || res.errors) {
debug(`${method.toUpperCase()} ${url} ${r.status} ERR`, dbg);
throw new GraphQLError(r, res);
}
debug(`${method.toUpperCase()} ${url} ${r.status} OK`, dbg);
return res;
}
}

module.exports = { MindfulApiClient };
57 changes: 57 additions & 0 deletions packages/mindful/graphql/advertising-post-by-id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
const gql = require('graphql-tag');

module.exports = gql`
query advertisingPostById($_id: ObjectID!) {
advertisingPostById(_id: $_id) {
_id
title {
default
}
teaser
body
url
publishedDay
statusEdge {
node {
_id
label
}
}
featuredImageEdge {
_id
node {
_id
src {
url
settings {
fpX
fpY
}
}
}
}
companyEdge {
_id
node {
_id
name {
default
}
logoEdge {
_id
node {
_id
src {
url
settings {
fpX
fpY
}
}
}
}
}
}
}
}
`;
26 changes: 26 additions & 0 deletions packages/mindful/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const { MindfulApiClient } = require('./api-client');
const { MindfulMarkoWebService } = require('./marko-web/service');

/**
* @param {import("express").Application} app
* @param {MindfulConfig} params
*
* @returns {MindfulConfig}
*/

module.exports = (params = {}) => (req, res, next) => {
const client = new MindfulApiClient(params);
const mindful = {
client,
service: new MindfulMarkoWebService({ client }),
};

req.mindful = mindful;
res.locals.mindful = mindful;
next();
};

/**
* @prop {string} namespace An object id representing the default subscription question
* @prop {string} url The mindful url default https://graphql.mindfulcms.com/query
*/
24 changes: 24 additions & 0 deletions packages/mindful/marko-web/service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const convertAdvertisingPostToNativeStory = require(('./utils/convert-advertising-post-to-native-story'));

class MindfulMarkoWebService {
/**
* @param {object} params
* @param {import("./api-client.js").MindfulApiClient} params.client
*/
constructor({ client }) {
this.client = client;
this.convertAdvertisingPostToNativeStory = convertAdvertisingPostToNativeStory;
}

/**
* Get post by Id
*
* @param {string} externalId
* @returns {Promise}
*/
async getAdvertisingPostById({ _id }, fragment) {
const post = await this.client.getAdvertisingPostById({ _id }, fragment);
return post;
}
}
module.exports = { MindfulMarkoWebService };
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
module.exports = ({ advertisingPost, preview }) => {
const {
_id: id,
title,
publishedDay,
statusEdge,
featuredImageEdge,
companyEdge,
...rest
} = advertisingPost || {};
const { label } = statusEdge && statusEdge.node ? statusEdge.node : {};
const isPublished = label === 'Published' && (new Date() >= new Date(publishedDay));
if (id && (isPublished || preview)) {
const { _id: imageId, src } = featuredImageEdge.node || { src: { settings: {} } };
const primaryImage = {
id: imageId,
src: src.url,
// @todo Determine if these are the correct values for focal point
focalPoint: {
x: src.settings.fpX,
y: src.settings.fpY,
},
};
const { _id: companyId, name, logoEdge } = companyEdge.node || { logoEdge: {} };
const { _id: logoId, src: logoSrc } = logoEdge.node || { src: { settings: {} } };
const advertiser = {
id: companyId,
name,
logo: {
id: logoId,
src: logoSrc.url,
// @todo Determine if these are the correct values for focal point
focalPoint: {
x: logoSrc.settings.fpX,
y: logoSrc.settings.fpY,
},
},
};
const story = {
id,
title: title.default,
// ex: publishedDay is a string of format YYYY-MM-DD
publishedAt: Number(new Date(publishedDay)),
primaryImage,
advertiser,
...rest,
};
return story;
}
return {};
};
34 changes: 34 additions & 0 deletions packages/mindful/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"name": "@parameter1/base-cms-mindful",
"version": "0.0.1",
"author": "Brian Miller <[email protected]>",
"repository": "https://github.com/parameter1/base-cms-mindful",
"license": "MIT",
"private": true,
"scripts": {
"lint": "eslint --ext .js --ext .vue --max-warnings 5 --config ../../.eslintrc.js --ignore-path ../../.eslintignore ./",
"lint:fix": "yarn lint --fix",
"test": "yarn lint"
},
"dependencies": {
"@parameter1/base-cms-env": "^4.5.12",
"@parameter1/base-cms-object-path": "^4.40.3",
"@parameter1/base-cms-utils": "^4.40.3",
"@parameter1/joi": "^1.2.10",
"debug": "^4.1.1",
"express": "^4.17.1",
"graphql": "^14.5.4",
"graphql-tag": "^2.12.6",
"joi": "^17.7.0",
"newrelic": "^9.10.2",
"node-fetch": "^2.6.1"
},
"engines": {
"node": ">=14.15"
},
"os": [
"darwin",
"linux",
"win32"
]
}
30 changes: 30 additions & 0 deletions packages/mindful/utils/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* @param {string} param
* @returns {?string}
*/
const getOperationName = (string) => {
const matches = /query\s+([a-z0-9]+)[(]?.+{/gi.exec(string);
if (matches && matches[1]) return matches[1];
return undefined;
};

/**
* @param {object} params
* @param {import("graphql").DocumentNode|string} params.fragment
* @param {boolean} [params.throwOnEmpty]
* @returns {?string}
*/
const extractFragmentName = ({ fragment, throwOnEmpty }) => {
const pattern = /fragment (.*) on/;
if (typeof fragment === 'string') return fragment.match(pattern)[1];
if (fragment && fragment.kind && fragment.kind === 'Document') {
return fragment.loc.source.body.match(pattern)[1];
}
if (throwOnEmpty) throw new Error('Unable to extract fragment');
return null;
};

module.exports = {
extractFragmentName,
getOperationName,
};

0 comments on commit dcfb107

Please sign in to comment.