diff --git a/.gitignore b/.gitignore index 924a810..24785c6 100755 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,6 @@ /node_modules .DS_Store /rethinkdb_data -/src/config.json \ No newline at end of file +/src/config.json +/venv +*.gz \ No newline at end of file diff --git a/rethinkdb_dump_2019-08-14T12:49:08.tar.gz b/rethinkdb_dump_2019-08-14T12:49:08.tar.gz deleted file mode 100644 index b353933..0000000 Binary files a/rethinkdb_dump_2019-08-14T12:49:08.tar.gz and /dev/null differ diff --git a/src/api/albums.js b/src/api/albums.js index 63649b0..82cd307 100644 --- a/src/api/albums.js +++ b/src/api/albums.js @@ -8,6 +8,7 @@ import xss from 'xss' import uuid from 'uuid/v4' import { provider } from '../lib/ethers-utils' import escapeRegex from 'escape-string-regexp' +import { isAddress } from 'web3-utils' // addresses that can moderate comments :) // const whitelist = [] @@ -17,17 +18,34 @@ export default ({ config, db, io }) => { r.table('albums') .get(id) .default({}) - .do((doc) => { - return doc.merge({ - user: r.table('users').get(doc('userAddress')) - .without('clovers', 'curationMarket').default(null) - }) - }) + .do(mergeUser) .run(db, (res) => { callback(res) }) } + async function makeUser(userAddress) { + const modified = await provider.getBlockNumber() + var user = userTemplate(userAddress.toLowerCase()) + user.created = modified + user.modified = modified + + // db update + const { changes } = await r.table('users') + .insert(user, { returnChanges: true }) + .run(db) + .catch((err) => { + console.error(err) + res.sendStatus(500).end() + return + }) + if (changes[0]) { + user = changes[0].new_val + } + io.emit('updateUser', user) + return user + } + let router = resource({ load, id: 'id', @@ -84,12 +102,7 @@ export default ({ config, db, io }) => { .getAll(true, { index }) .orderBy(asc ? r.asc(sort) : r.desc(sort)) .slice(start, start + pageSize) - .map((doc) => { - return doc.merge({ - user: r.table('users').get(doc('userAddress')) - .without('clovers', 'curationMarket').default(null) - }) - }) + .map(mergeUser) .run(db, (err, data) => { if (err) throw new Error(err) return data @@ -134,12 +147,8 @@ export default ({ config, db, io }) => { const { id } = req.params const result = await r.table('albums').get(id) - .do((doc) => { - return doc.merge({ - user: r.table('users').get(doc('userAddress')) - .without('clovers', 'curationMarket').default(null) - }) - }) + .do(mergeUser) + .do(mergeEditors) .default({}) .run(db) .catch((err) => { @@ -160,12 +169,8 @@ export default ({ config, db, io }) => { let result = await r.table('albums') .getAll(true, { index }) .pluck('id', 'clovers', 'name', 'userAddress') - .map((doc) => { - return doc.merge({ - user: r.table('users').get(doc('userAddress')) - .without('clovers', 'curationMarket').default(null) - }) - }).coerceTo('array').run(db).catch((err) => { + .map(mergeUser) + .coerceTo('array').run(db).catch((err) => { console.error(err) res.result(500).end() }) @@ -259,38 +264,18 @@ export default ({ config, db, io }) => { res.json({ ...album, id: generated_keys[0] }).end() }) }) - - - async function makeUser(userAddress) { - const modified = await provider.getBlockNumber() - var user = userTemplate(userAddress.toLowerCase()) - user.created = modified - user.modified = modified - - // db update - const { changes } = await r.table('users') - .insert(user, { returnChanges: true }) - .run(db) - .catch((err) => { - console.error(err) - res.sendStatus(500).end() - return - }) - if (changes[0]) { - user = changes[0].new_val - } - io.emit('updateUser', user) - return user - } }) + // update album router.put('/:id', async (req, res) => { - let { albumName, clovers } = req.body + let { albumName, clovers, editors } = req.body if (!albumName || !clovers) { return res.status(500).end() } + const { id } = req.params + // check user const userAddress = req.auth && req.auth.user if (!userAddress) { console.error("no userAddress") @@ -314,11 +299,15 @@ export default ({ config, db, io }) => { return res.status(401).send('Different album with that name already exists') } + // get album let album = await r.table('albums').get(id).run(db) + const isOwner = user.address === album.userAddress + const isEditor = album.editors && album.editors.includes(user.address) - albumName = xss(albumName) // check if albumName was changed - if (album.name !== albumName && album.userAddress !== user.address) { + albumName = xss(albumName) + const nameChange = album.name !== albumName + if (nameChange && !isOwner) { // cant change name of album unless you are owner return res.status(401).send('Only owner can change name') } @@ -329,34 +318,56 @@ export default ({ config, db, io }) => { return res.status(500).send(error.message) } + // check editors + editors = (editors || []).map(ed => ed.toLowerCase()) + editors = [...new Set(editors)] // de-dupe ES6 + const editorsCopy = JSON.parse(JSON.stringify(album.editors || [])) + const sameEditors = editors.length === editorsCopy.length && editors.every(e => editorsCopy.includes(e)) + + if (!sameEditors && !isOwner) { + // can't change editors unless you own the album + return res.status(401).send('Only owner can change editors') + } + + if (editors.length && !editors.every(e => isAddress(e))) { + // valid ETH addr + return res.status(401).send('Editors must be a valid ETH address.') + } + + if (editors.length > 4) { + // max editors + return res.status(401).send('Max 4 editors') + } + // check if any clovers were removed... let cloversCopy = JSON.parse(JSON.stringify(album.clovers)) clovers.forEach(c => { let i = cloversCopy.indexOf(c) cloversCopy.splice(i, 1) }); - if (cloversCopy.length > 0 && album.userAddress !== user.address) { - // can't remove clovers unless you own the album - return res.status(401).send('Only owner can remove clovers') + + if (cloversCopy.length > 0 && !(isOwner || isEditor)) { + // can't remove clovers unless you can edit + return res.status(401).send('Only editors can remove clovers') } // must update something - if (album.name === albumName && album.clovers.join('') === clovers.join('')) { + if (!nameChange && album.clovers.join('') === clovers.join('') && sameEditors) { return res.status(400).send('Must update something') } + const emitLog = nameChange || clovers.length > album.clovers.length // name change or added clover + const blockNum = await provider.getBlockNumber().catch((err) => { debug(err.toString()) return 0 }) - album.name = albumName - album.clovers = clovers - album.modified = new Date() // update it r.table('albums').get(id).update({ - name: album.name, - clovers: album.clovers, - modified: album.modified + name: albumName, + clovers: clovers, + modified: new Date(), + editors: editors, }).run(db, async (err, _) => { if (err) { console.error('db run error') @@ -364,6 +375,12 @@ export default ({ config, db, io }) => { return } + // get updated album + album = await r.table('albums').get(id) + .do(mergeUser) + .do(mergeEditors) + .run(db) + // update the user await r.table('users').get(user.address).update({ albumCount: r.table('albums') @@ -371,38 +388,44 @@ export default ({ config, db, io }) => { .count() }, { nonAtomic: true }).run(db) - // emit an event pls - const log = { - id: uuid(), - name: 'Album_Updated', - removed: false, - blockNumber: blockNum, - userAddress: null, // necessary data below - data: { - id, - userAddress: user.address, - name: albumName, - board: clovers.length > 0 && clovers[0], - createdAt: new Date() - }, - userAddresses: [] - } + // add a log ? + if (emitLog) { + const log = { + id: uuid(), + name: 'Album_Updated', + removed: false, + blockNumber: blockNum, + userAddress: null, // necessary data below + data: { + id, + userAddress: user.address, + name: albumName, + board: clovers.length > 0 && clovers[0], + createdAt: new Date() + }, + userAddresses: [] + } - r.table('logs').insert(log) - .run(db, (err) => { - if (err) { - debug('album log not saved') - debug(err) - } else { - try { - io.emit('newLog', log) - } catch (error) { - console.error(error) + r.table('logs').insert(log) + .run(db, (err) => { + if (err) { + debug('album log not saved') + debug(err) + } else { + try { + io.emit('newLog', log) + } catch (error) { + console.error(error) + } } - } - res.status(200).json({ ...album, id }).end() - }) - }) + res.status(200).json({ ...album, id }).end() + }) + + // no log + } else { + res.status(200).json({ ...album, id }).end() + } + }) }) router.delete('/:id', async (req, res) => { @@ -474,6 +497,17 @@ export function albumListener (server, db) { } +// util to add user to doc +const mergeUser = doc => doc.merge({ + user: r.table('users').get(doc('userAddress')) + .without('clovers', 'curationMarket').pluck('address', 'name').default(null) +}) +// util to add editors to doc +const mergeEditors = doc => doc.merge({ + editorsData: r.table('users').getAll(r.args(doc('editors').default([]))) + .coerceTo('array').pluck('address', 'name') +}) + async function verifyClovers(clovers, db) { const regex = /\b(0x[0-9a-fA-F]+|[0-9]+)\b/g; clovers.forEach(c => {