From f929059daf38d6b9539b26abd23a94659cd1b283 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 9 Jun 2017 13:23:16 -0600 Subject: [PATCH 1/6] added asset merging + url updates --- bin/cli-assets | 156 +++++++++++++++++++++++---------- errors.js | 10 ++- services/assets.js | 59 ++++++++++++- test/server/services/assets.js | 94 ++++++++++++++++++-- 4 files changed, 261 insertions(+), 58 deletions(-) diff --git a/bin/cli-assets b/bin/cli-assets index 97b5b043c..4805bead0 100755 --- a/bin/cli-assets +++ b/bin/cli-assets @@ -8,9 +8,12 @@ const program = require('./commander'); const parseDuration = require('ms'); const Table = require('cli-table'); const AssetModel = require('../models/asset'); +const CommentModel = require('../models/comment'); +const AssetsService = require('../services/assets'); const mongoose = require('../services/mongoose'); const scraper = require('../services/scraper'); const util = require('./util'); +const inquirer = require('inquirer'); // Register the shutdown criteria. util.onshutdown([ @@ -20,65 +23,112 @@ util.onshutdown([ /** * Lists all the assets registered in the database. */ -function listAssets() { - AssetModel - .find({}) - .sort({'created_at': 1}) - .then((asset) => { - let table = new Table({ - head: [ - 'ID', - 'Title', - 'URL' - ] - }); +async function listAssets() { + try { + let assets = await AssetModel.find({}).sort({'created_at': 1}); - asset.forEach((asset) => { - table.push([ - asset.id, - asset.title ? asset.title : '', - asset.url ? asset.url : '' - ]); - }); - - console.log(table.toString()); - util.shutdown(); - }) - .catch((err) => { - console.error(err); - util.shutdown(1); + let table = new Table({ + head: [ + 'ID', + 'Title', + 'URL' + ] }); + + assets.forEach((asset) => { + table.push([ + asset.id, + asset.title ? asset.title : '', + asset.url ? asset.url : '' + ]); + }); + + console.log(table.toString()); + util.shutdown(); + } catch (e) { + console.error(e); + util.shutdown(1); + } } -function refreshAssets(ageString) { - const now = new Date().getTime(); - const ageMs = parseDuration(ageString); - const age = new Date(now - ageMs); +async function refreshAssets(ageString) { + try { + const now = new Date().getTime(); + const ageMs = parseDuration(ageString); + const age = new Date(now - ageMs); - AssetModel.find({ - $or: [ - { - scraped: { - $lte: age + let assets = await AssetModel.find({ + $or: [ + { + scraped: { + $lte: age + } + }, + { + scraped: null } - }, - { - scraped: null - } - ] - }) + ] + }); - // Queue all the assets for scraping. - .then((assets) => Promise.all(assets.map(scraper.create))) + // Queue all the assets for scraping. + await Promise.all(assets.map(scraper.create)); - .then(() => { console.log('Assets were queued to be scraped'); util.shutdown(); - }) - .catch((err) => { - console.error(err); + } catch (e) { + console.error(e); util.shutdown(1); - }); + } +} + +async function updateURL(assetID, assetURL) { + try { + await AssetsService.updateURL(assetID, assetURL); + + console.log(`Asset ${assetID} was updated to have url ${assetURL}.`); + util.shutdown(); + } catch (e) { + console.error(e); + util.shutdown(1); + } +} + +async function merge(srcID, dstID) { + try { + + // Grab the assets... + let [srcAsset, dstAsset] = await AssetsService.findByIDs([srcID, dstID]); + if (!srcAsset || !dstAsset) { + throw new Error('Not all assets indicated by id exist, cannot merge'); + } + + // Count the affected resources... + let srcCommentCount = await CommentModel.find({asset_id: srcID}).count(); + + console.log(`Now going to update ${srcCommentCount} comments and delete the source Asset[${srcID}].`); + + let {confirm} = await inquirer.prompt([ + { + type: 'confirm', + name: 'confirm', + message: 'Proceed with merge', + default: false + } + ]); + + if (confirm) { + + // Perform the merge! + await AssetsService.merge(srcID, dstID); + } else { + console.warn('Aborting merge'); + } + + util.shutdown(0); + } catch (e) { + console.error(e); + util.shutdown(1); + } } //============================================================================== @@ -95,6 +145,16 @@ program .description('queues the assets that exceed the age requested') .action(refreshAssets); +program + .command('update-url ') + .description('update the URL of an asset') + .action(updateURL); + +program + .command('merge ') + .description('merges two assets together by moving comments from src to dst and deleting the src asset') + .action(merge); + program.parse(process.argv); // If there is no command listed, output help. diff --git a/errors.js b/errors.js index c203b1d25..87c3a2a65 100644 --- a/errors.js +++ b/errors.js @@ -168,6 +168,13 @@ const ErrCommentTooShort = new APIError('Comment was too short', { status: 400 }); +// ErrAssetURLAlreadyExists is returned when a rename operation is requested +// but an asset already exists with the new url. +const ErrAssetURLAlreadyExists = new APIError('Asset URL already exists, cannot rename', { + translation_key: 'ASSET_URL_ALREADY_EXISTS', + status: 409 +}); + module.exports = { ExtendableError, APIError, @@ -191,5 +198,6 @@ module.exports = { ErrInstallLock, ErrLoginAttemptMaximumExceeded, ErrEditWindowHasEnded, - ErrCommentTooShort + ErrCommentTooShort, + ErrAssetURLAlreadyExists }; diff --git a/services/assets.js b/services/assets.js index a86c4281f..b81513b72 100644 --- a/services/assets.js +++ b/services/assets.js @@ -1,3 +1,4 @@ +const CommentModel = require('../models/comment'); const AssetModel = require('../models/asset'); const SettingsService = require('./settings'); const domainlist = require('./domainlist'); @@ -128,9 +129,61 @@ module.exports = class AssetsService { * @param {Array} ids an array of Strings of asset_id * @return {Promise} resolves to list of Assets */ - static findMultipleById(ids) { - const query = ids.map((id) => ({id})); - return AssetModel.find(query); + static async findByIDs(ids) { + + // Find the assets. + let assets = await AssetModel.find({ + id: { + $in: ids + } + }); + + // Return them in the right order. + return ids.map((id) => assets.find((asset) => asset.id === id)); + } + + static async updateURL(id, url) { + + // Try to see if an asset already exists with the given url. + let asset = await AssetsService.findByUrl(url); + if (asset !== null) { + throw errors.ErrAssetURLAlreadyExists; + } + + // Seems that there was no other asset with the same url, try and perform + // the rename operation! An error may be thrown from this if the operation + // fails. This is ok. + await AssetModel.update({id}, {$set: {url}}); + } + + static async merge(srcAssetID, dstAssetID) { + + // Fetch both assets. + let [srcAsset, dstAsset] = await AssetsService.findByIDs([srcAssetID, dstAssetID]); + if (!srcAsset || !dstAsset) { + throw errors.ErrNotFound; + } + + // Resolve the merge operation, this invloves moving all resources attached + // to the src asset to the dst asset, and then removing the src asset. + + // First, update all the old comments to the new asset. + await CommentModel.update({ + asset_id: srcAssetID + }, { + $set: { + asset_id: dstAssetID + } + }, { + multi: true + }); + + // Second remove the old asset. + await AssetModel.remove({ + id: srcAssetID + }); + + // That's it! } static all(skip = null, limit = null) { diff --git a/test/server/services/assets.js b/test/server/services/assets.js index e46e75576..748e177e0 100644 --- a/test/server/services/assets.js +++ b/test/server/services/assets.js @@ -1,22 +1,29 @@ const AssetModel = require('../../../models/asset'); +const CommentModel = require('../../../models/comment'); const AssetsService = require('../../../services/assets'); +const CommentsService = require('../../../services/comments'); const SettingsService = require('../../../services/settings'); +const url = require('url'); const chai = require('chai'); const expect = chai.expect; +const chaiAsPromised = require('chai-as-promised'); + +chai.use(chaiAsPromised); // Use the chai should. chai.should(); +const settings = {id: '1', moderation: 'PRE', domains: {whitelist: ['new.test.com', 'test.com', 'override.test.com']}}; +const defaults = {url:'http://test.com'}; + describe('services.AssetsService', () => { - beforeEach(() => { - const settings = {id: '1', moderation: 'PRE', domains: {whitelist: ['new.test.com', 'test.com', 'override.test.com']}}; - const defaults = {url:'http://test.com'}; + let asset; + beforeEach(async () => { + await SettingsService.init(settings); - return SettingsService.init(settings).then(() => { - return AssetModel.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true}); - }); + asset = await AssetModel.findOneAndUpdate({id: '1'}, {$setOnInsert: defaults}, {upsert: true, new: true}); }); describe('#findById', ()=> { @@ -120,4 +127,79 @@ describe('services.AssetsService', () => { }); }); }); + + describe('#updateURL', () => { + + it('should change the url if the asset was found, and there was no conflict', async () => { + let newURL = url.resolve(asset.url, '/new-url'); + + // Update the asset. + await AssetsService.updateURL(asset.id, newURL); + + // Check that the url was updated. + let {url: databaseURL} = await AssetsService.findById(asset.id); + + expect(databaseURL).to.equal(newURL); + }); + + it('should error if the new url already exists', async () => { + let newURL = url.resolve(asset.url, '/new-url'); + + // Create a new asset with our new URL. + await AssetModel.findOneAndUpdate({id: '2'}, {$setOnInsert: {url: newURL}}, {upsert: true, new: true}); + + return AssetsService.updateURL(asset.id, newURL).should.eventually.be.rejected; + }); + + }); + + describe('#merge', () => { + + it('should error if either the src or the dst is missing', () => { + return AssetsService.merge('not-found', asset.id).should.eventually.be.rejected; + }); + + it('should merge the assets', async () => { + let newURL = url.resolve(asset.url, '/new-url'); + + // Create a new asset with our new URL. + await AssetModel.findOneAndUpdate({id: '2'}, {$setOnInsert: {url: newURL}}, {upsert: true, new: true}); + + // Create some comments on both assets. + await CommentsService.publicCreate([ + { + asset_id: '1', + body: 'This is a comment!', + status: 'ACCEPTED' + }, + { + asset_id: '1', + body: 'This is a comment!', + status: 'ACCEPTED' + }, + { + asset_id: '2', + body: 'This is a comment!', + status: 'ACCEPTED' + }, + { + asset_id: '2', + body: 'This is a comment!', + status: 'ACCEPTED' + } + ]); + + // Merge all the comments from asset 1 into asset 2, followed by deleting + // asset 1. + await AssetsService.merge('1', '2'); + + // Check to see if the comments are moved. + expect(await CommentModel.find({asset_id: '1'}).count()).to.equal(0); + expect(await CommentModel.find({asset_id: '2'}).count()).to.equal(4); + + // Check to see if the asset was removed. + expect(await AssetModel.findOne({id: '1'})).to.equal(null); + }); + + }); }); From cc4c943a21c7c6950b194c891b7b9ad347dfdbe5 Mon Sep 17 00:00:00 2001 From: IAmSamHankins Date: Fri, 9 Jun 2017 17:27:35 -0400 Subject: [PATCH 2/6] hover states, link styling, alignment fixes --- .../coral-admin/src/components/ui/Header.css | 2 +- .../routes/Dashboard/components/Widget.css | 22 +++++----- .../Moderation/components/CommentCount.css | 1 + .../Moderation/components/UserDetail.css | 4 +- .../routes/Moderation/components/styles.css | 44 ++++++++++++------- locales/en.yml | 4 +- 6 files changed, 45 insertions(+), 32 deletions(-) diff --git a/client/coral-admin/src/components/ui/Header.css b/client/coral-admin/src/components/ui/Header.css index 7cb68ecbf..d99a7f295 100644 --- a/client/coral-admin/src/components/ui/Header.css +++ b/client/coral-admin/src/components/ui/Header.css @@ -77,7 +77,7 @@ letter-spacing: .8; &:hover { - background-color: #232323; + background-color: #404040; } &.active { diff --git a/client/coral-admin/src/routes/Dashboard/components/Widget.css b/client/coral-admin/src/routes/Dashboard/components/Widget.css index 587c51ada..cf83fcbdc 100644 --- a/client/coral-admin/src/routes/Dashboard/components/Widget.css +++ b/client/coral-admin/src/routes/Dashboard/components/Widget.css @@ -17,8 +17,9 @@ .heading { margin: 0; padding-left: 10px; - font-size: 1.5rem; - font-weight: bold; + font-size: 1.3rem; + font-weight: 600; + color: #2c2c2c; } .widgetTable { @@ -32,7 +33,8 @@ } .widgetHead p { - color: rgb(35, 102, 223); + color: #2c2c2c; + font-weight: 500; padding: 10px; text-align: left; text-transform: capitalize; @@ -47,11 +49,11 @@ } .rowLinkify { - cursor: pointer; border-bottom: 1px solid lightgrey; color: #555; height: var(--row-height); padding: 10px; + transition: background-color 200ms; } .rowLinkify:last-child { @@ -60,6 +62,7 @@ .rowLinkify:hover { background-color: #f8f8f8; + pointer: default; } .linkToAsset { @@ -68,16 +71,17 @@ } .linkToModerate { - background-color: #e0e0e0; + background-color: #BDBDBD; padding: 10px 14px; text-decoration: none; color: black; float: right; margin-left: 15px; + transition: background-color 200ms; } .linkToModerate:hover { - background-color: #ccc; + background-color: #9E9E9E; } .lede { @@ -89,14 +93,10 @@ color: #555; text-decoration: none; font-size: 1.2em; - font-weight: normal; + font-weight: 500; margin: 0; } -.assetTitle:hover { - text-decoration: underline; -} - .widgetCount { color: #555; font-size: 1.3em; diff --git a/client/coral-admin/src/routes/Moderation/components/CommentCount.css b/client/coral-admin/src/routes/Moderation/components/CommentCount.css index b684d0f4a..4d24d5702 100644 --- a/client/coral-admin/src/routes/Moderation/components/CommentCount.css +++ b/client/coral-admin/src/routes/Moderation/components/CommentCount.css @@ -12,4 +12,5 @@ right: 0; margin-top: -2px; font-size: 12px; + color: white; } diff --git a/client/coral-admin/src/routes/Moderation/components/UserDetail.css b/client/coral-admin/src/routes/Moderation/components/UserDetail.css index d4b5f0db7..710f84759 100644 --- a/client/coral-admin/src/routes/Moderation/components/UserDetail.css +++ b/client/coral-admin/src/routes/Moderation/components/UserDetail.css @@ -15,7 +15,7 @@ display: flex; .stat { - margin: 0 4px 12px; + margin: 0 4px 10px 0px; } .stat:last-child { @@ -49,7 +49,7 @@ li { display: inline-block; - margin: 0 10px; + margin-right: 10px; cursor: pointer; padding: 0 10px; } diff --git a/client/coral-admin/src/routes/Moderation/components/styles.css b/client/coral-admin/src/routes/Moderation/components/styles.css index d10311f74..fc825eccb 100644 --- a/client/coral-admin/src/routes/Moderation/components/styles.css +++ b/client/coral-admin/src/routes/Moderation/components/styles.css @@ -18,14 +18,20 @@ .tab { flex: 1; - color: #EEEEEE; + color: #C0C0C0; text-transform: capitalize; font-weight: 100; - font-size: 15px; + font-size: 14px; letter-spacing: 1px; transition: border-bottom 200ms; - padding: 0px 5px; - margin-right: 30px; + transition: color 200ms; + padding: 0px 10px; + margin-right: 20px; + &:hover { + color: white; + border-bottom: solid 2px #F36451; + box-sizing: border-box; + } } .active { @@ -33,6 +39,10 @@ box-sizing: border-box; border-bottom: solid 4px #F36451; font-weight: 400; + &:hover { + border-bottom: solid 4px #F36451; + font-weight: 400; + } } .active > span { @@ -103,19 +113,18 @@ span { font-weight: 400; font-size: 15px; letter-spacing: 1px; - transition: opacity 200ms; + transition: background-color 200ms; opacity: 1; - &:hover { - opacity: .8; - cursor: pointer; - } - &:first-child { text-align: left; } &:nth-child(2) { + &:hover { + cursor: pointer; + background-color: #212121; + } span { text-align: center; text-overflow: ellipsis; @@ -245,7 +254,6 @@ span { padding: 5px; color: #262626; font-size: 14px; - margin-left: 15px; line-height: 1px; font-weight: 300; } @@ -421,17 +429,21 @@ span { .tabIcon { position: relative; - top: 2px; + top: 3px; font-size: 16px; } .username { - color: blue; - text-decoration: underline; + color: #393B44; + text-decoration: none; cursor: pointer; - + font-weight: 600; + padding: 2px 5px; + border-radius: 2px; + margin-left: -5px; + transition: background-color 200ms ease; &:hover { - background-color: rgba(255, 0, 0, .1); + background-color: #E0E0E0; } } diff --git a/locales/en.yml b/locales/en.yml index ce9abdce5..930a4eadb 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -144,8 +144,8 @@ en: auto_update: "Data automatically updates every five minutes or when you Reload." comment_count: comments flags: Flags - most_flags: "Articles with the most flags" - most_conversations: "Articles with the most conversations" + most_flags: "Stories with the most flags" + most_conversations: "Stories with the most conversations" next_update: "{0} minutes until next update." no_activity: "There haven't been any comments anywhere in the last five minutes." no_flags: "There have been no flags in the last 5 minutes! Hooray!" From aed265efcc2fbc8e69783270b6e8ef6f13667601 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 13 Jun 2017 22:01:58 +0700 Subject: [PATCH 3/6] Use correct fragment name --- client/coral-framework/graphql/fragments.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/coral-framework/graphql/fragments.js b/client/coral-framework/graphql/fragments.js index 3c64090bd..110f0796b 100644 --- a/client/coral-framework/graphql/fragments.js +++ b/client/coral-framework/graphql/fragments.js @@ -7,7 +7,7 @@ export default { 'SuspendUserResponse', 'RejectUsernameResponse', 'SetUserStatusResponse', - 'PostCommentResponse', + 'CreateFlagResponse', 'EditCommentResponse', 'PostFlagResponse', 'CreateDontAgreeResponse', @@ -17,3 +17,4 @@ export default { 'StopIgnoringUserResponse', ) }; + From 7d8f2aa876c1e9c3154c2faa4fbf3f511b78dfde Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 13 Jun 2017 22:02:54 +0700 Subject: [PATCH 4/6] More robust reactions --- errors.js | 15 +++++++ graph/mutators/action.js | 2 +- locales/en.yml | 1 + plugin-api/beta/client/hocs/withReaction.js | 50 ++++++++++++++++----- plugin-api/beta/server/getReactionConfig.js | 15 ++++++- services/actions.js | 37 ++++++++++----- 6 files changed, 98 insertions(+), 22 deletions(-) diff --git a/errors.js b/errors.js index c203b1d25..5d2593d66 100644 --- a/errors.js +++ b/errors.js @@ -104,6 +104,20 @@ class ErrAuthentication extends APIError { } } +/** + * ErrAlreadyExists is returned when an attempt to create a resource failed due to an existing one. + */ +class ErrAlreadyExists extends APIError { + constructor(existing = null) { + super('authentication error occured', { + translation_key: 'ALREADY_EXISTS', + status: 400 + }, { + existing + }); + } +} + // ErrContainsProfanity is returned in the event that the middleware detects // profanity/wordlisted words in the payload. const ErrContainsProfanity = new APIError('This username contains elements which are not permitted in our community. If you think this is in error, please contact us or try again.', { @@ -171,6 +185,7 @@ const ErrCommentTooShort = new APIError('Comment was too short', { module.exports = { ExtendableError, APIError, + ErrAlreadyExists, ErrPasswordTooShort, ErrSettingsNotInit, ErrMissingEmail, diff --git a/graph/mutators/action.js b/graph/mutators/action.js index de9efea1a..96bf091cf 100644 --- a/graph/mutators/action.js +++ b/graph/mutators/action.js @@ -36,7 +36,7 @@ const createAction = async ({user = {}}, {item_id, item_type, action_type, group * Deletes an action based on the user id if the user owns that action. * @param {Object} user the user performing the request * @param {String} id the id of the action to delete - * @return {Promise} resolves when the action is deleted + * @return {Promise} resolves to the deleted action, or null if not found. */ const deleteAction = ({user}, {id}) => { return ActionModel.findOneAndRemove({ diff --git a/locales/en.yml b/locales/en.yml index ce9abdce5..4f287e36b 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -189,6 +189,7 @@ en: PASSWORD_REQUIRED: "Must input a password" COMMENTING_CLOSED: "Commenting is already closed" NOT_FOUND: "Resource not found" + ALREADY_EXISTS: "Resource already exists" INVALID_ASSET_URL: "Assert URL is invalid" email: "Not a valid E-Mail" confirm_password: "Passwords don't match. Please check again" diff --git a/plugin-api/beta/client/hocs/withReaction.js b/plugin-api/beta/client/hocs/withReaction.js index 42ac79ec5..1ed777996 100644 --- a/plugin-api/beta/client/hocs/withReaction.js +++ b/plugin-api/beta/client/hocs/withReaction.js @@ -149,6 +149,9 @@ export default (reaction) => (WrappedComponent) => { client: PropTypes.object.isRequired, }; + // Whether or not a mutation is currently active. + duringMutation = false; + constructor(props, context) { super(props, context); @@ -208,6 +211,26 @@ export default (reaction) => (WrappedComponent) => { } } + postReaction = () => { + if (this.duringMutation) { + return; + } + this.duringMutation = true; + return this.props.postReaction(this.props.comment) + .then((result) => {this.duringMutation = false; return Promise.resolve(result); }) + .catch((err) => {this.duringMutation = false; throw err; }); + } + + deleteReaction = () => { + if (this.duringMutation) { + return; + } + this.duringMutation = true; + return this.props.deleteReaction(this.props.comment) + .then((result) => {this.duringMutation = false; return Promise.resolve(result); }) + .catch((err) => {this.duringMutation = false; throw err; }); + } + render() { const {comment} = this.props; @@ -223,9 +246,16 @@ export default (reaction) => (WrappedComponent) => { const alreadyReacted = !!reactionSummary; - const withReactionProps = {reactionSummary, count, alreadyReacted}; - - return ; + return ; } } @@ -240,16 +270,16 @@ export default (reaction) => (WrappedComponent) => { } `, { - props: ({mutate, ownProps}) => ({ - deleteReaction: () => { + props: ({mutate}) => ({ + deleteReaction: (comment) => { const reactionSummary = getMyActionSummary( `${Reaction}ActionSummary`, - ownProps.comment + comment ); const id = reactionSummary.current_user.id; - const item_id = ownProps.comment.id; + const item_id = comment.id; const input = {id}; return mutate({ @@ -283,11 +313,11 @@ export default (reaction) => (WrappedComponent) => { } `, { - props: ({mutate, ownProps}) => ({ - postReaction: () => { + props: ({mutate}) => ({ + postReaction: (comment) => { const input = { - item_id: ownProps.comment.id, + item_id: comment.id, }; return mutate({ diff --git a/plugin-api/beta/server/getReactionConfig.js b/plugin-api/beta/server/getReactionConfig.js index 83c45dcb5..78bc45dfd 100644 --- a/plugin-api/beta/server/getReactionConfig.js +++ b/plugin-api/beta/server/getReactionConfig.js @@ -1,5 +1,6 @@ const wrapResponse = require('../../../graph/helpers/response'); const {SEARCH_OTHER_USERS} = require('../../../perms/constants'); +const errors = require('../../../errors'); function getReactionConfig(reaction) { reaction = reaction.toLowerCase(); @@ -134,13 +135,24 @@ function getReactionConfig(reaction) { // The comment is needed to allow better filtering e.g. by asset_id. pubsub.publish(`${reaction}ActionCreated`, {action, comment}); return Promise.resolve(action); - }); + }) + .catch((err) => { + if (err instanceof errors.ErrAlreadyExists) { + return Promise.resolve(err.metadata.existing); + } + throw err; + }); }); return wrapResponse(reaction)(response); }, [`delete${Reaction}Action`]: (_, {input: {id}}, {mutators: {Action}, pubsub, loaders: {Comments}}) => { const response = Action.delete({id}) .then((action) => { + + // Action doesn't exist or was already deleted. + if (!action) { + return Promise.resolve(null); + } return Comments.get.load(action.item_id).then((comment) => { // The comment is needed to allow better filtering e.g. by asset_id. @@ -148,6 +160,7 @@ function getReactionConfig(reaction) { return Promise.resolve(action); }); }); + return wrapResponse(reaction)(response); } }, diff --git a/services/actions.js b/services/actions.js index 40bef65bb..c59b3a3f9 100644 --- a/services/actions.js +++ b/services/actions.js @@ -1,5 +1,6 @@ const ActionModel = require('../models/action'); const _ = require('lodash'); +const errors = require('../errors'); module.exports = class ActionsService { @@ -12,10 +13,10 @@ module.exports = class ActionsService { } /** - * Add an action. - * @param {String} item_id identifier of the comment (uuid) + * Inserts an action. + * @param {String} item_id identifier of the item (uuid) * @param {String} user_id user id of the action (uuid) - * @param {String} action the new action to the comment + * @param {String} action the new action to the item * @return {Promise} */ static insertUserAction(action) { @@ -31,16 +32,32 @@ module.exports = class ActionsService { }; // Create/Update the action. - return ActionModel.findOneAndUpdate(query, action, { + return new Promise((resolve, reject) => { + ActionModel.findOneAndUpdate( + query, { - // Ensure that if it's new, we return the new object created. - new: true, + // Only set when not existing. + $setOnInsert: action, + }, { - // Perform an upsert in the event that this doesn't exist. - upsert: true, + // Ensure that if it's new, we return the new object created. + new: true, - // Set the default values if not provided based on the mongoose models. - setDefaultsOnInsert: true + // Use raw result to get `updatedExisting`. + passRawResult: true, + + // Perform an upsert in the event that this doesn't exist. + upsert: true, + + // Set the default values if not provided based on the mongoose models. + setDefaultsOnInsert: true + }, (err, doc, raw) => { + if (err) { return reject(err); } + if (raw.lastErrorObject.updatedExisting) { + return reject(new errors.ErrAlreadyExists(raw.value)); + } + return resolve(raw.value); + }); }); } From 99745723fb670ea4ba1cb8111e527986533dee09 Mon Sep 17 00:00:00 2001 From: IAmSamHankins Date: Tue, 13 Jun 2017 12:40:07 -0400 Subject: [PATCH 5/6] style updates to flagged user names queue and updated translations --- .../routes/Community/components/Community.css | 19 ++++++++++++++----- .../routes/Community/components/styles.css | 8 +++++++- locales/en.yml | 8 ++++---- locales/es.yml | 6 +++--- 4 files changed, 28 insertions(+), 13 deletions(-) diff --git a/client/coral-admin/src/routes/Community/components/Community.css b/client/coral-admin/src/routes/Community/components/Community.css index 28551c8a0..e65c34514 100644 --- a/client/coral-admin/src/routes/Community/components/Community.css +++ b/client/coral-admin/src/routes/Community/components/Community.css @@ -175,7 +175,7 @@ right: 0; height: 100%; top: 0; - padding: 40px 18px; + padding: 50px 18px; box-sizing: border-box; } @@ -310,24 +310,33 @@ .flaggedByCount { display: block; text-align: left; + margin-top: 5px; +} + +.flaggedByCount i { + font-size: 14px; + margin-right: 10px; } .flaggedBy { display: inline; padding: 3px; - font-size: 16px; + font-size: 14px; + margin-left: 5px; } .flaggedByLabel { - font-weight: bold; + font-weight: 600; font-size: 14px; } .flaggedReasons { - padding-top: 15px; margin-left: 24px; + margin-top: 10px; } -.flaggedByReason { +p.flaggedByReason { font-size: 1tpx; + margin: 0px; + line-height: 1.4; } diff --git a/client/coral-admin/src/routes/Community/components/styles.css b/client/coral-admin/src/routes/Community/components/styles.css index 2cd3f7b50..8e4b716af 100644 --- a/client/coral-admin/src/routes/Community/components/styles.css +++ b/client/coral-admin/src/routes/Community/components/styles.css @@ -52,6 +52,10 @@ span { } } +.approve { + margin-top: 10px; +} + .notFound { position: relative; margin: 20px auto; @@ -193,7 +197,7 @@ span { top: 0; padding: 40px 18px; box-sizing: border-box; - } + } .itemHeader { display: flex; @@ -255,6 +259,8 @@ span { } + + .empty { color: #444; margin-top: 50px; diff --git a/locales/en.yml b/locales/en.yml index ce9abdce5..113737642 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -42,17 +42,17 @@ en: banned: Banned banned_user: "Banned User" cancel: Cancel - dont_like_username: "I don't like this username" + dont_like_username: "Dislike username" flaggedaccounts: "Flagged Usernames" flags: Flags - impersonating: "This user is impersonating" + impersonating: "Impersonation" loading: "Loading results" moderator: Moderator newsroom_role: "Newsroom Role" - no_flagged_accounts: "The Account Flags queue is currently empty." + no_flagged_accounts: "The Flagged Usernames queue is currently empty." no_results: "No users found with that user name or email address. They're hiding!" note: "Note: Banning this user will not let them edit comment or remove anything." - offensive: "This comment is offensive" + offensive: "Offensive" other: Other people: People role: "Select role..." diff --git a/locales/es.yml b/locales/es.yml index 1b483b70c..6897f6f8b 100644 --- a/locales/es.yml +++ b/locales/es.yml @@ -44,14 +44,14 @@ es: dont_like_username: "No me gusta este nombre de usuario" flaggedaccounts: "Nombres de Usuario Reportados" flags: Reportes - impersonating: "El usuario esta suplantando identidad" + impersonating: Impersonando loading: "Cargando resultados" moderator: Moderator newsroom_role: "Rol en la redacción" - no_flagged_accounts: "No hay ninguna cuenta reportada en este momento." + no_flagged_accounts: "No hay ningún nombre de usario reportado en este momento." no_results: "No se encontraron usuarios con ese nombre o correo." note: "Nota: Suspender a este usuario no le va a permitir (al usuario) borrar ni editar ni comentar." - offensive: "Este comentario es ofensivo" + offensive: Ofensivo other: Otro people: Gente role: "Seleccionar rol..." From cdfa8379ffe9b2fbb638656da9a861096ce6a973 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Wed, 14 Jun 2017 00:38:34 +0700 Subject: [PATCH 6/6] Change text and status of ErrAlreadyExists --- errors.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/errors.js b/errors.js index 5d2593d66..9b26a9359 100644 --- a/errors.js +++ b/errors.js @@ -109,9 +109,9 @@ class ErrAuthentication extends APIError { */ class ErrAlreadyExists extends APIError { constructor(existing = null) { - super('authentication error occured', { + super('resource already exists', { translation_key: 'ALREADY_EXISTS', - status: 400 + status: 409 }, { existing });