From 755ff0e717b057a7e61b889f82dab70b95ffa417 Mon Sep 17 00:00:00 2001 From: apostolof Date: Wed, 3 Feb 2021 02:16:58 +0200 Subject: [PATCH] feat: init contracts provider --- .../src/constants/contracts/ContractNames.js | 2 + .../src/options/drizzleOptions.js | 12 +++- .../concordia-app/src/utils/drizzleUtils.js | 47 +++++++++++++++ .../concordia-contracts-provider/.eslintrc.js | 60 +++++++++++++++++++ .../concordia-contracts-provider/.gitignore | 22 +++++++ .../concordia-contracts-provider/package.json | 26 ++++++++ .../src/constants.js | 3 + .../src/controllers/download.js | 19 ++++++ .../src/controllers/upload.js | 19 ++++++ .../concordia-contracts-provider/src/index.js | 19 ++++++ .../src/middleware/upload.js | 32 ++++++++++ .../src/routes/web.js | 14 +++++ 12 files changed, 274 insertions(+), 1 deletion(-) create mode 100755 packages/concordia-app/src/utils/drizzleUtils.js create mode 100755 packages/concordia-contracts-provider/.eslintrc.js create mode 100755 packages/concordia-contracts-provider/.gitignore create mode 100755 packages/concordia-contracts-provider/package.json create mode 100755 packages/concordia-contracts-provider/src/constants.js create mode 100755 packages/concordia-contracts-provider/src/controllers/download.js create mode 100755 packages/concordia-contracts-provider/src/controllers/upload.js create mode 100755 packages/concordia-contracts-provider/src/index.js create mode 100755 packages/concordia-contracts-provider/src/middleware/upload.js create mode 100755 packages/concordia-contracts-provider/src/routes/web.js diff --git a/packages/concordia-app/src/constants/contracts/ContractNames.js b/packages/concordia-app/src/constants/contracts/ContractNames.js index 8ddebb5..edaef10 100644 --- a/packages/concordia-app/src/constants/contracts/ContractNames.js +++ b/packages/concordia-app/src/constants/contracts/ContractNames.js @@ -1,3 +1,5 @@ export const FORUM_CONTRACT = 'Forum'; export const POST_VOTING_CONTRACT = 'PostVoting'; export const VOTING_CONTRACT = 'Voting'; +export const NUMBER_OF_CONTRACTS = 4; +export const NUMBER_OF_CONTRACTS = 4; diff --git a/packages/concordia-app/src/options/drizzleOptions.js b/packages/concordia-app/src/options/drizzleOptions.js index 7d689ee..1323bbb 100644 --- a/packages/concordia-app/src/options/drizzleOptions.js +++ b/packages/concordia-app/src/options/drizzleOptions.js @@ -2,13 +2,23 @@ import { contracts } from 'concordia-contracts'; import web3Options from './web3Options'; import appEvents from '../constants/contracts/events'; +import { downloadContractArtifactsSync } from '../utils/drizzleUtils'; const drizzleOptions = { web3: web3Options, - contracts, events: { ...appEvents }, reloadWindowOnNetworkChange: true, reloadWindowOnAccountChange: true, // We need it to reinitialize breeze and create new Orbit databases }; +const CONTRACTS_SUPPLIER_URL = process.env.REACT_APP_CONTRACTS_SUPPLIER; + +if (!CONTRACTS_SUPPLIER_URL) { + drizzleOptions.contracts = contracts; +} else { + const remoteContracts = downloadContractArtifactsSync(); + console.log(remoteContracts); + drizzleOptions.contracts = remoteContracts; +} + export default drizzleOptions; diff --git a/packages/concordia-app/src/utils/drizzleUtils.js b/packages/concordia-app/src/utils/drizzleUtils.js new file mode 100755 index 0000000..613d0cd --- /dev/null +++ b/packages/concordia-app/src/utils/drizzleUtils.js @@ -0,0 +1,47 @@ +// const downloadContractArtifacts = async () => { +import { NUMBER_OF_CONTRACTS } from '../constants/contracts/ContractNames'; + +export const downloadContractArtifacts = async () => { + const headers = new Headers(); + headers.append('Access-Control-Allow-Origin', 'http://localhost:7000'); + headers.append('Access-Control-Allow-Credentials', 'true'); + + const requestOptions = { + method: 'GET', + redirect: 'follow', + headers, + }; + + const remoteContracts = await fetch('http://127.0.0.1:8400/contracts/asdf', requestOptions) + .then((response) => response.text()) + .then((contractsRawData) => JSON.parse(contractsRawData)); + + if (remoteContracts.length !== NUMBER_OF_CONTRACTS) { + throw new Error(`Version mismatch detected. Artifacts brought ${remoteContracts.length} contracts but app + expected ${NUMBER_OF_CONTRACTS}`); + } + + return remoteContracts; +}; + +export const downloadContractArtifactsSync = () => { + const xhrRequest = new XMLHttpRequest(); + // xhrRequest.withCredentials = true; + xhrRequest.open('GET', 'http://127.0.0.1:8400/contracts/asdf', false); + xhrRequest.setRequestHeader('Access-Control-Allow-Origin', 'http://localhost:7000'); + xhrRequest.setRequestHeader('Access-Control-Allow-Credentials', 'true'); + xhrRequest.send(null); + + if (xhrRequest.status === 200) { + const contractsRawData = xhrRequest.responseText; + const remoteContracts = JSON.parse(contractsRawData); + + if (remoteContracts.length !== NUMBER_OF_CONTRACTS) { + throw new Error(`Version mismatch detected. Artifacts brought ${remoteContracts.length} contracts but app + expected ${NUMBER_OF_CONTRACTS}`); + } + + return remoteContracts; + } + throw new Error(`Remote contract artifacts download request failed!\n${xhrRequest.responseText}`); +}; diff --git a/packages/concordia-contracts-provider/.eslintrc.js b/packages/concordia-contracts-provider/.eslintrc.js new file mode 100755 index 0000000..7ca05eb --- /dev/null +++ b/packages/concordia-contracts-provider/.eslintrc.js @@ -0,0 +1,60 @@ +module.exports = { + env: { + browser: true, + es6: true, + jest: true, + }, + extends: [ + 'plugin:react/recommended', + 'airbnb', + ], + globals: { + Atomics: 'readonly', + SharedArrayBuffer: 'readonly', + }, + parser: 'babel-eslint', + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + ecmaVersion: 2018, + sourceType: 'module', + }, + plugins: [ + 'react', + 'react-hooks', + ], + rules: { + 'react/jsx-props-no-spreading': 'off', + 'import/extensions': 'off', + 'react/jsx-indent': [ + 'error', + 4, + { + checkAttributes: true, + indentLogicalExpressions: true, + }, + ], + 'react/require-default-props': 'off', + 'react/prop-types': 'off', + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'error', + 'max-len': ['warn', { code: 120, tabWidth: 4 }], + 'no-unused-vars': 'warn', + 'no-console': 'warn', + 'no-shadow': 'warn', + 'no-multi-str': 'warn', + 'jsx-a11y/label-has-associated-control': [2, { + labelAttributes: ['label'], + controlComponents: ['Input'], + depth: 3, + }], + }, + settings: { + 'import/resolver': { + node: { + extensions: ['.js', '.jsx'], + }, + }, + }, +}; diff --git a/packages/concordia-contracts-provider/.gitignore b/packages/concordia-contracts-provider/.gitignore new file mode 100755 index 0000000..b1c784e --- /dev/null +++ b/packages/concordia-contracts-provider/.gitignore @@ -0,0 +1,22 @@ +# Node +/node_modules + +# IDE +.DS_Store +.idea + +# Build Directories +/build +/src/build + +# Logs +/log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Misc +.env.local +.env.development.local +.env.test.local +.env.production.local diff --git a/packages/concordia-contracts-provider/package.json b/packages/concordia-contracts-provider/package.json new file mode 100755 index 0000000..7bdc166 --- /dev/null +++ b/packages/concordia-contracts-provider/package.json @@ -0,0 +1,26 @@ +{ + "name": "concordia-contracts-provider", + "description": "A server that provides built contracts for Concordia.", + "version": "0.1.0", + "private": true, + "main": "src/index.js", + "scripts": { + "start": "node -r esm src/index.js" + }, + "license": "MIT", + "dependencies": { + "cors": "^2.8.5", + "esm": "~3.2.25", + "express": "^4.17.1", + "lodash": "^4.17.20", + "multer": "^1.4.2", + "multiparty": "^4.2.2" + }, + "devDependencies": { + "eslint": "^7.19.0", + "eslint-config-airbnb": "^18.2.1", + "eslint-plugin-jsx-a11y": "^6.4.1", + "eslint-plugin-react": "^7.22.0", + "eslint-plugin-react-hooks": "^4.2.0" + } +} diff --git a/packages/concordia-contracts-provider/src/constants.js b/packages/concordia-contracts-provider/src/constants.js new file mode 100755 index 0000000..e46b59b --- /dev/null +++ b/packages/concordia-contracts-provider/src/constants.js @@ -0,0 +1,3 @@ +export const API_HOST = '127.0.0.1'; +export const API_PORT = '8400'; +export const UPLOADED_CONTRACTS_DIR = './contracts-uploads/'; diff --git a/packages/concordia-contracts-provider/src/controllers/download.js b/packages/concordia-contracts-provider/src/controllers/download.js new file mode 100755 index 0000000..5748fff --- /dev/null +++ b/packages/concordia-contracts-provider/src/controllers/download.js @@ -0,0 +1,19 @@ +import * as fs from 'fs'; +import path from 'path'; +import { UPLOADED_CONTRACTS_DIR } from '../constants'; + +const downloadContracts = async (req, res) => { + const { params: { hash } } = req; + const directoryPath = path.join(`${__dirname}/../../${UPLOADED_CONTRACTS_DIR}/${hash}`); + const contracts = []; + + fs.readdirSync(directoryPath).forEach((contractFilename) => { + const rawContractData = fs.readFileSync(path.join(`${directoryPath}/${contractFilename}`), 'utf-8'); + const contractJson = JSON.parse(rawContractData); + contracts.push(contractJson); + }); + + res.send(contracts); +}; + +export default downloadContracts; diff --git a/packages/concordia-contracts-provider/src/controllers/upload.js b/packages/concordia-contracts-provider/src/controllers/upload.js new file mode 100755 index 0000000..018a047 --- /dev/null +++ b/packages/concordia-contracts-provider/src/controllers/upload.js @@ -0,0 +1,19 @@ +import upload from '../middleware/upload'; + +const uploadContracts = async (req, res) => { + try { + await upload(req, res); + + if (req.files.length <= 0) { + return res.send('You must select at least 1 file.'); + } + + return res.send('Files have been uploaded.'); + } catch (error) { + console.log(error); + + return res.send(`Error when trying upload many files: ${error}`); + } +}; + +export default uploadContracts; diff --git a/packages/concordia-contracts-provider/src/index.js b/packages/concordia-contracts-provider/src/index.js new file mode 100755 index 0000000..462db85 --- /dev/null +++ b/packages/concordia-contracts-provider/src/index.js @@ -0,0 +1,19 @@ +import express from 'express'; +import cors from 'cors'; +import initRoutes from './routes/web'; +import { API_HOST, API_PORT } from './constants'; + +const app = express(); + +const corsOptions = { + origin: ['localhost:7000', '127.0.0.1:7000', 'http://localhost:7000'], + optionsSuccessStatus: 200, // some legacy browsers (IE11, various SmartTVs) choke on 204 +}; + +app.use(express.urlencoded({ extended: true })); +app.use(cors(corsOptions)); + +initRoutes(app); +app.listen(API_PORT, () => { + console.log(`Contracts provider listening at http://${API_HOST}:${API_PORT}`); +}); diff --git a/packages/concordia-contracts-provider/src/middleware/upload.js b/packages/concordia-contracts-provider/src/middleware/upload.js new file mode 100755 index 0000000..ced18fa --- /dev/null +++ b/packages/concordia-contracts-provider/src/middleware/upload.js @@ -0,0 +1,32 @@ +import * as util from 'util'; +import * as path from 'path'; +import * as fs from 'fs'; +import multer from 'multer'; +import { UPLOADED_CONTRACTS_DIR } from '../constants'; + +const storage = multer.diskStorage({ + destination: (req, file, callback) => { + const { params: { hash } } = req; + const contractsPath = path.join(`${__dirname}/../../${UPLOADED_CONTRACTS_DIR}/${hash}`); + + fs.mkdirSync(contractsPath, { recursive: true }); + callback(null, contractsPath); + // callback(null, UPLOADED_CONTRACTS_DIR); + }, + filename: (req, file, callback) => { + const match = ['application/json']; + + if (match.indexOf(file.mimetype) === -1) { + const message = `${file.originalname} is invalid. Only JSON files are accepted.`; + return callback(message, null); + } + + const filename = `${file.originalname}`; + callback(null, filename); + }, +}); + +const uploadFiles = multer({ storage }).array('contracts'); +const uploadFilesMiddleware = util.promisify(uploadFiles); + +export default uploadFilesMiddleware; diff --git a/packages/concordia-contracts-provider/src/routes/web.js b/packages/concordia-contracts-provider/src/routes/web.js new file mode 100755 index 0000000..5bdd4fb --- /dev/null +++ b/packages/concordia-contracts-provider/src/routes/web.js @@ -0,0 +1,14 @@ +import express from 'express'; +import downloadContracts from '../controllers/download'; +import uploadContracts from '../controllers/upload'; + +const router = express.Router(); + +const routes = (app) => { + router.get('/contracts/:hash', downloadContracts); + router.post('/contracts/:hash', uploadContracts); + + return app.use('/', router); +}; + +export default routes;