import 'dotenv/config' import grpc from "grpc" import express from "express" import bodyParser from "body-parser" import basicAuth from "express-basic-auth" import { CommandService_v1Client as CommandService, QueryService_v1Client as QueryService } from 'iroha-helpers/lib/proto/endpoint_grpc_pb' import { queries } from 'iroha-helpers' import util from 'util' import { exec } from 'child_process' import cors from 'cors' import * as sql from 'sqlite3' import fs from 'fs' const app = express(); app.use(bodyParser.json()); //TODO is cors package necesary? basic middleware could suffice app.use(cors()); //TODO: set only safe origins app.use(basicAuth({ users: { admin: 'superPasswd' } })); const asyncExec = util.promisify(exec); const IROHA_ADMIN_PRIV = "f101537e319568c765b2cc89698325604991dca57b9716b58016b253506cab70"; const IROHA_ADMIN = "admin@test"; const IROHA_ADDRESS = process.env.IROHA_ADDRESS || "localhost:50051"; const queryService = new QueryService(IROHA_ADDRESS, grpc.credentials.createInsecure()); const CHAIN4ALL_RASTER_CLIP_SCRIPT_PATH = process.env.CHAIN4ALL_RASTER_CLIP_SCRIPT_PATH || '/home/kunickyd/Documents/chain4all/chain4all_raster_clip.sh'; const CHAIN4ALL_SERVICE_PORT = process.env.CHAIN4ALL_SERVICE_PORT || 3000; const PRICE_MODIFIER: number = parseFloat(process.env.PRICE_MODIFIER || "0.5"); const DB_FILE_NAME: string = "data/transfers.db"; app.get("/", (req, res) => { res.send("Chain4All Blockchain service"); }); app.get("/transfers/:userId", async (req, res, next) => { let db = await getDbConnection(); db.all( "SELECT hash " + "FROM transfers " + "WHERE user=? " + "ORDER BY id DESC " + "LIMIT 10;", [req.params.userId], (err, rows) => { if (err) { next(err); return; } res.send(rows); } ); db.close(); }); app.post("/transfer", async (req, res, next) => { try { if (!req.body) { res.status(400); throw Error(JSON.stringify({ error: { name: "Error, request has no body!" } })); } if (!req.body.txHash) { res.status(400); throw Error(JSON.stringify({ error: { name: "Error, request body has no \"txHash\" property!" } })); } if (!req.body.user) { res.status(400); throw Error(JSON.stringify({ error: { name: "Error, request body has no \"user\" property!" } })); } let db = await getDbConnection(); db.run( "INSERT INTO transfers (hash, user)" + "VALUES (?, ?);", [req.body.txHash, req.body.user], (err) => { if (err) { next(err); return; } res.status(201); res.send(); } ); db.close(); } catch (err) { next(err); } }); app.post("/price", (req, res) => { //add caching of same requests if (req.body && req.body.area) { res.send({ price: getPrice(req.body.area) }); } else { res.status(400); throw Error(JSON.stringify({ error: { name: "Error, request has no body with \"area\" property!" } })); } }); app.post("/buy", async (req, res, next) => { try { if (!req.body) { res.status(400); throw Error(JSON.stringify({ error: { name: "Error, request has no body!" } })); } if (!req.body.txHash) { res.status(400); throw Error(JSON.stringify({ error: { name: "Error, request body has no \"txHash\" property!" } })); } //TODO load from http headers, or something like that?? if (!req.body.user) { res.status(400); throw Error(JSON.stringify({ error: { name: "Error, request body has no \"user\" property!" } })); } //TODO validate this properly let txDetail = await getTransactionDetail(req.body.txHash, req.body.user); let extent: number[] = JSON.parse(txDetail.description).extent as number[]; let dataFileId: string = Date.now().toString(); let dataCommand = CHAIN4ALL_RASTER_CLIP_SCRIPT_PATH + ' ' + extent[0] + ' ' + extent[1] + ' ' + extent[2] + ' ' + extent[3] + ' ' + dataFileId; console.debug(dataCommand); const { stdout, stderr } = await asyncExec(dataCommand); console.debug(stdout); if (stderr) { console.warn(stderr); } res.send({ dataUrl: "https://gis.lesprojekt.cz/chain4all/raster_" + dataFileId + ".tif" }); } catch (err) { next(err); } }); function getDbConnection(): sql.Database { return new sql.Database(DB_FILE_NAME); } function initDatabase(): Promise{ return new Promise((resolve, reject) => { if(!fs.existsSync("./data")){ fs.mkdirSync("data"); }; let db = getDbConnection(); db.run( "CREATE TABLE IF NOT EXISTS transfers( " + "id INTEGER PRIMARY KEY, " + "hash TEXT NOT NULL, " + "user TEXT NOT NULL " + ");", (err) => { if (err) { reject(err); } else{ resolve(); } } ); }); } async function getTransactionDetail(txHash: string, user: string) { let quer: any = await queries.getAccountTransactions({ privateKey: IROHA_ADMIN_PRIV, creatorAccountId: IROHA_ADMIN, queryService, timeoutLimit: 5000 }, { accountId: user, pageSize: 10, firstTxHash: txHash, ordering: { field: undefined, direction: undefined }, firstTxTime: undefined, lastTxTime: undefined, firstTxHeight: 1, lastTxHeight: 3 }); //TODO find better way to look for transferAssets command in transaction return quer.transactionsList[0].payload.reducedPayload.commandsList[0].transferAsset; } function getPrice(area: number): number { return area * PRICE_MODIFIER; } function errorMiddleware(err: any, req: any, res: any, next: any): void { //TODO: add custom Exception class console.log(err); res.status(500); res.send(err); } app.use(errorMiddleware); app.listen(CHAIN4ALL_SERVICE_PORT,async () => { try{ await initDatabase(); console.log(`Listening at http://localhost:${CHAIN4ALL_SERVICE_PORT}`); }catch(err){ console.error("Cannot start service!"); console.error(err); } });