Skip to content

Commit

Permalink
Refreshed with latest from origin repo
Browse files Browse the repository at this point in the history
  • Loading branch information
Franck Chastagnol committed May 20, 2020
1 parent 21022c7 commit 401d429
Show file tree
Hide file tree
Showing 20 changed files with 725 additions and 2,716 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# ![Origin Protocol](../marketplace/data/origin-header.png)

A decentralized e-commerce store served entirely from IPFS.

## Repo structure
- shop: front-end code
- backend: server code
- packages: utility packages
- scripts: development helper scripts

## Getting started
To bring up the example store and the server on your local:
```
yarn install
DATA_DIR=example yarn run start
```

## Documentation
- Front-end [README](./shop/README.md)
- Server [README](./backend/README.md)

4 changes: 2 additions & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,12 @@
"body-parser": "^1.19.0",
"bottleneck": "^2.19.5",
"bs58": "^4.0.1",
"bull": "^3.14.0",
"bull-board": "^0.7.0",
"cids": ">0.5.5",
"cloudflare": "^2.7.0",
"commander": "^4.1.1",
"connect-session-sequelize": "^6.1.1",
"cookie-parser": "^1.4.4",
"cors": "^2.8.5",
"dayjs": "^1.8.14",
"dotenv": "^8.2.0",
Expand All @@ -39,7 +40,6 @@
"ipfs-deploy": "7.14.0",
"ipfs-http-client": "^44.0.3",
"lodash": "^4.17.15",
"memorystore": "^1.6.1",
"mjml": "^4.5.1",
"node-fetch": "^2.6.0",
"nodemailer": "^6.3.1",
Expand Down
56 changes: 56 additions & 0 deletions backend/queues/fallbackQueue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* A fallback "queue" to use when redis is unavailable. It just runs
* the queue's processing function immediately when you add a job.
*
* Thus, this this isn't actually a queue at all, and contains
* no retries or error handling. But your job gets run.
*/
class FallbackQueue {
/**
* @param {*} name - name of queue
* @param {object} opts - unused, here for compatibility only
*/
constructor(name, opts) {
this.name = name
this.processor = undefined
this.opts = opts
}

/**
* Adds a job to the queue. Job will be run immediately, and you
* can `await` the job's completion if your processing function supports it.
* @param {*} data
*/
async add(data) {
console.log(this.name + ' queue using inline queue processor fallback.')
if (this.processor == undefined) {
throw new Error('No processor defined for this fake job queue')
}
const job = {
data: data,
progress: () => undefined,
log: console.log
}
await this.processor(job)
}

/**
* Registers your job processing function.
* @param {function} fn - code the runs for each submitted job
*/
process(fn) {
this.processor = fn
}

/**
* Do nothing stub
*/
resume() {}

/**
* Do nothing stub
*/
pause() {}
}

module.exports = FallbackQueue
13 changes: 13 additions & 0 deletions backend/queues/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const queues = require('./queues')

/**
* Attaches all backend queue processing functions to their respective queues.
*/
function runProcessors() {
require('./makeOfferProcessor').attachToQueue()
}

module.exports = {
queues,
runProcessors
}
144 changes: 144 additions & 0 deletions backend/queues/makeOfferProcessor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
const queues = require('./queues')

const Web3 = require('web3')

const { ListingID } = require('../utils/id')
const { Shop, Network } = require('../models')
const { post, getBytes32FromIpfsHash } = require('../utils/_ipfs')
const encConf = require('../utils/encryptedConfig')
const abi = require('../utils/_abi')

const ZeroAddress = '0x0000000000000000000000000000000000000000'

function attachToQueue() {
const queue = queues['makeOfferQueue']
queue.process(processor)
queue.resume() // Start if paused
}

/**
* Processes a credit card transaction and submit it to the blockchain.
*
* job.data should have {shopId, amount, encryptedData}
* @param {*} job
*/
async function processor(job) {
const log = (progress, str) => {
job.log(str)
job.progress(progress)
}

const { shopId, amount, encryptedData } = job.data
const shop = await getShop(shopId)
log(5, 'Load encrypted shop config')
const shopConfig = getShopConfig(shop)
const network = await getNetwork(shop.networkId)

log(10, 'Creating offer')
const lid = ListingID.fromFQLID(shop.listingId)
const offer = createOfferJson(lid, amount, encryptedData)
const ires = await postOfferIPFS(network, offer)

log(20, 'Submitting Offer')
const web3 = new Web3(network.provider)
const account = web3.eth.accounts.wallet.add(shopConfig.web3Pk)
const walletAddress = account.address
log(22, `using walletAddress ${walletAddress}`)
log(25, 'Sending to marketplace')
const tx = await offerToMarketplace(
web3,
lid,
network,
walletAddress,
offer,
ires
)
log(50, JSON.stringify(tx))

// TODO: Code to prevent duplicate txs
// TODO Record tx and wait for TX to go through the blockchain

log(100, 'Finished')
}

async function getShop(id) {
try {
return await Shop.findOne({ where: { id } })
} catch (err) {
err.message = `Could not load shop from ID '${id}'. ${err.message}`
throw err
}
}

async function getNetwork(networkId) {
const network = await Network.findOne({
where: { networkId: networkId, active: true }
})
if (!network) {
throw new Error(`Could not find network ${networkId}`)
}
if (!network.marketplaceContract) {
throw new Error(
'Missing marketplaceContract address for network. Unable to send transaction.'
)
}
return network
}

