diff --git a/package.json b/package.json index 687c0419..e45a4066 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "climate-warehouse", - "version": "1.0.1", + "version": "1.0.3", "private": true, "bin": "build/server.js", "type": "module", @@ -17,13 +17,17 @@ "resetTestDb": "rm -f ./test.sqlite3 && rm -f ./testMirror.sqlite3", "resetMirrorDb": "npx sequelize-cli db:drop --env mirror", "prepare": "husky install", - "build": "babel src --keep-file-extension --out-dir build --copy-files", + "build": "babel src --keep-file-extension --out-dir build --copy-files && cp package.json ./build", "build-migrations": "babel migrations --keep-file-extension --out-dir dist/migrations --copy-files", "prepare-binary": "rm -rf dist && mkdir dist", "create-win-x64-dist": "npm run build && npm run prepare-binary && pkg package.json -t node16-win-x64 --out-path dist", "create-mac-x64-dist": "npm run build && npm run prepare-binary && pkg package.json -t node16-macos-x64 --out-path dist", "create-linux-x64-dist": "npm run build && npm run prepare-binary && pkg package.json -t node16-linux-x64 --out-path dist" }, + "pkg": { + "scripts": "package.json", + "assets": "package.json" + }, "dependencies": { "body-parser": "^1.19.0", "char-spinner": "^1.0.1", diff --git a/src/datalayer/writeService.js b/src/datalayer/writeService.js index 9c607b79..0ac66994 100644 --- a/src/datalayer/writeService.js +++ b/src/datalayer/writeService.js @@ -36,7 +36,7 @@ const syncDataLayer = async (storeId, data, failedCallback) => { }; const retry = (storeId, changeList, failedCallback, retryAttempts) => { - logger.info('RETRYING...', retryAttempts); + logger.info(`Retrying pushing to store ${storeId}: ${retryAttempts}`); if (retryAttempts >= 60) { logger.info( 'Could not push changelist to datalayer after retrying 10 times', @@ -46,7 +46,6 @@ const retry = (storeId, changeList, failedCallback, retryAttempts) => { } setTimeout(async () => { - logger.info('Retrying...', storeId); await pushChangesWhenStoreIsAvailable( storeId, changeList, diff --git a/src/middleware.js b/src/middleware.js new file mode 100644 index 00000000..51db75d5 --- /dev/null +++ b/src/middleware.js @@ -0,0 +1,111 @@ +'use strict'; + +import _ from 'lodash'; + +import express from 'express'; +import bodyParser from 'body-parser'; +import cors from 'cors'; +import fileUpload from 'express-fileupload'; +import { V1Router } from './routes/v1'; +import { getConfig } from './utils/config-loader'; +import { logger } from './config/logger.cjs'; +import { + assertChiaNetworkMatchInConfiguration, + assertDataLayerAvailable, + assertWalletIsAvailable, +} from './utils/data-assertions'; +import packageJson from '../package.json'; + +const { API_KEY, READ_ONLY } = getConfig().APP; + +const headerKeys = Object.freeze({ + API_VERSION_HEADER_KEY: 'x-api-version', + CR_READY_ONLY_HEADER_KEY: 'cw-read-only', + DATA_MODEL_VERION_HEADER_KEY: 'x-datamodel-version', +}); + +const app = express(); + +app.use( + cors({ + exposedHeaders: Object.values(headerKeys).join(','), + }), +); + +app.use(express.json()); +app.use(bodyParser.urlencoded({ extended: false })); +app.use(fileUpload()); + +// Common assertions on every endpoint +app.use(async function (req, res, next) { + try { + await assertChiaNetworkMatchInConfiguration(); + await assertDataLayerAvailable(); + await assertWalletIsAvailable(); + next(); + } catch (err) { + res.status(400).json({ + message: 'Chia Exception', + error: err.message, + }); + } +}); + +// Add optional API key if set in .env file +app.use(function (req, res, next) { + if (API_KEY && API_KEY !== '') { + const apikey = req.header('x-api-key'); + if (API_KEY === apikey) { + next(); + } else { + res.status(403).json({ message: 'API key not found' }); + } + } else { + next(); + } +}); + +app.use(function (req, res, next) { + if (READ_ONLY) { + res.setHeader(headerKeys.CR_READY_ONLY_HEADER_KEY, READ_ONLY); + } else { + res.setHeader(headerKeys.CR_READY_ONLY_HEADER_KEY, false); + } + + next(); +}); + +app.use(function (req, res, next) { + logger.debug( + `Setting header x-api-verion to package.json version: ${packageJson.version}`, + ); + const version = packageJson.version; + res.setHeader(headerKeys.API_VERSION_HEADER_KEY, version); + + const majorVersion = version.split('.')[0]; + res.setHeader(headerKeys.DATA_MODEL_VERION_HEADER_KEY, `v${majorVersion}`); + + next(); +}); + +app.use('/v1', V1Router); + +app.use((err, req, res, next) => { + if (err) { + if (_.get(err, 'error.details')) { + // format Joi validation errors + return res.status(400).json({ + message: 'Data Validation error', + errors: err.error.details.map((detail) => { + return _.get(detail, 'context.message', detail.message); + }), + }); + } + + return res.status(err.status).json(err); + } + + next(); +}); + +export default app; diff --git a/src/routes/index.js b/src/routes/index.js index 7ccca1d5..f3159a2e 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -1,82 +1,11 @@ 'use strict'; -import _ from 'lodash'; - -import express from 'express'; -import bodyParser from 'body-parser'; -import cors from 'cors'; -import fileUpload from 'express-fileupload'; import { prepareDb } from '../database'; import scheduler from '../tasks'; -import { V1Router } from './v1'; import { sequelize } from '../database'; -import { getConfig } from '../utils/config-loader'; -import { fileLoader } from '../utils/file-loader'; import { logger } from '../config/logger.cjs'; -import { - assertChiaNetworkMatchInConfiguration, - assertDataLayerAvailable, - assertWalletIsAvailable, -} from '../utils/data-assertions'; - -const { API_KEY, READ_ONLY } = getConfig().APP; -const app = express(); - -app.use(cors()); -app.use(express.json()); -app.use(bodyParser.urlencoded({ extended: false })); -app.use(fileUpload()); - -// Common assertions on every endpoint -app.use(async function (req, res, next) { - try { - await assertChiaNetworkMatchInConfiguration(); - await assertDataLayerAvailable(); - await assertWalletIsAvailable(); - next(); - } catch (err) { - res.status(400).json({ - message: 'Chia Exception', - error: err.message, - }); - } -}); - -// Add optional API key if set in .env file -app.use(function (req, res, next) { - if (API_KEY && API_KEY !== '') { - const apikey = req.header('x-api-key'); - if (API_KEY === apikey) { - next(); - } else { - res.status(403).json({ message: 'API key not found' }); - } - } else { - next(); - } -}); - -// Add readonly header if set in .env file -app.use(function (req, res, next) { - res.setHeader('Access-Control-Expose-Headers', 'cw-read-only'); - if (READ_ONLY) { - res.setHeader('cw-read-only', READ_ONLY); - } else { - res.setHeader('cw-read-only', false); - } - next(); -}); - -app.use(function (req, res, next) { - const packageJson = fileLoader('package.json'); - logger.debug(`Setting header x-api-verion to package.json version: ${packageJson.version}`); - res.setHeader('x-api-version', packageJson.version); - - next(); -}); - -app.use('/v1', V1Router); +import app from '../middleware'; sequelize.authenticate().then(async () => { logger.info('Connected to database'); @@ -86,21 +15,4 @@ sequelize.authenticate().then(async () => { }, 5000); }); -app.use((err, req, res, next) => { - if (err) { - if (_.get(err, 'error.details')) { - // format Joi validation errors - return res.status(400).json({ - message: 'Data Validation error', - errors: err.error.details.map((detail) => { - return _.get(detail, 'context.message', detail.message); - }), - }); - } - - return res.status(err.status).json(err); - } - - next(); -}); export default app;