From 1936fdb5a63a86abb7a7c0c85e1bc365e09e52d7 Mon Sep 17 00:00:00 2001 From: ourwarmhouse Date: Sun, 27 Sep 2020 16:05:56 +0930 Subject: [PATCH] Added Google merchant product feed --- .gitignore | 3 +- README.md | 12 ++++++++ app.js | 8 ++++++ config/settings.json | 1 + lib/googledata.js | 67 ++++++++++++++++++++++++++++++++++++++++++++ package-lock.json | 15 +++++++++- package.json | 1 + routes/index.js | 14 +++++++++ 8 files changed, 119 insertions(+), 2 deletions(-) create mode 100755 lib/googledata.js diff --git a/.gitignore b/.gitignore index 048472c..d539dbb 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ public/uploads .vscode **.DS_Store env.yaml -ecosystem.config.js \ No newline at end of file +ecosystem.config.js +bin/googleproducts.xml \ No newline at end of file diff --git a/README.md b/README.md index bfd790b..138b449 100644 --- a/README.md +++ b/README.md @@ -443,6 +443,18 @@ You may want to create a static page to show contact details, about us, shipping New static pages are setup via `/admin/settings/pages`. +## Google data + +By default the product data is updated into a Google feed format here: `/googleproducts.xml`. + +You can setup Google to read this data [here](https://merchants.google.com/) + +1. Products > Feeds > (+) +2. Set the Country and language +3. Set A name and `Scheduled fetch` +4. Set the url to `https://mydomain.com/googleproducts.xml` +5. Complete + ## TODO - Modernize the frontend of the admin diff --git a/app.js b/app.js index f672422..f696231 100644 --- a/app.js +++ b/app.js @@ -21,6 +21,7 @@ const { getConfig, getPaymentConfig, updateConfigLocal } = require('./lib/config const { runIndexing } = require('./lib/indexing'); const { addSchemas } = require('./lib/schema'); const { initDb, getDbUri } = require('./lib/db'); +const { writeGoogleData } = require('./lib/googledata'); let handlebars = require('express-handlebars'); const i18n = require('i18n'); @@ -481,6 +482,13 @@ initDb(config.databaseConnectionString, async (err, db) => { }); }); + await writeGoogleData(db); + + // Fire up the cron job to create google product feed + cron.schedule('0 * * * *', async () => { + await writeGoogleData(db); + }); + // Set trackStock for testing if(process.env.NODE_ENV === 'test'){ config.trackStock = true; diff --git a/config/settings.json b/config/settings.json index 9540704..d085846 100644 --- a/config/settings.json +++ b/config/settings.json @@ -16,6 +16,7 @@ "injectJs": "", "customCss": "", "currencySymbol": "£", + "currencyISO": "USD", "paymentGateway": [ "stripe" ], diff --git a/lib/googledata.js b/lib/googledata.js new file mode 100755 index 0000000..720e206 --- /dev/null +++ b/lib/googledata.js @@ -0,0 +1,67 @@ +const _ = require('lodash'); +const fs = require('fs'); +const path = require('path'); +const convert = require('xml-js'); +const { + getConfig +} = require('./config'); + +const writeGoogleData = async (db) => { + const config = getConfig(); + const fileName = path.join('bin', 'googleproducts.xml'); + + // Remove old file + try{ + fs.unlinkSync(fileName); + }catch(ex){} + + console.log('config', config); + + // Get products + const products = await db.products.find({ productPublished: true }).toArray(); + + // Setup the XML + const xmlOpt = { compact: true, ignoreComment: true, spaces: 4 }; + const jsonObj = { + _declaration: { _attributes: { version: '1.0', encoding: 'utf-8' } }, + rss: { + _attributes: { + 'xmlns:g': 'http://base.google.com/ns/1.0', + version: '2.0' + }, + channel: { + title: config.cartTitle, + description: config.cartDescription, + link: config.baseUrl, + item: [] + } + } + }; + + // Add products + _.forEach(products, (product) => { + const item = { + 'g:title': product.productTitle, + 'g:id': product._id.toString(), + 'g:link': `${config.baseUrl}/product/${product.productPermalink}`, + 'g:description': product.productDescription, + 'g:price': `${product.productPrice} ${config.currencyISO}`, + 'g:availability': 'in stock' + }; + // Add image if exists + if(product.productImage){ + item['g:image_link'] = `${config.baseUrl}/${product.productImage}`; + } + jsonObj.rss.channel.item.push(item); + }); + + // Generate XML + const xml = convert.js2xml(jsonObj, xmlOpt); + + // Write product file + fs.writeFileSync(fileName, xml); +}; + +module.exports = { + writeGoogleData +}; diff --git a/package-lock.json b/package-lock.json index e72cf2d..3f38816 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "express-cart", - "version": "1.1.18", + "version": "1.1.19", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -9432,6 +9432,11 @@ "sparse-bitfield": "^3.0.3" } }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "semver": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", @@ -11010,6 +11015,14 @@ "integrity": "sha1-SWsswQnsqNus/i3HK2A8F8WHCtQ=", "dev": true }, + "xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "requires": { + "sax": "^1.2.4" + } + }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", diff --git a/package.json b/package.json index 9b5a0ef..a1a6442 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "strip-bom": "^3.0.0", "stripe": "^7.12.0", "uglifycss": "0.0.27", + "xml-js": "^1.6.11", "yenv": "^2.1.1" }, "devDependencies": { diff --git a/routes/index.js b/routes/index.js index 81d2913..bb13c2b 100644 --- a/routes/index.js +++ b/routes/index.js @@ -3,6 +3,8 @@ const router = express.Router(); const colors = require('colors'); const stripHtml = require('string-strip-html'); const moment = require('moment'); +const fs = require('fs'); +const path = require('path'); const _ = require('lodash'); const { getId, @@ -30,6 +32,18 @@ const { } = require('../lib/menu'); const countryList = getCountryList(); +// Google products +router.get('/googleproducts.xml', async (req, res, next) => { + let productsFile = ''; + try{ + productsFile = fs.readFileSync(path.join('bin', 'googleproducts.xml')); + }catch(ex){ + console.log('Google products file not found'); + } + res.type('text/plain'); + res.send(productsFile); +}); + // These is the customer facing routes router.get('/payment/:orderId', async (req, res, next) => { const db = req.app.db;