mirror of https://gitlab.com/ecentrics/concordia
Ezerous
4 years ago
20 changed files with 2053 additions and 1589 deletions
@ -1,3 +1,11 @@ |
|||||
export const FORUM_CONTRACT = 'Forum'; |
export const FORUM_CONTRACT = 'Forum'; |
||||
export const POST_VOTING_CONTRACT = 'PostVoting'; |
export const POST_VOTING_CONTRACT = 'PostVoting'; |
||||
export const VOTING_CONTRACT = 'Voting'; |
export const VOTING_CONTRACT = 'Voting'; |
||||
|
|
||||
|
const CONTRACTS = [ |
||||
|
FORUM_CONTRACT, |
||||
|
POST_VOTING_CONTRACT, |
||||
|
VOTING_CONTRACT, |
||||
|
]; |
||||
|
|
||||
|
export default CONTRACTS; |
||||
|
@ -0,0 +1,67 @@ |
|||||
|
import { |
||||
|
REACT_APP_CONCORDIA_HOST_DEFAULT, |
||||
|
REACT_APP_CONCORDIA_PORT_DEFAULT, |
||||
|
REACT_APP_CONTRACTS_SUPPLIER_HOST_DEFAULT, |
||||
|
REACT_APP_CONTRACTS_SUPPLIER_PORT_DEFAULT, |
||||
|
REACT_APP_CONTRACTS_VERSION_HASH_DEFAULT, |
||||
|
} from '../constants/configuration/defaults'; |
||||
|
import CONTRACTS from '../constants/contracts/ContractNames'; |
||||
|
|
||||
|
function getContractsDownloadRequest() { |
||||
|
const CONTRACTS_SUPPLIER_HOST = process.env.REACT_APP_CONTRACTS_SUPPLIER_HOST |
||||
|
|| REACT_APP_CONTRACTS_SUPPLIER_HOST_DEFAULT; |
||||
|
const CONTRACTS_SUPPLIER_PORT = process.env.REACT_APP_CONTRACTS_SUPPLIER_PORT |
||||
|
|| REACT_APP_CONTRACTS_SUPPLIER_PORT_DEFAULT; |
||||
|
const CONTRACTS_VERSION_HASH = process.env.REACT_APP_CONTRACTS_VERSION_HASH |
||||
|
|| REACT_APP_CONTRACTS_VERSION_HASH_DEFAULT; |
||||
|
const HOST = process.env.REACT_APP_CONCORDIA_HOST || REACT_APP_CONCORDIA_HOST_DEFAULT; |
||||
|
const PORT = process.env.REACT_APP_CONCORDIA_PORT || REACT_APP_CONCORDIA_PORT_DEFAULT; |
||||
|
|
||||
|
const xhrRequest = new XMLHttpRequest(); |
||||
|
|
||||
|
xhrRequest.open('GET', |
||||
|
`http://${CONTRACTS_SUPPLIER_HOST}:${CONTRACTS_SUPPLIER_PORT}/contracts/${CONTRACTS_VERSION_HASH}`, |
||||
|
false); |
||||
|
xhrRequest.setRequestHeader('Access-Control-Allow-Origin', `${HOST}:${PORT}`); |
||||
|
xhrRequest.setRequestHeader('Access-Control-Allow-Credentials', 'true'); |
||||
|
|
||||
|
return xhrRequest; |
||||
|
} |
||||
|
|
||||
|
function validateRemoteContracts(remoteContracts) { |
||||
|
if (remoteContracts.length !== CONTRACTS.length) { |
||||
|
throw new Error(`Version mismatch detected. Artifacts brought ${remoteContracts.length} contracts but app
|
||||
|
expected ${CONTRACTS.length}`);
|
||||
|
} |
||||
|
|
||||
|
const contractsPresentStatus = CONTRACTS.map((contract) => ({ |
||||
|
contract, |
||||
|
present: remoteContracts.includes((remoteContract) => remoteContract.contractName === contract), |
||||
|
})); |
||||
|
|
||||
|
if (contractsPresentStatus.reduce((accumulator, contract) => accumulator && contract.present, true)) { |
||||
|
throw new Error(`Contracts missing from artifacts. Supplier didn't bring ${contractsPresentStatus |
||||
|
.filter((contractPresentStatus) => contractPresentStatus.present === false) |
||||
|
.map((contractPresentStatus) => contractPresentStatus.contract) |
||||
|
.join(', ')}.`);
|
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const downloadContractArtifactsSync = () => { |
||||
|
const xhrRequest = getContractsDownloadRequest(); |
||||
|
|
||||
|
xhrRequest.send(null); |
||||
|
|
||||
|
if (xhrRequest.status === 200) { |
||||
|
const contractsRawData = xhrRequest.responseText; |
||||
|
const remoteContracts = JSON.parse(contractsRawData); |
||||
|
|
||||
|
validateRemoteContracts(remoteContracts); |
||||
|
|
||||
|
return remoteContracts; |
||||
|
} |
||||
|
|
||||
|
throw new Error(`Remote contract artifacts download request failed!\n${xhrRequest.responseText}`); |
||||
|
}; |
||||
|
|
||||
|
export default downloadContractArtifactsSync; |
@ -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'], |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
}; |
@ -0,0 +1,24 @@ |
|||||
|
# 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 |
||||
|
|
||||
|
contracts-uploads |
@ -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" |
||||
|
} |
||||
|
} |
@ -0,0 +1,11 @@ |
|||||
|
import path from 'path'; |
||||
|
|
||||
|
const PROVIDER_PORT = '8400'; |
||||
|
const UPLOAD_CONTRACTS_DIRECTORY = path.join(__dirname, '..', 'contracts-uploads'); |
||||
|
const CORS_ALLOWED_ORIGINS = ['http://127.0.0.1:7000', 'http://localhost:7000']; |
||||
|
|
||||
|
export default { |
||||
|
port: PROVIDER_PORT, |
||||
|
uploadsDirectory: UPLOAD_CONTRACTS_DIRECTORY, |
||||
|
corsAllowedOrigins: CORS_ALLOWED_ORIGINS, |
||||
|
}; |
@ -0,0 +1,31 @@ |
|||||
|
import * as fs from 'fs'; |
||||
|
import path from 'path'; |
||||
|
import { getStorageLocation, getTagsDirectory } from '../utils/storageUtils'; |
||||
|
|
||||
|
const downloadContracts = async (req, res) => { |
||||
|
const { params: { hash: hashOrTag } } = req; |
||||
|
let directoryPath = getStorageLocation(hashOrTag); |
||||
|
|
||||
|
if (!fs.existsSync(directoryPath)) { |
||||
|
const tagsDirectory = getTagsDirectory(); |
||||
|
|
||||
|
if (fs.existsSync(tagsDirectory)) { |
||||
|
const tagFilePath = path.join(tagsDirectory, hashOrTag); |
||||
|
const tagReference = fs.readFileSync(tagFilePath, 'utf-8'); |
||||
|
|
||||
|
directoryPath = getStorageLocation(tagReference); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
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; |
@ -0,0 +1,37 @@ |
|||||
|
import path from 'path'; |
||||
|
import fs from 'fs'; |
||||
|
import upload from '../middleware/upload'; |
||||
|
import { getTagsDirectory } from '../utils/storageUtils'; |
||||
|
|
||||
|
const addOrTransferTag = (tag, hash) => { |
||||
|
const tagsDirectory = getTagsDirectory(); |
||||
|
const tagFilePath = path.join(tagsDirectory, tag); |
||||
|
|
||||
|
fs.mkdirSync(tagsDirectory, { recursive: true }); |
||||
|
fs.writeFileSync(tagFilePath, hash); |
||||
|
}; |
||||
|
|
||||
|
const uploadContracts = async (req, res) => { |
||||
|
try { |
||||
|
await upload(req, res); |
||||
|
|
||||
|
const { body: { tag } } = req; |
||||
|
const { params: { hash } } = req; |
||||
|
|
||||
|
if (tag) { |
||||
|
addOrTransferTag(tag, hash); |
||||
|
} |
||||
|
|
||||
|
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; |
@ -0,0 +1,25 @@ |
|||||
|
import express from 'express'; |
||||
|
import cors from 'cors'; |
||||
|
import initRoutes from './routes/web'; |
||||
|
import constants from './constants'; |
||||
|
|
||||
|
const PROVIDER_PORT = process.env.CONTRACTS_PROVIDER_PORT || constants.port; |
||||
|
const ALLOWED_ORIGINS = process.env.CORS_ALLOWED_ORIGINS |
||||
|
? process.env.CORS_ALLOWED_ORIGINS.split(';') |
||||
|
: constants.corsAllowedOrigins; |
||||
|
|
||||
|
const app = express(); |
||||
|
|
||||
|
const corsOptions = { |
||||
|
origin: ALLOWED_ORIGINS, |
||||
|
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(PROVIDER_PORT, () => { |
||||
|
console.log(`Contracts provider listening at http://127.0.0.1:${PROVIDER_PORT}`); |
||||
|
}); |
@ -0,0 +1,30 @@ |
|||||
|
import * as util from 'util'; |
||||
|
import * as fs from 'fs'; |
||||
|
import multer from 'multer'; |
||||
|
import { getStorageLocation } from '../utils/storageUtils'; |
||||
|
|
||||
|
const storage = multer.diskStorage({ |
||||
|
destination: (req, file, callback) => { |
||||
|
const { params: { hash } } = req; |
||||
|
const contractsPath = getStorageLocation(hash); |
||||
|
|
||||
|
fs.mkdirSync(contractsPath, { recursive: true }); |
||||
|
callback(null, contractsPath); |
||||
|
}, |
||||
|
filename: (req, file, callback) => { |
||||
|
const match = ['application/json']; |
||||
|
|
||||
|
if (match.indexOf(file.mimetype) === -1) { |
||||
|
const message = `<strong>${file.originalname}</strong> 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; |
@ -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; |
@ -0,0 +1,17 @@ |
|||||
|
import path from 'path'; |
||||
|
import constants from '../constants'; |
||||
|
|
||||
|
export const getStorageLocation = (hash) => { |
||||
|
const UPLOADS_DIRECTORY = process.env.UPLOAD_CONTRACTS_DIRECTORY || constants.uploadsDirectory; |
||||
|
|
||||
|
if (hash) { |
||||
|
return path.join(UPLOADS_DIRECTORY, hash); |
||||
|
} |
||||
|
|
||||
|
return UPLOADS_DIRECTORY; |
||||
|
}; |
||||
|
|
||||
|
export const getTagsDirectory = () => { |
||||
|
const uploadsPath = getStorageLocation(); |
||||
|
return path.join(uploadsPath, '/tags'); |
||||
|
}; |
@ -0,0 +1,32 @@ |
|||||
|
const path = require('path'); |
||||
|
const unirest = require('unirest'); |
||||
|
const { contracts } = require('../index'); |
||||
|
const defaults = require('../constants/config/defaults'); |
||||
|
|
||||
|
const uploadContractsToProviderUnirest = (versionHash, tag) => { |
||||
|
const CONTRACTS_PROVIDER_HOST = process.env.CONTRACTS_PROVIDER_HOST || defaults.contractsProviderHost; |
||||
|
const CONTRACTS_PROVIDER_PORT = process.env.CONTRACTS_PROVIDER_PORT || defaults.contractsProviderPort; |
||||
|
|
||||
|
const uploadPath = `http://${CONTRACTS_PROVIDER_HOST}:${CONTRACTS_PROVIDER_PORT}/contracts/${versionHash}`; |
||||
|
const request = unirest('POST', uploadPath) |
||||
|
.field('tag', tag); |
||||
|
|
||||
|
contracts |
||||
|
.forEach((contract) => request |
||||
|
.attach('contracts', path.join(__dirname, '../', 'build/', `${contract.contractName}.json`))); |
||||
|
|
||||
|
console.log(`Uploading to ${uploadPath}`); |
||||
|
request.end((res) => { |
||||
|
if (res.error) { |
||||
|
throw new Error(`Failed to upload contracts to provider: ${res.error}`); |
||||
|
} |
||||
|
|
||||
|
console.log('Contracts uploaded to provider.'); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
const main = () => { |
||||
|
uploadContractsToProviderUnirest(process.argv[2], process.argv[3]); |
||||
|
}; |
||||
|
|
||||
|
main(); |
File diff suppressed because it is too large
Loading…
Reference in new issue