Error on duplicate.

This commit is contained in:
gaba
2017-05-08 13:11:48 -07:00
parent 28920eb357
commit cb09c50820
19 changed files with 173 additions and 266 deletions
-2
View File
@@ -6,7 +6,6 @@ const Assets = require('./assets');
const Comments = require('./comments');
const Metrics = require('./metrics');
const Settings = require('./settings');
const Tags = require('./tags');
const Users = require('./users');
const plugins = require('../../services/plugins');
@@ -19,7 +18,6 @@ let loaders = [
Comments,
Metrics,
Settings,
Tags,
Users,
// Load the plugin loaders from the manager.
+15 -2
View File
@@ -1,12 +1,25 @@
const SettingsService = require('../../services/settings');
const SettingModel = require('../../models/setting');
const util = require('./util');
/**
* Search for tags based on their item_type.
* @param {String} item_type the item type to search by
* @return {Promise} resolves to distinct items tags
*/
const getByItemType = (_, item_type) => {
return SettingModel.distinct('tags.models', {item_type});
};
/**
* Creates a set of loaders based on a GraphQL context.
* @param {Object} context the context of the GraphQL request
* @return {Object} object of loaders
*/
module.exports = () => ({
Settings: new util.SingletonResolver(() => SettingsService.retrieve())
module.exports = (context) => ({
Settings: {
get: new util.SingletonResolver(() => SettingsService.retrieve()),
getTagsByItemType: (item_type) => getByItemType(context, item_type)
}
});
-37
View File
@@ -1,37 +0,0 @@
const DataLoader = require('dataloader');
const util = require('./util');
const TagsService = require('../../services/tags');
const TagModel = require('../../models/tag');
/**
* Gets tags based on their item id's.
*/
const getByItemID = (_, item_ids) => {
return TagsService
.findByItemIdArray(item_ids)
.then(util.arrayJoinBy(item_ids, 'item_id'));
};
/**
* Search for tags based on their item_type and ensures that
* the tags returned have unique item id's.
* @param {String} item_type the item id to search by
* @return {Promise} resolves to distinct items tags
*/
const getItemIdsByItemType = (_, item_type) => {
return TagModel.distinct('item_id', {item_type});
};
/**
* Creates a set of loaders based on a GraphQL context.
* @param {Object} context the context of the GraphQL request
* @return {Object} object of loaders
*/
module.exports = (context) => ({
Tags: {
getByID: new DataLoader((ids) => getByItemID(context, ids)),
getByTypes: ({item_type}) => getItemIdsByItemType(context, item_type)
}
});
+4 -4
View File
@@ -16,7 +16,7 @@ const Wordlist = require('../../services/wordlist');
* @param {String} [status='NONE'] the status of the new comment
* @return {Promise} resolves to the created comment
*/
const createComment = ({user, loaders: {Comments}, pubsub}, {body, asset_id, parent_id = null, tags = []}, status = 'NONE') => {
const createComment = ({user, loaders: {Comments}, pubsub}, {body, asset_id, parent_id = null}, status = 'NONE') => {
return CommentsService.publicCreate({
body,
@@ -28,7 +28,7 @@ const createComment = ({user, loaders: {Comments}, pubsub}, {body, asset_id, par
.then(async (comment) => {
if (user.hasRoles('ADMIN') || user.hasRoles('MODERATOR')) {
await CommentsService.addTag(comment.id, 'STAFF', user.id, 'PUBLIC');
await CommentsService.addTag(comment.id, 'STAFF', user.id);
}
// If the loaders are present, clear the caches for these values because we
@@ -208,8 +208,8 @@ const setCommentStatus = ({user, loaders: {Comments}}, {id, status}) => {
* @param {String} id identifier of the comment (uuid)
* @param {String} tag name of the tag
*/
const addCommentTag = ({user, loaders: {Comments}}, {id, tag, privacy_type}) => {
return CommentsService.addTag(id, tag, user.id, privacy_type);
const addCommentTag = ({user, loaders: {Comments}}, {id, tag}) => {
return CommentsService.addTag(id, tag, user.id);
};
/**
-3
View File
@@ -6,9 +6,6 @@ const Comment = {
return Comments.get.load(parent_id);
},
tags({id}, _, {loaders: {Tags}}) {
return Tags.getByID.load([id]);
},
user({author_id}, _, {loaders: {Users}}) {
return Users.getByID.load(author_id);
},
+2 -2
View File
@@ -29,8 +29,8 @@ const RootMutation = {
setCommentStatus(_, {id, status}, {mutators: {Comment}}) {
return wrapResponse(null)(Comment.setCommentStatus({id, status}));
},
addCommentTag(_, {id, tag, privacy_type}, {mutators: {Comment}}) {
return wrapResponse('comment')(Comment.addCommentTag({id, tag, privacy_type}).then(() => CommentsService.findById(id)));
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)));
+11 -6
View File
@@ -54,10 +54,10 @@ type User {
type Tag {
# the actual tag for the comment.
name: String!
id: String!
# the user that assigned the tag. If NULL then the system automatically tagged it.
assigned_by: String
added_by: String
# the time when the tag was assigned.
created_at: Date!
@@ -588,8 +588,13 @@ enum ACTION_ITEM_TYPE {
USERS
}
enum TAG_TYPE {
STAFF
input CreateLikeInput {
# The item's id for which we are to create a like.
item_id: ID!
# The type of the item for which we are to create the like.
item_type: ACTION_ITEM_TYPE!
}
input CreateCommentInput {
@@ -604,7 +609,7 @@ input CreateCommentInput {
body: String!
# Tags
tags: [TAG_TYPE]
tags: [String]
}
@@ -746,7 +751,7 @@ type RootMutation {
setCommentStatus(id: ID!, status: COMMENT_STATUS!): SetCommentStatusResponse
# Add tag to comment.
addCommentTag(id: ID!, tag: String!, privacy_type: String!): AddCommentTagResponse
addCommentTag(id: ID!, tag: String!): AddCommentTagResponse
# Remove tag from comment.
removeCommentTag(id: ID!, tag: String!): RemoveCommentTagResponse
+25
View File
@@ -30,6 +30,29 @@ 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}
@@ -55,6 +78,8 @@ const CommentSchema = new Schema({
},
parent_id: String,
tags: [TagSchema],
// Additional metadata stored on the field.
metadata: {
default: {},
+37 -1
View File
@@ -1,6 +1,41 @@
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'
@@ -88,7 +123,8 @@ const SettingSchema = new Schema({
type: Array,
default: ['localhost']
}
}
},
tags: [TagSchema]
}, {
timestamps: {
createdAt: 'created_at',
-77
View File
@@ -1,77 +0,0 @@
const mongoose = require('../services/mongoose');
const uuid = require('uuid');
const Schema = mongoose.Schema;
// in settings
// --> decide who can apply them (self, role, anyone) and
// Who can see the tag (self, by role, anyone)
const PRIVACY_TYPES = [
'ADMIN',
'SELF',
'PUBLIC'
];
// The type of item that the tag is apply on.
const ITEM_TYPES = [
'ASSETS',
'COMMENTS',
'USERS'
];
/**
* The Mongo schema for a Comment Tag.
* @type {Schema}
*/
const TagSchema = new Schema({
id: {
type: String,
default: uuid.v4,
unique: true
},
name: {
type: String,
},
item_type: {
type: String,
enum: ITEM_TYPES
},
item_id: String,
// The User ID of the user that assigned the status.
assigned_by: {
type: String,
default: null
},
privacy_type: {
type: String,
enum: PRIVACY_TYPES,
default: 'SELF'
},
// Additional metadata stored on the field.
metadata: Schema.Types.Mixed
}, {
timestamps: {
createdAt: 'created_at',
updatedAt: 'updated_at'
}
});
// Add the indixies on the tag on an item.
TagSchema.index({
'item_type': 1,
'item_id': 1,
'name': 1
}, {
unique: true,
background: false
});
const Tag = mongoose.model('Tag', TagSchema);
module.exports = Tag;
+12
View File
@@ -111,6 +111,18 @@ const UserSchema = new mongoose.Schema({
default: false
},
tags: [{
id: {
type: String,
unique: true
},
public: Boolean,
text: [{
type: mongoose.Schema.Types.Mixed,
default: null
}]
}],
// User's settings
settings: {
bio: {
+30 -24
View File
@@ -3,8 +3,7 @@ const CommentModel = require('../models/comment');
const ActionModel = require('../models/action');
const ActionsService = require('./actions');
const TagsService = require('./tags');
const TagModel = require('../models/tag');
const SettingsService = require('./settings');
const STATUSES = [
'ACCEPTED',
@@ -13,6 +12,10 @@ const STATUSES = [
'NONE',
];
const ALLOWED_TAGS = [
'STAFF'
];
module.exports = class CommentsService {
/**
@@ -22,6 +25,8 @@ module.exports = class CommentsService {
*/
static publicCreate(comment) {
console.log('-----------------> debug publicCreate');
// Check to see if this is an array of comments, if so map it out.
if (Array.isArray(comment)) {
return Promise.all(comment.map(CommentsService.publicCreate));
@@ -47,22 +52,26 @@ module.exports = class CommentsService {
* @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} assigned_by the user id for the user who added the tag
* @param {String} added_by the user id for the user who added the tag
*/
static addTag(id, name, assigned_by, privacy_type) {
static addTag(id, name, added_by) {
return CommentModel.findOne({id})
.then((comment) => {
if (comment == null) {
console.log('-----------------> debug addtag', name);
SettingsService.retrieve()
.then(({tags}) => {
if (!ALLOWED_TAGS.includes(name) || tags.findIndex((t) => {return t.id === name & t.models.include('COMMENTS');}) === -1) {
return Promise.reject(new Error('tag not allowed'));
}
return TagsService.insertCommentTag({
name,
item_id: id,
item_type: 'COMMENTS',
user_id: assigned_by,
privacy_type
});
});
return CommentModel.findOneAndUpdate({id}, {
$push: {
tags: {
id: name,
added_by: added_by
}
}
});
}
@@ -70,17 +79,14 @@ module.exports = class CommentsService {
* 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} name the name of the tag to add
* @param {String} tag_id the id of the tag to remove
*/
static removeTag(id, name) {
return TagModel.findOneAndRemove({
item_type: 'COMMENTS',
item_id: id,
name
})
.then((tag) => {
if (tag == null) {
return Promise.reject(new Error('tag does not exist'));
static removeTag(id, tag_id) {
return CommentModel.findOneAndUpdate({id}, {
$pull: {
tags: {
id: tag_id
}
}
});
}
-1
View File
@@ -65,5 +65,4 @@ require('../models/action');
require('../models/asset');
require('../models/comment');
require('../models/setting');
require('../models/tag');
require('../models/user');
-83
View File
@@ -1,83 +0,0 @@
const TagModel = require('../models/tag');
const ALLOWED_COMMENT_TAGS = [
{name: 'STAFF'},
{name: 'BEST'},
];
const ALLOWED_PRIVACY_TYPE = [
{privacy_type: 'PUBLIC'},
{privacy_type: 'SELF'},
{privacy_type: 'ADMIN'}
];
module.exports = class TagsService {
/**
* Finds a tag by the id.
* @param {String} id identifier of the tag (uuid)
*/
static findById(id) {
return TagModel.findOne({id});
}
/**
* Finds atag by the item_id and name.
* @param {String} item_id identifier of the item that the tag was applied into(uuid)
* @param {string} name name of the tag
*/
static findByItemIdAndName(item_id, name, item_type) {
return TagModel.find({
item_id,
item_type,
name
});
}
/**
* Finds actions in an array of ids.
* @param {String} ids array of user identifiers (uuid)
*/
static async findByItemIdArray(item_ids) {
let tags = await TagModel.find({
'item_id': {$in: item_ids}
});
if (tags === null) {
return [];
}
return tags;
}
/**
* Add a tag.
* @param {string} name the actual tag
* @param {String} item_id identifier of the comment (uuid)
* @param {String} item_type type of the object being tag (COMMENTS)
* @param {String} user_id user id that assigned the tag (uuid)
* @param {String} privacy_type visibility of the tag on the comment
* @return {Promise}
*/
static insertCommentTag(tag) {
if (ALLOWED_COMMENT_TAGS.find((t) => t.name === tag.name) == null) {
return Promise.reject(new Error('tag not allowed'));
}
if (ALLOWED_PRIVACY_TYPE.find((p) => p.privacy_type === tag.privacy_type) == null) {
return Promise.reject(new Error('privacy type not allowed'));
}
// Create/Update the tag.
let newtag = new TagModel({
name: tag.name,
item_id: tag.item_id,
item_type: tag.item_type,
assigned_by: tag.user_id,
privacy_type: tag.privacy_type
});
return newtag.save();
}
};
+22 -6
View File
@@ -23,11 +23,27 @@ describe('graph.loaders.Metrics', () => {
describe('different comment states', () => {
beforeEach(() => CommentModel.create([
{id: '1', body: 'a new comment!'},
{id: '2', body: 'a new comment!'},
{id: '3', body: 'a new comment!'}
]));
beforeEach(() =>{
CommentModel.create( {id: '1', body: 'a new comment!'})
.then((comment) => {
console.log('*************** wtf comment', comment);
})
.catch((error) => {
console.log('1 debug the rror create ', error);
});
console.log('debug 1');
CommentModel.create({id: '2', body: 'a new comment!'})
.catch((error) => {
console.log('2 debug the rror create ', error);
});
console.log('debug 2');
CommentModel.create({id: '3', body: 'a new comment!'})
.catch((error) => {
console.log('3 debug the rror create ', error);
});
console.log('debug 3');
}
);
[
{flagged: 0, actions: []},
@@ -40,6 +56,7 @@ describe('graph.loaders.Metrics', () => {
]}
].forEach(({flagged, actions}) => {
console.log('-----------------> debug forEach');
describe(`with actions=${actions.length}`, () => {
beforeEach(() => ActionModel.create(actions));
@@ -52,7 +69,6 @@ describe('graph.loaders.Metrics', () => {
to: (new Date()).setMinutes((new Date()).getMinutes() + 5)
})
.then(({data, errors}) => {
console.log(errors);
expect(errors).to.be.undefined;
expect(data.flagged).to.have.length(flagged);
});
+5 -6
View File
@@ -4,7 +4,6 @@ const {graphql} = require('graphql');
const schema = require('../../../../graph/schema');
const Context = require('../../../../graph/context');
const UserModel = require('../../../../models/user');
const TagService = require('../../../../services/tags');
const SettingsService = require('../../../../services/settings');
const CommentsService = require('../../../../services/comments');
@@ -16,8 +15,8 @@ describe('graph.mutations.addCommentTag', () => {
});
const query = `
mutation AddCommentTag ($id: ID!, $tag: String!, $privacy_type: String!) {
addCommentTag(id:$id, tag:$tag, privacy_type:$privacy_type) {
mutation AddCommentTag ($id: ID!, $tag: String!) {
addCommentTag(id:$id, tag:$tag) {
comment {
id
}
@@ -31,14 +30,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', privacy_type: 'PUBLIC'});
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;
return TagService.findByItemIdAndName(response.data.addCommentTag.comment.id, 'BEST', 'COMMENTS')
.then((tags) => {
return CommentsService.findById(response.data.addCommentTag.comment.id)
.then(({tags}) => {
expect(tags).to.have.length(1);
});
});
+3 -3
View File
@@ -9,7 +9,7 @@ const AssetModel = require('../../../../models/asset');
const ActionModel = require('../../../../models/action');
const SettingsService = require('../../../../services/settings');
const TagService = require('../../../../services/tags');
const CommentsService = require('../../../../services/comments');
describe('graph.mutations.createComment', () => {
beforeEach(() => SettingsService.init());
@@ -223,9 +223,9 @@ describe('graph.mutations.createComment', () => {
expect(data.createComment).to.have.property('comment').not.null;
expect(data.createComment).to.have.property('errors').null;
return TagService.findByItemIdAndName(data.createComment.comment.id, tag, 'COMMENTS');
return CommentsService.findById(data.createComment.comment.id);
})
.then((tags) => {
.then(({tags}) => {
if (tag) {
expect(tags).to.have.length(1);
expect(tags[0]).to.have.property('name', tag);
@@ -7,7 +7,6 @@ const UserModel = require('../../../../models/user');
const SettingsService = require('../../../../services/settings');
const CommentsService = require('../../../../services/comments');
const TagService = require('../../../../services/tags');
describe('graph.mutations.removeCommentTag', () => {
let comment;
@@ -42,8 +41,8 @@ describe('graph.mutations.removeCommentTag', () => {
expect(response.errors).to.be.empty;
expect(response.data.removeCommentTag.errors).to.be.null;
TagService.findByItemIdAndName(response.data.removeCommentTag.comment.id, 'BEST')
.then((tags) => {
CommentsService.findById(response.data.removeCommentTag.comment.id)
.then(({tags}) => {
expect(tags).to.deep.equal([]);
});
+5 -6
View File
@@ -5,7 +5,6 @@ const ActionsService = require('../../../services/actions');
const UsersService = require('../../../services/users');
const SettingsService = require('../../../services/settings');
const CommentsService = require('../../../services/comments');
const TagService = require('../../../services/tags');
const settings = {id: '1', moderation: 'PRE', wordlist: {banned: ['bad words'], suspect: ['suspect words']}};
@@ -226,7 +225,7 @@ describe('services.CommentsService', () => {
const tagName = 'BEST';
const userId = users[0].id;
await CommentsService.addTag(commentId, tagName, userId, 'PUBLIC');
const tags = await TagService.findByItemIdAndName(commentId, 'BEST', 'COMMENTS');
const {tags} = await CommentsService.findById(commentId);
expect(tags.length).to.equal(1);
expect(tags[0].name).to.equal(tagName);
expect(tags[0].assigned_by).to.equal(userId);
@@ -257,13 +256,13 @@ describe('services.CommentsService', () => {
const commentId = comments[0].id;
const tagName = 'BEST';
await CommentsService.addTag(commentId, tagName, users[0].id, 'PUBLIC');
const tags = await TagService.findByItemIdAndName(commentId, tagName, 'COMMENTS');
const {tags} = await CommentsService.findById(commentId);
expect(tags.length).to.equal(1);
// ok now to remove it
await CommentsService.removeTag(commentId, tagName);
const tags2 = await TagService.findByItemIdAndName(commentId, tagName, 'COMMENTS');
expect(tags2.length).to.equal(0);
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;
@@ -271,7 +270,7 @@ describe('services.CommentsService', () => {
const tagName = 'BEST';
// just make sure it has no tags to start
const tags = await TagService.findByItemIdAndName(commentId, tagName, 'COMMENTS');
const {tags} = await CommentsService.findById(commentId);
expect(tags.length).to.equal(0);
// ok now to remove it