modularized mutators, loaders

This commit is contained in:
Wyatt Johnson
2017-01-20 16:49:19 -07:00
parent 32d23214f1
commit 0ec2c11cf7
29 changed files with 580 additions and 288 deletions
+1 -1
View File
@@ -17,7 +17,7 @@
"no-template-curly-in-string": [1],
"no-unsafe-negation": [1],
"array-callback-return": [1],
"eqeqeq": [2],
"eqeqeq": [2, "smart"],
"no-eval": [2],
"no-global-assign": [2],
"no-implied-eval": [2],
+1 -1
View File
@@ -22,7 +22,7 @@ const Comment = ({comment, currentUser, asset, depth}) => {
id={`c_${comment.id}`}
style={{marginLeft: depth * 30}}>
<hr aria-hidden={true} />
{/*<AuthorName
{/* <AuthorName
author={comment.user}
addNotification={this.props.addNotification}
id={comment.id}
+3 -2
View File
@@ -3,8 +3,8 @@ import Pym from 'pym.js';
import {compose} from 'react-apollo';
import {connect} from 'react-redux';
import {postComment} from './graphql/mutations'
import {queryStream} from './graphql/queries'
import {postComment} from './graphql/mutations';
import {queryStream} from './graphql/queries';
import {
@@ -92,6 +92,7 @@ class Embed extends Component {
}
render () {
// const rootItemId = this.props.items.assets && Object.keys(this.props.items.assets)[0];
// const rootItem = this.props.items.assets && this.props.items.assets[rootItemId];
// const {actions, users, comments} = this.props.items;
@@ -1,4 +1,4 @@
import { graphql } from 'react-apollo';
import {graphql} from 'react-apollo';
import POST_COMMENT from './postComment.graphql';
export const postComment = graphql(POST_COMMENT, {
@@ -1,4 +1,4 @@
import { graphql } from 'react-apollo';
import {graphql} from 'react-apollo';
import STREAM_QUERY from './streamQuery.graphql';
import Pym from 'pym.js';
@@ -1,7 +1,7 @@
import React from 'react';
const Notification = (props) => {
console.log(props)
console.log(props);
if (props.notification.text) {
setTimeout(() => {
props.clearNotification();
+1 -1
View File
@@ -59,7 +59,7 @@ class CommentBox extends Component {
}
postItem(comment, 'comments')
.then(({data}) => {
const postedComment = data.createComment
const postedComment = data.createComment;
const commentId = postedComment.id;
if (postedComment.status === 'rejected') {
addNotification('error', lang.t('comment-post-banned-word'));
+21
View File
@@ -0,0 +1,21 @@
const loaders = require('./loaders');
const mutators = require('./mutators');
/**
* Stores the request context.
*/
class Context {
constructor({user = null}) {
// Load the current logged in user to `user`, otherwise this'll be null.
this.user = user;
// Create the loaders.
this.loaders = loaders(this);
// Create the mutators.
this.mutators = mutators(this);
}
}
module.exports = Context;
+8 -18
View File
@@ -1,24 +1,14 @@
const loaders = require('./loaders');
const mutators = require('./mutators');
const schema = require('./schema');
const Context = require('./context');
module.exports = {
createGraphOptions: (req) => {
createGraphOptions: (req) => ({
let context = {};
// Schema is created already, so just include it.
schema,
// Load the current logged in user to `user`, otherwise this'll be null.
context.user = req.user;
// Create the loaders.
context.loaders = loaders(context);
// Create the mutators.
context.mutators = mutators(context);
return {
schema,
context
};
}
// Load in the new context here, this'll create the loaders + mutators for
// the lifespan of this request.
context: new Context(req)
})
};
-171
View File
@@ -1,171 +0,0 @@
const DataLoader = require('dataloader');
const _ = require('lodash');
const url = require('url');
const errors = require('../errors');
const scraper = require('../services/scraper');
const Comment = require('../models/comment');
const User = require('../models/user');
const Action = require('../models/action');
const Asset = require('../models/asset');
const Settings = require('../models/setting');
/**
* SingletonResolver is a cached loader for a single result.
*/
class SingletonResolver {
constructor(resolver) {
this._cache = null;
this._resolver = resolver;
}
load() {
if (this._cache) {
return this._cache;
}
let promise = this._resolver(arguments).then((result) => {
return result;
});
// Set the promise on the cache.
this._cache = promise;
return promise;
}
}
/**
* This joins a set of results with a specific keys and sets an empty array in
* place if it was not found.
* @param {Array} ids ids to locate
* @param {String} key key to group by
* @return {Array} array of results
*/
const arrayJoinBy = (ids, key) => (items) => {
const itemsByKey = _.groupBy(items, key);
return ids.map((id) => {
if (id in itemsByKey) {
return itemsByKey[id];
}
return [];
});
};
/**
* This joins a set of results with a specific keys and sets null in place if it
* was not found.
* @param {Array} ids ids to locate
* @param {String} key key to group by
* @return {Array} array of results
*/
const singleJoinBy = (ids, key) => (items) => {
const itemsByKey = _.groupBy(items, key);
return ids.map((id) => {
if (id in itemsByKey) {
return itemsByKey[id][0];
}
return null;
});
};
/**
* Retrieves assets by an array of ids.
* @param {Array} ids array of ids to lookup
*/
const genAssetsByID = (ids) => Asset.find({
id: {
$in: ids
}
}).then(singleJoinBy(ids, 'id'));
/**
* Retrieves actions by an array of ids.
* @param {Array} ids array of ids to lookup
*/
const genActionsByID = (ids, user = {}) => Action.getActionSummaries(ids, user.id).then(arrayJoinBy(ids, 'item_id'));
/**
* Retrieves comments by an array of asset id's.
* @param {Array} ids array of ids to lookup
*/
const genCommentsByAssetID = (ids) => Comment.find({
asset_id: {
$in: ids
},
parent_id: null,
status: {
$in: [null, 'accepted']
}
}).then(arrayJoinBy(ids, 'asset_id'));
/**
* Retrieves comments by an array of parent ids.
* @param {Array} ids array of ids to lookup
*/
const genCommentsByParentID = (ids) => Comment.find({
parent_id: {
$in: ids
},
status: {
$in: [null, 'accepted']
}
}).then(arrayJoinBy(ids, 'parent_id'));
/**
* This endpoint find or creates an asset at the given url when it is loaded.
* @param {String} asset_url the url passed in from the query
* @returns {Promise} resolves to the asset
*/
const findOrCreateAssetByURL = (asset_url) => {
// Verify that the asset_url is parsable.
let parsed_asset_url = url.parse(asset_url);
if (!parsed_asset_url.protocol) {
return Promise.reject(errors.ErrInvalidAssetURL);
}
return Asset.findOrCreateByUrl(asset_url)
.then((asset) => {
// If the asset wasn't scraped before, scrape it! Otherwise just return
// the asset.
if (!asset.scraped) {
return scraper.create(asset).then(() => asset);
}
return asset;
});
};
/**
* Creates a set of loaders based on a GraphQL context.
* @param {Object} context the context of the GraphQL request
* @return {Object} object of loaders
*/
const createLoaders = (context) => ({
Comments: {
getByParentID: new DataLoader((ids) => genCommentsByParentID(ids)),
getByAssetID: new DataLoader((ids) => genCommentsByAssetID(ids)),
},
Actions: {
getByID: new DataLoader((ids) => genActionsByID(ids, context.user)),
},
Users: {
getByID: new DataLoader((ids) => User.findByIdArray(ids))
},
Assets: {
// TODO: decide whether we want to move these to mutators or not, as in fact
// this operation create a new asset if one isn't found.
getByURL: (url) => findOrCreateAssetByURL(url),
getByID: new DataLoader((ids) => genAssetsByID(ids)),
getAll: new SingletonResolver(() => Asset.find({}))
},
Settings: new SingletonResolver(() => Settings.retrieve())
});
module.exports = createLoaders;
+27
View File
@@ -0,0 +1,27 @@
const DataLoader = require('dataloader');
const util = require('./util');
const Action = require('../../models/action');
/**
* Looks up actions based on the requested id's all bounded by the user.
* @param {Object} context the context of the request
* @param {Array} ids array of id's to get
* @return {Promise} resolves to the promises of the requested actions
*/
const genActionSummariessByItemID = ({user = {}}, item_ids) => {
return Action.getActionSummaries(item_ids, user.id)
.then(util.arrayJoinBy(item_ids, 'item_id'));
};
/**
* 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) => ({
Actions: {
getByItemID: new DataLoader((ids) => genActionSummariessByItemID(context, ids)),
}
});
+63
View File
@@ -0,0 +1,63 @@
const DataLoader = require('dataloader');
const url = require('url');
const errors = require('../../errors');
const scraper = require('../../services/scraper');
const util = require('./util');
const Asset = require('../../models/asset');
/**
* Retrieves assets by an array of ids.
* @param {Object} context the context of the request
* @param {Array} ids array of ids to lookup
*/
const genAssetsByID = (context, ids) => Asset.find({
id: {
$in: ids
}
}).then(util.singleJoinBy(ids, 'id'));
/**
* This endpoint find or creates an asset at the given url when it is loaded.
* @param {Object} context the context of the request
* @param {String} asset_url the url passed in from the query
* @returns {Promise} resolves to the asset
*/
const findOrCreateAssetByURL = (context, asset_url) => {
// Verify that the asset_url is parsable.
let parsed_asset_url = url.parse(asset_url);
if (!parsed_asset_url.protocol) {
return Promise.reject(errors.ErrInvalidAssetURL);
}
return Asset.findOrCreateByUrl(asset_url)
.then((asset) => {
// If the asset wasn't scraped before, scrape it! Otherwise just return
// the asset.
if (!asset.scraped) {
return scraper.create(asset).then(() => asset);
}
return asset;
});
};
/**
* 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) => ({
Assets: {
// TODO: decide whether we want to move these to mutators or not, as in fact
// this operation create a new asset if one isn't found.
getByURL: (url) => findOrCreateAssetByURL(context, url),
getByID: new DataLoader((ids) => genAssetsByID(context, ids)),
getAll: new util.SingletonResolver(() => Asset.find({}))
}
});
+91
View File
@@ -0,0 +1,91 @@
const DataLoader = require('dataloader');
const util = require('./util');
const Action = require('../../models/action');
const Comment = require('../../models/comment');
/**
* Retrieves comments by an array of asset id's.
* @param {Array} ids array of ids to lookup
*/
const genCommentsByAssetID = (context, ids) => Comment.find({
asset_id: {
$in: ids
},
parent_id: null,
status: {
$in: [null, 'accepted']
}
}).then(util.arrayJoinBy(ids, 'asset_id'));
/**
* Retrieves comments by an array of parent ids.
* @param {Array} ids array of ids to lookup
*/
const genCommentsByParentID = (context, ids) => Comment.find({
parent_id: {
$in: ids
},
status: {
$in: [null, 'accepted']
}
}).then(util.arrayJoinBy(ids, 'parent_id'));
const getCommentsByStatusAndAssetID = (context, {status = null, asset_id = null}) => {
// TODO: remove when we move the enum over to the uppercase.
if (status) {
status = status.toLowerCase();
}
return Comment.moderationQueue(status, asset_id);
};
const getCommentsByActionTypeAndAssetID = (context, {action_type, asset_id = null}) => {
// TODO: remove when we move the enum over to the uppercase.
if (action_type) {
action_type = action_type.toLowerCase();
}
return Action.find({
action_type,
// TODO: remove when we move the enum over to the uppercase.
item_type: 'comments'
}).then((actions) => {
let comments = Comment.find({
id: {
$in: actions.map((action) => action.item_id)
}
});
if (asset_id) {
comments = comments.where({asset_id});
}
return comments;
});
};
const genCommentsByAuthorID = (context, authorIDs) => Comment.find({
author_id: {
$in: authorIDs
}
}).then(util.arrayJoinBy(authorIDs, 'author_id'));
/**
* 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) => ({
Comments: {
getByParentID: new DataLoader((ids) => genCommentsByParentID(context, ids)),
getByAssetID: new DataLoader((ids) => genCommentsByAssetID(context, ids)),
getByStatusAndAssetID: (query) => getCommentsByStatusAndAssetID(context, query),
getByActionTypeAndAssetID: (query) => getCommentsByActionTypeAndAssetID(context, query),
getByAuthorID: new DataLoader((authorIDs) => genCommentsByAuthorID(context, authorIDs))
}
});
+28
View File
@@ -0,0 +1,28 @@
const _ = require('lodash');
const Actions = require('./actions');
const Assets = require('./assets');
const Comments = require('./comments');
const Settings = require('./settings');
const Users = require('./users');
/**
* 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) => {
// We need to return an object to be accessed.
return _.merge(...[
Actions,
Assets,
Comments,
Settings,
Users
].map((loaders) => {
// Each loader is a function which takes the context.
return loaders(context);
}));
};
+12
View File
@@ -0,0 +1,12 @@
const Settings = require('../../models/setting');
const util = require('./util');
/**
* 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(() => Settings.retrieve())
});
+16
View File
@@ -0,0 +1,16 @@
const DataLoader = require('dataloader');
const User = require('../../models/user');
const genUserByIDs = (context, ids) => User.findByIdArray(ids);
/**
* 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) => ({
Users: {
getByID: new DataLoader((ids) => genUserByIDs(context, ids))
}
});
+68
View File
@@ -0,0 +1,68 @@
const _ = require('lodash');
/**
* SingletonResolver is a cached loader for a single result.
*/
class SingletonResolver {
constructor(resolver) {
this._cache = null;
this._resolver = resolver;
}
load() {
if (this._cache) {
return this._cache;
}
let promise = this._resolver(arguments).then((result) => {
return result;
});
// Set the promise on the cache.
this._cache = promise;
return promise;
}
}
/**
* This joins a set of results with a specific keys and sets an empty array in
* place if it was not found.
* @param {Array} ids ids to locate
* @param {String} key key to group by
* @return {Array} array of results
*/
const arrayJoinBy = (ids, key) => (items) => {
const itemsByKey = _.groupBy(items, key);
return ids.map((id) => {
if (id in itemsByKey) {
return itemsByKey[id];
}
return [];
});
};
/**
* This joins a set of results with a specific keys and sets null in place if it
* was not found.
* @param {Array} ids ids to locate
* @param {String} key key to group by
* @return {Array} array of results
*/
const singleJoinBy = (ids, key) => (items) => {
const itemsByKey = _.groupBy(items, key);
return ids.map((id) => {
if (id in itemsByKey) {
return itemsByKey[id][0];
}
return null;
});
};
module.exports = {
singleJoinBy,
arrayJoinBy,
SingletonResolver
};
+54
View File
@@ -0,0 +1,54 @@
const Action = require('../../models/action');
/**
* Creates an action on a item.
* @param {Object} user the user performing the request
* @param {String} item_id id of the item to add the action to
* @param {String} item_type type of the item
* @param {String} action_type type of the action
* @return {Promise} resolves to the action created
*/
const createAction = ({user = {}}, {item_id, item_type, action_type, metadata = {}}) => {
return Action.insertUserAction({
item_id,
item_type,
user_id: user.id,
action_type,
metadata
});
};
/**
* Deletes an action based on the user id if the user owns that action.
* @param {Object} user the user performing the request
* @param {String} id the id of the action to delete
* @return {Promise} resolves when the action is deleted
*/
const deleteAction = ({user}, {id}) => {
return Action.remove({
id,
user_id: user.id
});
};
module.exports = (context) => {
// TODO: refactor to something that'll return an error in the event an attempt
// is made to mutate state while not logged in. There's got to be a better way
// to do this.
if (context.user) {
return {
Action: {
create: (action) => createAction(context, action),
delete: (action) => deleteAction(context, action)
}
};
}
return {
Action: {
create: () => {},
delete: () => {}
}
};
};
@@ -1,12 +1,8 @@
/* eslint eqeqeq: ["error", "smart"]*/
const errors = require('../../errors');
const Asset = require('../../models/asset');
const Comment = require('../../models/comment');
const errors = require('../errors');
const Action = require('../models/action');
const Asset = require('../models/asset');
const Comment = require('../models/comment');
const User = require('../models/user');
const Wordlist = require('../services/wordlist');
const Wordlist = require('../../services/wordlist');
/**
* Creates a new comment.
@@ -126,7 +122,7 @@ const createPublicComment = (context, commentInput) => {
// TODO: this is kind of fragile, we should refactor this to resolve
// all these const's that we're using like 'comments', 'flag' to be
// defined in a checkable schema.
return createAction(null, {
return context.mutators.Action.createAction(null, {
item_id: comment.id,
item_type: 'comments',
action_type: 'flag',
@@ -142,47 +138,6 @@ const createPublicComment = (context, commentInput) => {
}));
};
/**
* Creates an action on a item.
* @param {Object} user the user performing the request
* @param {String} item_id id of the item to add the action to
* @param {String} item_type type of the item
* @param {String} action_type type of the action
* @return {Promise} resolves to the action created
*/
const createAction = ({user = {}}, {item_id, item_type, action_type, metadata = {}}) => {
return Action.insertUserAction({
item_id,
item_type,
user_id: user.id,
action_type,
metadata
});
};
/**
* Deletes an action based on the user id if the user owns that action.
* @param {Object} user the user performing the request
* @param {[type]} id [description]
* @return {[type]} [description]
*/
const deleteAction = ({user}, {id}) => {
return Action.remove({
id,
user_id: user.id
});
};
/**
* Updates a users settings.
* @param {Object} user the user performing the request
* @param {String} bio the new user bio
* @return {Promise}
*/
const updateUserSettings = ({user}, {bio}) => {
return User.updateSettings(user.id, {bio});
};
module.exports = (context) => {
// TODO: refactor to something that'll return an error in the event an attempt
@@ -192,13 +147,6 @@ module.exports = (context) => {
return {
Comment: {
create: (comment) => createPublicComment(context, comment)
},
Action: {
create: (action) => createAction(context, action),
delete: (action) => deleteAction(context, action)
},
User: {
updateSettings: (settings) => updateUserSettings(context, settings)
}
};
}
@@ -206,13 +154,6 @@ module.exports = (context) => {
return {
Comment: {
create: () => {}
},
Action: {
create: () => {},
delete: () => {}
},
User: {
updateSettings: () => {}
}
};
};
+19
View File
@@ -0,0 +1,19 @@
const _ = require('lodash');
const Comment = require('./comment');
const Action = require('./action');
const User = require('./user');
module.exports = (context) => {
// We need to return an object to be accessed.
return _.merge(...[
Comment,
Action,
User,
].map((mutators) => {
// Each set of mutators is a function which takes the context.
return mutators(context);
}));
};
+31
View File
@@ -0,0 +1,31 @@
const User = require('../../models/user');
/**
* Updates a users settings.
* @param {Object} user the user performing the request
* @param {String} bio the new user bio
* @return {Promise}
*/
const updateUserSettings = ({user}, {bio}) => {
return User.updateSettings(user.id, {bio});
};
module.exports = (context) => {
// TODO: refactor to something that'll return an error in the event an attempt
// is made to mutate state while not logged in. There's got to be a better way
// to do this.
if (context.user) {
return {
User: {
updateSettings: (settings) => updateUserSettings(context, settings)
}
};
}
return {
User: {
updateSettings: () => {}
}
};
};
+9 -4
View File
@@ -1,18 +1,23 @@
const Action = {
action_type({action_type}) {
// TODO: remove once we cast the data model to have uppercase action
// FIXME: remove once we cast the data model to have uppercase action
// types.
return action_type.toUpperCase();
},
item_type({item_type}) {
// TODO: remove once we cast the data model to have uppercase item
// FIXME: remove once we cast the data model to have uppercase item
// types.
return item_type.toUpperCase();
},
user({user_id}, _, {loaders}) {
return loaders.Users.getByID.load(user_id);
// This will load the user for the specific action. We'll limit this to the
// admin users only.
user({user_id}, _, {loaders, user}) {
if (user.hasRole('admin')) {
return loaders.Users.getByID.load(user_id);
}
}
};
+2 -2
View File
@@ -1,13 +1,13 @@
const ActionSummary = {
action_type({action_type}) {
// TODO: remove once we cast the data model to have uppercase action
// FIXME: remove once we cast the data model to have uppercase action
// types.
return action_type.toUpperCase();
},
item_type({item_type}) {
// TODO: remove once we cast the data model to have uppercase item
// FIXME: remove once we cast the data model to have uppercase item
// types.
return item_type.toUpperCase();
}
+4 -1
View File
@@ -6,7 +6,7 @@ const Comment = {
return loaders.Comments.getByParentID.load(id);
},
actions({id}, _, {loaders}) {
return loaders.Actions.getByID.load(id);
return loaders.Actions.getByItemID.load(id);
},
status({status}) {
@@ -14,6 +14,9 @@ const Comment = {
if (status) {
return status.toUpperCase();
}
},
asset({asset_id}, _, {loaders}) {
return loaders.Assets.getByID.load(asset_id);
}
};
+30 -7
View File
@@ -1,23 +1,46 @@
const RootQuery = {
assets(_, args, {loaders}) {
return loaders.Assets.getAll.load();
assets(_, args, {loaders, user}) {
if (user.hasRole('admin')) {
return loaders.Assets.getAll.load();
}
},
asset(_, {id = null, url}, {loaders}) {
if (id) {
asset(_, query, {loaders}) {
if (query.id) {
// TODO: we may not always have a comment stream here, therefore, when we
// load it, we may also need to create with the url. This may also have to
// move the logic over to the mutators function as an upsert operation
// possibly.
return loaders.Assets.getByID.load(id);
} else {
return loaders.Assets.getByURL(url);
return loaders.Assets.getByID.load(query.id);
}
return loaders.Assets.getByURL(query.url);
},
settings(_, args, {loaders}) {
return loaders.Settings.load();
},
// This endpoint is used for loading moderation queues, so hide it in the
// event that we aren't an admin.
comments(_, {query}, {loaders, user}) {
if (user == null || !user.hasRole('admin')) {
return null;
}
if (query.action_type) {
return loaders.Comments.getByActionTypeAndAssetID(query);
} else {
return loaders.Comments.getByStatusAndAssetID(query);
}
},
// This returns the current user, ensure that if we aren't logged in, we
// return null.
me(_, args, {user}) {
if (user == null) {
return null;
}
return user;
}
};
+10
View File
@@ -1,6 +1,16 @@
const User = {
actions({id}, _, {loaders}) {
return loaders.Actions.getByID.load(id);
},
comments({id}, _, {loaders, user}) {
// If the user is not an admin, only return comment list for the owner of
// the comments.
if (!user.hasRoles('admin') || user.id !== id) {
return null;
}
return loaders.Comments.getByAuthorID.load(id);
}
};
+65 -11
View File
@@ -1,26 +1,66 @@
// TODO: Adjust `RootQuery.asset(id: ID, url: URL)` to instead be
// `RootQuery.asset(id: ID, url: URL!)` because we'll always need the url, if
// TODO: Adjust `RootQuery.asset(id: ID, url: String)` to instead be
// `RootQuery.asset(id: ID, url: String!)` because we'll always need the url, if
// this change is done now everything will likely break on the front end.
const typeDefs = [`
interface ActionableItem {
id: ID!
}
type UserSettings {
# bio of the user.
bio: String
}
input CommentsInput {
# current status of a comment.
status: COMMENT_STATUS
# asset that a comment is on.
asset_id: ID
# action type to find comments that have an action with.
action_type: ACTION_TYPE
}
# Any person who can author comments, create actions, and view comments on a
# stream.
type User {
id: ID!
# display name of a user.
displayName: String!
# actions against a specific user.
actions: [ActionSummary]
# settings for a user.
settings: UserSettings
# returns all comments based on a query.
comments(query: CommentsInput): [Comment]
}
type Comment {
id: ID!
# the actual comment data.
body: String!
# the user who authored the comment.
user: User
# the replies that were made to the comment.
replies(limit: Int = 3): [Comment]
# the actions made against a comment.
actions: [ActionSummary]
status: String
# the asset that a comment was made on.
asset: Asset
# the current status of a comment.
status: COMMENT_STATUS
}
enum ITEM_TYPE {
@@ -34,22 +74,20 @@ enum ACTION_TYPE {
FLAG
}
interface ActionInterface {
action_type: ACTION_TYPE!
item_type: ITEM_TYPE!
}
type Action implements ActionInterface {
type Action {
id: ID!
item_id: ID!
action_type: ACTION_TYPE!
item_id: ID!
item_type: ITEM_TYPE!
item: ActionableItem
user: User!
updated_at: String
created_at: String
}
type ActionSummary implements ActionInterface {
type ActionSummary {
action_type: ACTION_TYPE!
item_type: ITEM_TYPE!
count: Int
@@ -76,10 +114,26 @@ type Asset {
closedAt: String
}
enum COMMENT_STATUS {
ACCEPTED
REJECTED
PREMOD
}
type RootQuery {
# retrieves site wide settings and defaults.
settings: Settings
# retrieves all assets.
assets: [Asset]
# retrieves a specific asset.
asset(id: ID, url: String): Asset
# retrieves comments based on the input query.
comments(query: CommentsInput): [Comment]
# retrieves the current logged in user.
me: User
}
+1 -2
View File
@@ -166,8 +166,7 @@ ActionSchema.statics.getActionSummaries = function(item_ids, current_user_id = '
current_user: '$current_user'
}
}
])
.exec();
]);
};
/*
+7
View File
@@ -169,6 +169,13 @@ UserSchema.method('filterForUser', function(user = false) {
return this.toJSON();
});
/**
* Returns true if the user has all the roles specified.
*/
UserSchema.method('hasRoles', function(...roles) {
return roles.every((role) => this.roles.indexOf(role) >= 0);
});
// Create the User model.
const UserModel = mongoose.model('User', UserSchema);