Adjusted query/tag flow

This commit is contained in:
Wyatt Johnson
2017-05-09 15:01:01 -06:00
parent b8e530ca1f
commit 2e1bc8d75a
25 changed files with 476 additions and 343 deletions
+140 -15
View File
@@ -18,18 +18,25 @@ const Wordlist = require('../../services/wordlist');
*/
const createComment = ({user, loaders: {Comments}, pubsub}, {body, asset_id, parent_id = null}, status = 'NONE') => {
// Add the staff tag for comments created as a staff member.
let tags = [];
if (user.hasRoles('ADMIN') || user.hasRoles('MODERATOR')) {
tags = [{
tag: {
name: 'STAFF'
}
}];
}
return CommentsService.publicCreate({
body,
asset_id,
parent_id,
status,
tags,
author_id: user.id
})
.then(async (comment) => {
if (user.hasRoles('ADMIN') || user.hasRoles('MODERATOR')) {
await CommentsService.addTag(comment.id, 'STAFF', user.id);
}
.then((comment) => {
// If the loaders are present, clear the caches for these values because we
// just added a new comment, hence the counts should be updated. We should
@@ -204,21 +211,139 @@ const setCommentStatus = ({user, loaders: {Comments}}, {id, status}) => {
};
/**
* Adds a tag to a Comment
* @param {String} id identifier of the comment (uuid)
* @param {String} tag name of the tag
* Adds a tag to a Comment.
*/
const addCommentTag = ({user, loaders: {Comments}}, {id, tag}) => {
return CommentsService.addTag(id, tag, user.id);
const addCommentTag = async ({user, loaders: {Assets, Comments, Settings}}, {id, asset_id, name}) => {
// If the current user does not have the ADMIN or MODERATOR role
if (!user || !user.can('mutation:addCommentTag')) {
throw errors.ErrNotAuthorized;
}
// Load the settings from the dataloader, it will be cached if it was
// retrieved already.
let settings = await AssetsService.rectifySettings(AssetsService.findById(asset_id));
// Try to find the tag in the asset tags. This will contain the permission
// information.
let tag = settings.tags.find((globalTag) => {
return globalTag.name === name && globalTag.models.include('COMMENTS');
});
// Create the new tagLink that will be created to attach to the comment.
let tagLink = {
tag,
assigned_by: user.id,
created_at: new Date()
};
// If the tag was found, we need to ensure that the current user can indeed
// set this tag on the comment.
if (tag) {
// If the tag has roles defined, and the current user has at least one of
// the required roles, then add the tag without checking for ownership.
if (tag.permissions && tag.permissions.roles && tag.permissions.roles.some((role) => user.roles.include(role))) {
return CommentsService.addTag(id, tagLink, false);
}
// If the permissions allow for self assignment, then ensure that the query
// is compose with that in mind.
if (tag.permissions && tag.permissions.self) {
// Otherwise, we assume that we have to check to see that the user indeed
// owns the resource before allowing the tag to get added.
return CommentsService.addTag(id, tagLink, true);
}
throw errors.ErrNotAuthorized;
}
// Only admin/moderators can add unique tags, these are tags that are not in
// the global list.
if (!(user.hasRoles('ADMIN') || user.hasRoles('MODERATOR'))) {
throw errors.ErrNotAuthorized;
}
// Generate the tag in the event now that we have to create the tag for this
// specific comment.
tagLink.tag = {
name,
permissions: {
public: true,
self: false,
roles: []
},
models: ['COMMENTS'],
created_at: new Date()
};
// Actually attach the new tag that we created to the comment.
return CommentsService.addTag(id, tagLink, false);
};
/**
* Removes a tag from a Comment
* @param {String} id identifier of the comment (uuid)
* @param {String} tag name of the tag
* Removes a tag from a Comment.
*/
const removeCommentTag = ({user, loaders: {Comments}}, {id, tag}) => {
return CommentsService.removeTag(id, tag);
const removeCommentTag = async ({user, loaders: {Comments}}, {id, asset_id, name}) => {
// If the current user does not have the ADMIN or MODERATOR role
if (!user || !user.can('mutation:removeCommentTag')) {
throw errors.ErrNotAuthorized;
}
// Load the settings from the dataloader, it will be cached if it was
// retrieved already.
let settings = await AssetsService.rectifySettings(AssetsService.findById(asset_id));
// Try to find the tag in the asset tags. This will contain the permission
// information.
let tag = settings.tags.find((globalTag) => {
return globalTag.name === name && globalTag.models.include('COMMENTS');
});
// Create the new tagLink that will be created to attach to the comment.
let tagLink = {
tag,
assigned_by: user.id
};
// If the tag was found, we need to ensure that the current user can indeed
// remove this tag on the comment.
if (tag) {
// If the tag has roles defined, and the current user has at least one of
// the required roles, then remove the tag without checking for ownership.
if (tag.permissions && tag.permissions.roles && tag.permissions.roles.some((role) => user.roles.include(role))) {
return CommentsService.removeTag(id, tagLink, false);
}
// If the permissions allow for self assignment, then ensure that the query
// is compose with that in mind.
if (tag.permissions && tag.permissions.self) {
// Otherwise, we assume that we have to check to see that the user indeed
// owns the resource before allowing the tag to get removed.
return CommentsService.removeTag(id, tagLink, true);
}
throw errors.ErrNotAuthorized;
}
// Only admin/moderators can remove unique tags, these are tags that are not
// in the global list.
if (!(user.hasRoles('ADMIN') || user.hasRoles('MODERATOR'))) {
throw errors.ErrNotAuthorized;
}
// Generate the tag in the event now that we have to create the tag for this
// specific comment.
tagLink.tag = {
name
};
// Actually attach the new tag that we created to the comment.
return CommentsService.removeTag(id, tagLink, false);
};
module.exports = (context) => {
+4
View File
@@ -16,6 +16,8 @@ const RootMutation = require('./root_mutation');
const RootQuery = require('./root_query');
const Settings = require('./settings');
const Subscription = require('./subscription');
const TagLink = require('./tag_link');
const Tag = require('./tag');
const UserError = require('./user_error');
const User = require('./user');
const ValidationUserError = require('./validation_user_error');
@@ -39,6 +41,8 @@ let resolvers = {
RootQuery,
Settings,
Subscription,
TagLink,
Tag,
UserError,
User,
ValidationUserError,
+5 -6
View File
@@ -1,5 +1,4 @@
const wrapResponse = require('../helpers/response');
const CommentsService = require('../../services/comments');
const RootMutation = {
createComment(_, {comment}, {mutators: {Comment}}) {
@@ -29,12 +28,12 @@ const RootMutation = {
setCommentStatus(_, {id, status}, {mutators: {Comment}}) {
return wrapResponse(null)(Comment.setCommentStatus({id, status}));
},
addCommentTag(_, {id, tag}, {mutators: {Comment}}) {
return wrapResponse('comment')(Comment.addCommentTag({id, tag}).then(() => CommentsService.findById(id)));
},
removeCommentTag(_, {id, tag}, {mutators: {Comment}}) {
return wrapResponse('comment')(Comment.removeCommentTag({id, tag}).then(() => CommentsService.findById(id)));
addCommentTag(_, {id, asset_id, name}, {mutators: {Comment}}) {
return wrapResponse('comment')(Comment.addCommentTag({id, asset_id, name}));
},
removeCommentTag(_, {id, asset_id, name}, {mutators: {Comment}}) {
return wrapResponse('comment')(Comment.removeCommentTag({id, asset_id, name}));
}
};
module.exports = RootMutation;
+5
View File
@@ -0,0 +1,5 @@
const Tag = {
};
module.exports = Tag;
+9
View File
@@ -0,0 +1,9 @@
const TagLink = {
assigned_by({assigned_by}, _, {user, loaders: {Users}}) {
if (user && user.hasRole('ADMIN')) {
return Users.getByID.load(assigned_by);
}
}
};
module.exports = TagLink;
+26 -10
View File
@@ -52,14 +52,30 @@ type User {
status: USER_STATUS
}
# Tag represents the underlying Tag that can be either stored in a global list
# or added uniquely to the entity.
type Tag {
# the actual tag for the comment.
id: String!
# the user that assigned the tag. If NULL then the system automatically tagged it.
added_by: String
# The actual name of the tag entry.
name: String!
# the time when the tag was assigned.
# The time that this Tag was created.
created_at: Date!
}
# TagLink is used to associate a given Tag with a Model via a TagLink.
type TagLink {
# The underlying Tag that is either duplicated from the global list or created
# uniquely for this specific model.
tag: Tag!
# The user that assigned the tag. This TagLink could have been created by the
# system, in which case this will be null. It could also be null if the
# current user is not an Admin/Moderator.
assigned_by: User
# The date that the TagLink was created.
created_at: Date!
}
@@ -176,7 +192,7 @@ type Comment {
body: String!
# the tags on the comment
tags: [Tag]
tags: [TagLink!]
# the user who authored the comment.
user: User
@@ -702,15 +718,15 @@ type SetCommentStatusResponse implements Response {
# Response to addCommentTag mutation
type AddCommentTagResponse implements Response {
# An array of errors relating to the mutation that occured.
comment: Comment
errors: [UserError]
}
# Response to removeCommentTag mutation
type RemoveCommentTagResponse implements Response {
# An array of errors relating to the mutation that occured.
comment: Comment
errors: [UserError]
}
@@ -751,10 +767,10 @@ type RootMutation {
setCommentStatus(id: ID!, status: COMMENT_STATUS!): SetCommentStatusResponse
# Add tag to comment.
addCommentTag(id: ID!, tag: String!): AddCommentTagResponse
addCommentTag(id: ID!, asset_id: ID!, name: String!): AddCommentTagResponse
# Remove tag from comment.
removeCommentTag(id: ID!, tag: String!): RemoveCommentTagResponse
removeCommentTag(id: ID!, asset_id: ID!, name: String!): RemoveCommentTagResponse
# Ignore comments by another user
ignoreUser(id: ID!): IgnoreUserResponse
+2 -11
View File
@@ -1,17 +1,8 @@
const mongoose = require('../services/mongoose');
const uuid = require('uuid');
const Schema = mongoose.Schema;
const ACTION_TYPES = [
'LIKE',
'FLAG'
];
const ITEM_TYPES = [
'ASSETS',
'COMMENTS',
'USERS'
];
const ACTION_TYPES = require('./enum/action_types');
const ITEM_TYPES = require('./enum/item_types');
const ActionSchema = new Schema({
id: {
+7 -37
View File
@@ -1,13 +1,8 @@
const mongoose = require('../services/mongoose');
const Schema = mongoose.Schema;
const TagLinkSchema = require('./schema/tag_link');
const uuid = require('uuid');
const STATUSES = [
'ACCEPTED',
'REJECTED',
'PREMOD',
'NONE'
];
const COMMENT_STATUS = require('./enum/comment_status');
/**
* The Mongo schema for a Comment Status.
@@ -16,7 +11,7 @@ const STATUSES = [
const StatusSchema = new Schema({
type: {
type: String,
enum: STATUSES,
enum: COMMENT_STATUS,
},
// The User ID of the user that assigned the status.
@@ -30,29 +25,6 @@ const StatusSchema = new Schema({
_id: false
});
/**
* The Mongo schema for a Comment Tag.
* @type {Schema}
*/
const TagSchema = new Schema({
// This is the actual 'tag' and we only permit tags that are in Setting.tags.
id: String,
// The User ID of the user that added the tag.
added_by: {
type: String,
default: null
},
created_at: {
type: Date,
default: Date
}
}, {
_id: false
});
/**
* The Mongo schema for a Comment.
* @type {Schema}
@@ -73,12 +45,11 @@ const CommentSchema = new Schema({
status_history: [StatusSchema],
status: {
type: String,
enum: STATUSES,
enum: COMMENT_STATUS,
default: 'NONE'
},
parent_id: String,
tags: [TagSchema],
tags: [TagLinkSchema],
// Additional metadata stored on the field.
metadata: {
@@ -92,10 +63,9 @@ const CommentSchema = new Schema({
}
});
// Add the indexes on the comment tag.
// Add the indexes for the id of the comment.
CommentSchema.index({
'id': 1,
'tags.id': 1
'id': 1
}, {
unique: true,
background: false
+4
View File
@@ -0,0 +1,4 @@
module.exports = [
'LIKE',
'FLAG'
];
+6
View File
@@ -0,0 +1,6 @@
module.exports = [
'ACCEPTED',
'REJECTED',
'PREMOD',
'NONE'
];
+5
View File
@@ -0,0 +1,5 @@
module.exports = [
'ASSETS',
'COMMENTS',
'USERS'
];
+4
View File
@@ -0,0 +1,4 @@
module.exports = [
'PRE',
'POST'
];
+4
View File
@@ -0,0 +1,4 @@
module.exports = [
'ADMIN',
'MODERATOR'
];
+6
View File
@@ -0,0 +1,6 @@
module.exports = [
'ACTIVE',
'BANNED',
'PENDING',
'APPROVED' // Indicates that the users' username has been approved
];
+53
View File
@@ -0,0 +1,53 @@
const mongoose = require('../../services/mongoose');
const Schema = mongoose.Schema;
const ITEM_TYPES = require('../enum/item_types');
const USER_ROLES = require('../enum/user_roles');
/**
* The Mongo schema for a Tag.
* @type {Schema}
*/
const TagSchema = new Schema({
// The actual name of the tag.
name: String,
// Contains permission data.
permissions: {
// Determines if this tag is public or not.
public: {
type: Boolean,
default: true
},
// Determines if the owner of the Model can add/remove this tag on their own
// resources.
self: Boolean,
// Determines other roles that are allowed to set this tag on other
// resources.
roles: [{
type: String,
enum: USER_ROLES,
default: []
}]
},
// A list of all the model types that this tag can be added to.
models: [{
type: String,
enum: ITEM_TYPES
}],
// The date for when the tag was created.
created_at: {
type: Date,
default: Date
}
}, {
_id: false
});
module.exports = TagSchema;
+31
View File
@@ -0,0 +1,31 @@
const mongoose = require('../../services/mongoose');
const Schema = mongoose.Schema;
const TagSchema = require('./tag');
/**
* The Mongo schema for linking a Tag to a Model.
* @type {Schema}
*/
const TagLinkSchema = new Schema({
// A deep copy of the tag that is the origin for this link. If the ID matches
// with existing tags in the global/asset context then content will be
// substituted.
tag: TagSchema,
// The User ID of the user that assigned the status.
assigned_by: {
type: String,
default: null
},
// The date when the tag was added to the model.
created_at: {
type: Date,
default: Date
}
}, {
_id: false
});
module.exports = TagLinkSchema;
+2 -41
View File
@@ -1,45 +1,7 @@
const mongoose = require('../services/mongoose');
const Schema = mongoose.Schema;
const PERMISSIONS = [
'ADMIN'
];
/**
* The Mongo schema for a Tag.
* @type {Schema}
*/
const TagSchema = new Schema({
id: {
type: String,
unique: true,
default: 'STAFF'
},
public: Boolean,
text: {
type: Schema.Types.Mixed,
default: null
},
permissions: [{
type: String,
enum: PERMISSIONS,
default: 'ADMIN'
}],
models: [String],
// Additional metadata stored on the field.
metadata: Schema.Types.Mixed
}, {
timestamps: {
createdAt: 'created_at',
updatedAt: 'updated_at'
}
});
const MODERATION_OPTIONS = [
'PRE',
'POST'
];
const TagSchema = require('./schema/tag');
const MODERATION_OPTIONS = require('./enum/moderation_options');
/**
* SettingSchema manages application settings that get used on front and backend.
@@ -156,4 +118,3 @@ SettingSchema.method('merge', function(src) {
const Setting = mongoose.model('Setting', SettingSchema);
module.exports = Setting;
module.exports.MODERATION_OPTIONS = MODERATION_OPTIONS;
+2 -41
View File
@@ -4,41 +4,10 @@ const Schema = mongoose.Schema;
const uuid = require('uuid');
// USER_ROLES is the array of roles that is permissible as a user role.
const USER_ROLES = [
'ADMIN',
'MODERATOR'
];
const USER_ROLES = require('./enum/user_roles');
// USER_STATUS is the list of statuses that are permitted for the user status.
const USER_STATUS = [
'ACTIVE',
'BANNED',
'PENDING',
'APPROVED' // Indicates that the users' username has been approved
];
// /**
// * The Mongo schema for a User Tag.
// * @type {Schema}
// */
// const TagSchema = new Schema({
//
// // This is the actual 'tag' and we only permit tags that are in Setting.tags.
// id: String,
//
// // The User ID of the user that added the tag.
// added_by: {
// type: String,
// default: null
// },
//
// created_at: {
// type: Date,
// default: Date
// }
// }, {
// _id: false
// });
const USER_STATUS = require('./enum/user_status');
// ProfileSchema is the mongoose schema defined as the representation of a
// User's profile stored in MongoDB.
@@ -245,12 +214,6 @@ UserSchema.method('can', function(...actions) {
return false;
}
// {add,remove}CommentTag - requires admin and/or moderator role
const userCanModifyTags = user => ['ADMIN', 'MODERATOR'].some(r => user.hasRoles(r));
if (actions.some(a => ['mutation:removeCommentTag', 'mutation:addCommentTag', 'mutation:removeUserTag', 'mutation: addUserTag'].includes(a)) && ! userCanModifyTags(this)) {
return false;
}
return true;
});
@@ -258,5 +221,3 @@ UserSchema.method('can', function(...actions) {
const UserModel = mongoose.model('User', UserSchema);
module.exports = UserModel;
module.exports.USER_ROLES = USER_ROLES;
module.exports.USER_STATUS = USER_STATUS;
+38 -84
View File
@@ -1,23 +1,7 @@
const CommentModel = require('../models/comment');
const ActionModel = require('../models/action');
const ActionsService = require('./actions');
const SettingModel = require('../models/setting');
const SettingsService = require('./settings');
const UsersService = require('./users');
const errors = require('../errors');
const STATUSES = [
'ACCEPTED',
'REJECTED',
'PREMOD',
'NONE',
];
const ALLOWED_TAGS = [
'STAFF'
];
const COMMENT_STATUS = require('../models/enum/comment_status');
module.exports = class CommentsService {
@@ -47,82 +31,52 @@ module.exports = class CommentsService {
return commentModel.save();
}
/**
* Adds a tag if it doesn't already exist on the comment.
* @throws if tag is already added to the comment
* @throws if tag name is not in ALLOWED_TAGS
* @param {String} id the id of the comment to tag
* @param {String} name the name of the tag to add
* @param {String} added_by the user id for the user who added the tag
*/
static addTag(id, name, added_by) {
static addTag(id, tagLink, verifyOwnership = false) {
// Check that the tag is allowed by the system OR in our setting.tags.
return SettingsService.retrieve()
.then((settings) => {
// Compose the query to find the comment.
const query = {
id,
'tags.tag.name': {
$ne: tagLink.tag.name
}
};
UsersService.findById(added_by)
.then((user) => {
// If ownership verification is required, ensure that the person that is
// assigning the tag is the same person that owns the comment.
if (verifyOwnership) {
query['author_id'] = tagLink.assigned_by;
}
// Moderators or ADMIN can add any tag automatically.
if (user != null && (user.hasRoles('ADMIN') || user.hasRoles('MODERATOR'))) {
SettingModel.findOneAndUpdate({id: settings.id}, {
$push: {
tags: {
id: name,
models: ['COMMENTS']
}
}
});
}
else if (!ALLOWED_TAGS.includes(name) || settings.tags.findIndex((t) => {return t.id === name & t.models.include('COMMENTS');}) === -1) {
return Promise.reject(errors.ErrTagNotAllowed);
}
});
return CommentModel.findOneAndUpdate({id, 'tags.id': {$ne: name}}, {
$push: {
tags: {
id: name,
added_by: added_by
}
},
})
.then(({nModified}) => {
switch (nModified) {
case 0:
return Promise.reject(errors.ErrNoCommentFound);
case 1:
return;
default:
}
});
return CommentModel.update(query, {
$push: {
tags: tagLink
}
});
}
/**
* Removes a tag from a comment
* @throws if the tag is not on the comment
* @param {String} id the id of the comment to tag
* @param {String} tag_id the id of the tag to remove
*/
static removeTag(id, tag_id) {
return CommentModel.findOneAndUpdate({id, 'tags.id': tag_id}, {
static removeTag(id, {tag: {name}, assigned_by}, verifyOwnership = false) {
// Compose the query to find the comment that has the id: `id` and a tag
// that has the name: `name`.
const query = {
id,
'tags.tag.name': {
$eq: name
}
};
// If ownership verification is required, ensure that the person that is
// assigning the tag is the same person that owns the comment.
if (verifyOwnership) {
query['author_id'] = assigned_by;
}
return CommentModel.update(query, {
$pull: {
tags: {
id: tag_id
name
}
}
}
)
.then(({nModified}) => {
switch(nModified) {
case 0:
return Promise.reject(errors.ErrNoCommentFound);
case 1:
return;
default:
}
});
}
@@ -226,7 +180,7 @@ module.exports = class CommentsService {
static pushStatus(id, status, assigned_by = null) {
// Check to see if the comment status is in the allowable set of statuses.
if (STATUSES.indexOf(status) === -1) {
if (COMMENT_STATUS.indexOf(status) === -1) {
// Comment status is not supported! Error out here.
return Promise.reject(new Error(`status ${status} is not supported`));
+5
View File
@@ -0,0 +1,5 @@
class TagsService {
}
module.exports = TagsService;
+2 -2
View File
@@ -12,8 +12,8 @@ const redis = require('./redis');
const redisClient = redis.createClient();
const UserModel = require('../models/user');
const USER_STATUS = require('../models/user').USER_STATUS;
const USER_ROLES = require('../models/user').USER_ROLES;
const USER_STATUS = require('../models/enum/user_status');
const USER_ROLES = require('../models/enum/user_roles');
const RECAPTCHA_WINDOW_SECONDS = 60 * 10; // 10 minutes.
const RECAPTCHA_INCORRECT_TRIGGER = 5; // after 3 incorrect attempts, recaptcha will be required.
+14 -15
View File
@@ -3,23 +3,25 @@ const {graphql} = require('graphql');
const schema = require('../../../../graph/schema');
const Context = require('../../../../graph/context');
const AssetModel = require('../../../../models/asset');
const UserModel = require('../../../../models/user');
const SettingsService = require('../../../../services/settings');
const CommentsService = require('../../../../services/comments');
describe('graph.mutations.addCommentTag', () => {
let comment;
let comment, asset;
beforeEach(async () => {
await SettingsService.init();
comment = await CommentsService.publicCreate({body: `hello there! ${ String(Math.random()).slice(2)}`});
asset = new AssetModel({url: 'http://new.test.com/'});
await asset.save();
comment = await CommentsService.publicCreate({asset_id: asset.id, body: `hello there! ${String(Math.random()).slice(2)}`});
});
const query = `
mutation AddCommentTag ($id: ID!, $tag: String!) {
addCommentTag(id:$id, tag:$tag) {
comment {
id
}
mutation AddCommentTag ($id: ID!, $asset_id: ID!, $name: String!) {
addCommentTag(id: $id, asset_id: $asset_id, name: $name) {
errors {
translation_key
}
@@ -30,16 +32,14 @@ describe('graph.mutations.addCommentTag', () => {
it('moderators can add tags to comments', async () => {
const user = new UserModel({roles: ['MODERATOR' ]});
const context = new Context({user});
const response = await graphql(schema, query, {}, context, {id: comment.id, tag: 'BEST'});
const response = await graphql(schema, query, {}, context, {id: comment.id, asset_id: asset.id, name: 'BEST'});
if (response.errors && response.errors.length) {
console.error(response.errors);
}
expect(response.errors).to.be.empty;
return CommentsService.findById(response.data.addCommentTag.comment.id)
.then(({tags}) => {
expect(tags).to.have.length(1);
});
let {tags} = await CommentsService.findById(comment.id);
expect(tags).to.have.length(1);
});
describe('users who cant add tags', () => {
@@ -48,15 +48,14 @@ describe('graph.mutations.addCommentTag', () => {
'regular commenter': new UserModel({}),
'banned moderator': new UserModel({roles: ['MODERATOR'], status: 'BANNED'})
}).forEach(([ userDescription, user ]) => {
it(userDescription, async function () {
it(userDescription, async () => {
const context = new Context({user});
const response = await graphql(schema, query, {}, context, {id: comment.id, tag: 'BEST', privacy_type: 'PUBLIC'});
const response = await graphql(schema, query, {}, context, {id: comment.id, asset_id: asset.id, name: 'BEST'});
if (response.errors && response.errors.length) {
console.error(response.errors);
}
expect(response.errors).to.be.empty;
expect(response.data.addCommentTag.errors).to.deep.equal([{'translation_key':'NOT_AUTHORIZED'}]);
expect(response.data.addCommentTag.comment).to.be.null;
});
});
});
+4 -2
View File
@@ -21,7 +21,9 @@ describe('graph.mutations.createComment', () => {
id
status
tags {
id
tag {
name
}
}
}
errors {
@@ -228,7 +230,7 @@ describe('graph.mutations.createComment', () => {
.then(({tags}) => {
if (tag) {
expect(tags).to.have.length(1);
expect(tags[0]).to.have.property('id', tag);
expect(tags[0].tag.name).to.have.equal(tag);
} else {
expect(tags).length(0);
}
+33 -35
View File
@@ -6,22 +6,24 @@ const Context = require('../../../../graph/context');
const UserModel = require('../../../../models/user');
const SettingModel = require('../../../../models/setting');
const AssetModel = require('../../../../models/asset');
const SettingsService = require('../../../../services/settings');
const CommentsService = require('../../../../services/comments');
describe('graph.mutations.removeCommentTag', () => {
let comment;
let asset, comment;
beforeEach(async () => {
await SettingsService.init();
comment = await CommentsService.publicCreate({body: `hello there! ${ String(Math.random()).slice(2)}`});
asset = new AssetModel({url: 'http://new.test.com/'});
await asset.save();
comment = await CommentsService.publicCreate({asset_id: asset.id, body: `hello there! ${String(Math.random()).slice(2)}`});
});
const query = `
mutation RemoveCommentTag ($id: ID!, $tag: String!) {
removeCommentTag(id:$id, tag:$tag) {
comment {
id
}
mutation RemoveCommentTag ($id: ID!, $asset_id: ID!, $name: String!) {
removeCommentTag(id: $id, asset_id: $asset_id, name: $name) {
errors {
translation_key
}
@@ -30,57 +32,53 @@ describe('graph.mutations.removeCommentTag', () => {
`;
it('moderators can add remove tags from comments', async () => {
const user = new UserModel({roles: ['MODERATOR' ]});
const user = new UserModel({roles: ['MODERATOR']});
const context = new Context({user});
// add a tag first
await CommentsService.addTag(comment.id, 'BEST', user.id);
const response = await graphql(schema, query, {}, context, {id: comment.id, tag: 'BEST'});
await CommentsService.addTag(comment.id, {tag: {name: 'BEST'}}, false);
const response = await graphql(schema, query, {}, context, {id: comment.id, asset_id: asset.id, name: 'BEST'});
if (response.errors && response.errors.length) {
console.error(response.errors);
}
expect(response.errors).to.be.empty;
expect(response.data.removeCommentTag.errors).to.be.null;
CommentsService.findById(response.data.removeCommentTag.comment.id)
.then(({tags}) => {
expect(tags).to.deep.equal([]);
});
let retrievedComment = await CommentsService.findById(comment.id);
expect(retrievedComment.tags).to.have.length(0);
});
describe('users who cant remove tags', () => {
// allow the tag in the settings
SettingModel.findOneAndUpdate({id: 1}, {
before(() => SettingModel.findOneAndUpdate({id: 1}, {
$push: {
tags: {
id: 'BEST',
models: ['COMMENTS']
}
}
})
.then(() => {
Object.entries({
'anonymous': undefined,
'regular commenter': new UserModel({}),
'banned moderator': new UserModel({roles: ['MODERATOR'], status: 'BANNED'})
}).forEach(([ userDescription, user ]) => {
it(userDescription, async function () {
const context = new Context({user});
}));
// add a tag first
await CommentsService.addTag(comment.id, 'BEST', user.id);
Object.entries({
'anonymous': undefined,
'regular commenter': new UserModel({}),
'banned moderator': new UserModel({roles: ['MODERATOR'], status: 'BANNED'})
}).forEach(([userDescription, user]) => {
it(userDescription, async function () {
const context = new Context({user});
const response = await graphql(schema, query, {}, context, {id: comment.id, tag: 'BEST'});
if (response.errors && response.errors.length) {
console.error(response.errors);
}
expect(response.errors).to.be.empty;
// add a tag first
await CommentsService.addTag(comment.id, {tag: {name: 'BEST'}}, false);
expect(response.data.removeCommentTag.errors).to.deep.equal([{'translation_key':'NOT_AUTHORIZED'}]);
expect(response.data.removeCommentTag.comment).to.be.null;
});
const response = await graphql(schema, query, {}, context, {id: comment.id, asset_id: asset.id, name: 'BEST'});
if (response.errors && response.errors.length) {
console.error(response.errors);
}
expect(response.errors).to.be.empty;
expect(response.data.removeCommentTag.errors).to.deep.equal([{'translation_key':'NOT_AUTHORIZED'}]);
});
});
});
+65 -44
View File
@@ -221,63 +221,84 @@ describe('services.CommentsService', () => {
describe('#addTag', () => {
it('adds a tag', async () => {
const commentId = comments[0].id;
const tagName = 'BEST';
const userId = users[0].id;
await CommentsService.addTag(commentId, tagName, userId, 'PUBLIC');
const {tags} = await CommentsService.findById(commentId);
expect(tags.length).to.equal(1);
expect(tags[0].id).to.equal(tagName);
expect(tags[0].added_by).to.equal(userId);
expect(tags[0].created_at).to.be.an.instanceof(Date);
});
it('can\'t add a tag to comment id that doesn\'t exist', async () => {
const commentId = 'fakenews';
const tagName = 'BEST';
const userId = users[0].id;
CommentsService.addTag(commentId, tagName, userId)
.catch((error) => {
expect(error).to.not.be.null;
const id = comments[0].id;
const name = 'BEST';
const assigned_by = users[0].id;
await CommentsService.addTag(id, {
tag: {
name
},
assigned_by
});
const {tags} = await CommentsService.findById(id);
expect(tags.length).to.equal(1);
expect(tags[0].tag.name).to.equal(name);
expect(tags[0].assigned_by).to.equal(assigned_by);
});
it('can\'t add same tag.id twice', async () => {
const commentId = comments[0].id;
const tagName = 'BEST';
const userId = users[0].id;
const id = comments[0].id;
const name = 'BEST';
const assigned_by = users[0].id;
await CommentsService.addTag(id, {
tag: {
name
},
assigned_by
});
// first time
await CommentsService.addTag(commentId, tagName, userId);
{
let {tags} = await CommentsService.findById(id);
expect(tags.length).to.equal(1);
}
// second time should fail
await expect(CommentsService.addTag(commentId, tagName, userId)).to.be.rejected;
await CommentsService.addTag(id, {
tag: {
name
},
assigned_by
});
{
let {tags} = await CommentsService.findById(id);
expect(tags.length).to.equal(1);
}
});
});
describe('#removeTag', () => {
it('removes a tag', async () => {
const commentId = comments[0].id;
const tagName = 'BEST';
await CommentsService.addTag(commentId, tagName, users[0].id);
const {tags} = await CommentsService.findById(commentId);
expect(tags.length).to.equal(1);
const id = comments[0].id;
const name = 'BEST';
const assigned_by = users[0].id;
await CommentsService.addTag(id, {
tag: {
name
},
assigned_by
});
{
const {tags} = await CommentsService.findById(id);
expect(tags.length).to.equal(1);
}
// ok now to remove it
await CommentsService.removeTag(commentId, tagName);
const comment = await CommentsService.findById(commentId);
expect(comment.tags.length).to.equal(0);
});
it('throws if removing a tag that isn\'t there', async () => {
const commentId = comments[0].id;
await CommentsService.removeTag(id, {
tag: {
name
},
assigned_by
});
const tagName = 'BEST';
// just make sure it has no tags to start
const {tags} = await CommentsService.findById(commentId);
expect(tags.length).to.equal(0);
// ok now to remove it
await expect(CommentsService.removeTag(commentId, tagName)).to.be.rejected;
{
const {tags} = await CommentsService.findById(id);
expect(tags.length).to.equal(0);
}
});
});