function getShopConfig(shop) {
const shopConfig = encConf.getConfig(shop.config)
if (!shopConfig.web3Pk) {
throw new Error('No PK configured for shop')
}
return shopConfig
}

function createOfferJson(lid, amount, encryptedData) {
return {
schemaId: 'https://schema.originprotocol.com/offer_2.0.0.json',
listingId: lid.toString(),
listingType: 'unit',
unitsPurchased: 1,
totalPrice: {
amount: 0,
currency: 'encrypted'
},
commission: { currency: 'OGN', amount: '0' },
finalizes: 60 * 60 * 24 * 14, // 2 weeks after offer accepted
encryptedData: encryptedData
}
}

async function postOfferIPFS(network, offer) {
try {
return await post(network.ipfsApi, offer, true)
} catch (err) {
err.message = `Error adding offer to ${network.ipfsApi}! ${err.message}`
throw err
}
}

async function offerToMarketplace(
web3,
lid,
network,
walletAddress,
offer,
ires
) {
const Marketplace = new web3.eth.Contract(abi, network.marketplaceContract)
const offerTx = Marketplace.methods.makeOffer(
lid.listingId,
getBytes32FromIpfsHash(ires),
offer.finalizes,
ZeroAddress, // Affiliate
'0',
'0',
ZeroAddress,
walletAddress // Arbitrator
)
const tx = await offerTx.send({ from: walletAddress, gas: 350000 })
return tx
}

module.exports = { processor, attachToQueue }
32 changes: 32 additions & 0 deletions backend/queues/queues.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* @file Queue objects for adding processing dshop jobs.
*
* If no REDIS_URL defined, will fallback to prossing jobs as the jobs are
* submitted, without using a queue.
*
* Example REDIS_URL=redis://0.0.0.0:6379
*/

const { REDIS_URL } = require('../utils/const')

const Queue = REDIS_URL ? require('bull') : require('./fallbackQueue')
const backendUrl = REDIS_URL ? REDIS_URL : undefined
const queueOpts = {}

const all = [
new Queue('makeOffer', backendUrl, queueOpts),
new Queue('download', backendUrl, queueOpts),
new Queue('discord', backendUrl, queueOpts),
new Queue(
'email',
backendUrl,
Object.assign(queueOpts, {
maxStalledCount: 0 // We don't want to risk sending an email twice
})
)
]

module.exports = {}
for (const queue of all) {
module.exports[`${queue.name}Queue`] = queue
}
44 changes: 44 additions & 0 deletions backend/queues/ui.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* @file Shows a UI for our queues.
*/

const { REDIS_URL } = require('../utils/const')
const { authSuperUser } = require('../routes/_auth')

/**
* If we are not using bull for a queue, don't setup the UI for it.
* @param {*} app
*/
function noUi(app) {
app.get('/super-admin/queue', authSuperUser, function(req, res) {
res.send('Redis is not configured. Queuing disabled.')
})
}

/**
* Use the bull board queue UI, and attach it to all our queues.
* @param {*} app
*/
function bullBoardUI(app) {
const { UI, setQueues } = require('bull-board')
const queues = require('./queues')

// Add all queues to the UI
const allQueues = []
for (const name of Object.keys(queues)) {
const queue = queues[name]
allQueues.push(queue)
}
setQueues(allQueues)

// Use the UI
app.use('/super-admin/queue', authSuperUser, UI)
}

module.exports = function(app) {
if (REDIS_URL != undefined) {
bullBoardUI(app)
} else {
noUi(app)
}
}
43 changes: 0 additions & 43 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
"name": "root",
"private": true,
"dependencies": {
<<<<<<< HEAD
"lerna": "3.20.2"
},
"workspaces": {
Expand All @@ -14,48 +13,6 @@
"nohoist": [
"**/openzeppelin-solidity"
]
=======
"@google-cloud/bigquery": "^4.7.0",
"@google-cloud/dns": "^2.0.0",
"aws-sdk": "^2.572.0",
"bcrypt": "^3.0.7",
"body-parser": "^1.19.0",
"bottleneck": "^2.19.5",
"bs58": "^4.0.1",
"bull": "^3.14.0",
"bull-board": "^0.7.0",
"cids": ">0.5.5",
"cloudflare": "^2.7.0",
"commander": "^4.1.1",
"connect-session-sequelize": "^6.1.1",
"cors": "^2.8.5",
"dayjs": "^1.8.14",
"dotenv": "^8.2.0",
"envkey": "^1.2.7",
"ethereumjs-util": "^6.2.0",
"express": "^4.17.1",
"express-session": "^1.17.0",
"form-data": "^3.0.0",
"inquirer": "^7.0.4",
"ipfs-deploy": "7.14.0",
"ipfs-http-client": "^44.0.3",
"lodash": "^4.17.15",
"mjml": "^4.5.1",
"node-fetch": "^2.6.0",
"nodemailer": "^6.3.1",
"openpgp": "^4.6.2",
"pg": "7.18.1",
"randomstring": "^1.1.5",
"reconnecting-websocket": "^4.4.0",
"sequelize": "^5.21.2",
"sequelize-cli": "^5.5.1",
"serve-static": "^1.14.1",
"sharp": "0.24.0",
"sqlite3": "^4.1.1",
"stripe": "^8.0.0",
"web3": "1.0.0-beta.34",
"ws": "^7.2.0"
>>>>>>> 05660731bd28654b7f147d8e76eec094e663cd98
},
"devDependencies": {
"babel-eslint": "10.1.0",
Expand Down
Loading

0 comments on commit 401d429

Please sign in to comment.