mirror of
https://github.com/wassname/talk.git
synced 2026-07-05 01:43:25 +08:00
Adjusted query/tag flow
This commit is contained in:
+140
-15
@@ -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) => {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
const Tag = {
|
||||
|
||||
};
|
||||
|
||||
module.exports = Tag;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
'LIKE',
|
||||
'FLAG'
|
||||
];
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = [
|
||||
'ACCEPTED',
|
||||
'REJECTED',
|
||||
'PREMOD',
|
||||
'NONE'
|
||||
];
|
||||
@@ -0,0 +1,5 @@
|
||||
module.exports = [
|
||||
'ASSETS',
|
||||
'COMMENTS',
|
||||
'USERS'
|
||||
];
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
'PRE',
|
||||
'POST'
|
||||
];
|
||||
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
'ADMIN',
|
||||
'MODERATOR'
|
||||
];
|
||||
@@ -0,0 +1,6 @@
|
||||
module.exports = [
|
||||
'ACTIVE',
|
||||
'BANNED',
|
||||
'PENDING',
|
||||
'APPROVED' // Indicates that the users' username has been approved
|
||||
];
|
||||
@@ -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;
|
||||
@@ -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
@@ -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
@@ -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
@@ -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`));
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
class TagsService {
|
||||
|
||||
}
|
||||
|
||||
module.exports = TagsService;
|
||||
+2
-2
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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'}]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user