forked from OriginProtocol/dshop
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refreshed with latest from origin repo
- Loading branch information
Franck Chastagnol
committed
May 20, 2020
1 parent
21022c7
commit 401d429
Showing
20 changed files
with
725 additions
and
2,716 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
#  | ||
|
||
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) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.