diff --git a/graph/loaders/index.js b/graph/loaders/index.js index 8bc170c6d..26727940f 100644 --- a/graph/loaders/index.js +++ b/graph/loaders/index.js @@ -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. diff --git a/graph/loaders/settings.js b/graph/loaders/settings.js index 77ece165f..9d777255f 100644 --- a/graph/loaders/settings.js +++ b/graph/loaders/settings.js @@ -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) + } }); diff --git a/graph/loaders/tags.js b/graph/loaders/tags.js deleted file mode 100644 index cb767fdf6..000000000 --- a/graph/loaders/tags.js +++ /dev/null @@ -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) - } -}); diff --git a/graph/mutators/comment.js b/graph/mutators/comment.js index ec310d763..3413c6f4e 100644 --- a/graph/mutators/comment.js +++ b/graph/mutators/comment.js @@ -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); }; /** diff --git a/graph/resolvers/comment.js b/graph/resolvers/comment.js index f94cfaf52..88ddbee07 100644 --- a/graph/resolvers/comment.js +++ b/graph/resolvers/comment.js @@ -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); }, diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index e3e3ae1d7..5b60fbaa8 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -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))); diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index a4ebe98b3..781b3b328 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -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 diff --git a/models/comment.js b/models/comment.js index 09f592eca..f3e239080 100644 --- a/models/comment.js +++ b/models/comment.js @@ -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: {}, diff --git a/models/setting.js b/models/setting.js index e63fa8f3e..44f5ad5ca 100644 --- a/models/setting.js +++ b/models/setting.js @@ -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', diff --git a/models/tag.js b/models/tag.js deleted file mode 100644 index 85bb0845a..000000000 --- a/models/tag.js +++ /dev/null @@ -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; diff --git a/models/user.js b/models/user.js index 159407fdf..b85f09a93 100644 --- a/models/user.js +++ b/models/user.js @@ -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: { diff --git a/services/comments.js b/services/comments.js index bdc878ce1..4c8f423c6 100644 --- a/services/comments.js +++ b/services/comments.js @@ -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 + } } }); } diff --git a/services/mongoose.js b/services/mongoose.js index 206075239..c7092326f 100644 --- a/services/mongoose.js +++ b/services/mongoose.js @@ -65,5 +65,4 @@ require('../models/action'); require('../models/asset'); require('../models/comment'); require('../models/setting'); -require('../models/tag'); require('../models/user'); diff --git a/services/tags.js b/services/tags.js deleted file mode 100644 index 5cd0a09e1..000000000 --- a/services/tags.js +++ /dev/null @@ -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(); - } - -}; diff --git a/test/server/graph/loaders/metrics.js b/test/server/graph/loaders/metrics.js index a24c18f79..5d74f154d 100644 --- a/test/server/graph/loaders/metrics.js +++ b/test/server/graph/loaders/metrics.js @@ -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); }); diff --git a/test/server/graph/mutations/addCommentTag.js b/test/server/graph/mutations/addCommentTag.js index 01966bd88..83a12dc2e 100644 --- a/test/server/graph/mutations/addCommentTag.js +++ b/test/server/graph/mutations/addCommentTag.js @@ -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); }); }); diff --git a/test/server/graph/mutations/createComment.js b/test/server/graph/mutations/createComment.js index d132c46c7..31cb9872f 100644 --- a/test/server/graph/mutations/createComment.js +++ b/test/server/graph/mutations/createComment.js @@ -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); diff --git a/test/server/graph/mutations/removeCommentTag.js b/test/server/graph/mutations/removeCommentTag.js index 7660eef35..4deb4bd5e 100644 --- a/test/server/graph/mutations/removeCommentTag.js +++ b/test/server/graph/mutations/removeCommentTag.js @@ -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([]); }); diff --git a/test/server/services/comments.js b/test/server/services/comments.js index d4fc5f17e..ed908d1b2 100644 --- a/test/server/services/comments.js +++ b/test/server/services/comments.js @@ -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