mirror of
https://github.com/wassname/talk.git
synced 2026-08-20 12:50:41 +08:00
modularized mutators, loaders
This commit is contained in:
+1
-1
@@ -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],
|
||||
|
||||
@@ -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,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();
|
||||
|
||||
@@ -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'));
|
||||
|
||||
@@ -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
@@ -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)
|
||||
})
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
@@ -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)),
|
||||
}
|
||||
});
|
||||
@@ -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({}))
|
||||
}
|
||||
});
|
||||
@@ -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))
|
||||
}
|
||||
});
|
||||
@@ -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);
|
||||
}));
|
||||
};
|
||||
@@ -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())
|
||||
});
|
||||
@@ -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))
|
||||
}
|
||||
});
|
||||
@@ -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
|
||||
};
|
||||
@@ -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: () => {}
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -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);
|
||||
}));
|
||||
};
|
||||
@@ -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: () => {}
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
@@ -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
@@ -166,8 +166,7 @@ ActionSchema.statics.getActionSummaries = function(item_ids, current_user_id = '
|
||||
current_user: '$current_user'
|
||||
}
|
||||
}
|
||||
])
|
||||
.exec();
|
||||
]);
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user