Merge branch 'master' into story-137818425-tag-staff

This commit is contained in:
gaba
2017-02-10 13:42:50 -08:00
44 changed files with 753 additions and 337 deletions
+10
View File
@@ -5,6 +5,15 @@ const util = require('./util');
const ActionsService = require('../../services/actions');
const ActionModel = require('../../models/action');
/**
* Gets actions based on their item id's.
*/
const genActionsByItemID = (_, item_ids) => {
return ActionsService
.findByItemIdArray(item_ids)
.then(util.arrayJoinBy(item_ids, 'item_id'));
};
/**
* Looks up actions based on the requested id's all bounded by the user.
* @param {Object} context the context of the request
@@ -35,6 +44,7 @@ const getItemIdsByActionTypeAndItemType = (_, action_type, item_type) => {
*/
module.exports = (context) => ({
Actions: {
getByID: new DataLoader((ids) => genActionsByItemID(context, ids)),
getSummariesByItemID: new DataLoader((ids) => genActionSummariessByItemID(context, ids)),
getByTypes: ({action_type, item_type}) => getItemIdsByActionTypeAndItemType(context, action_type, item_type)
}
+5 -3
View File
@@ -1,6 +1,7 @@
const ActionModel = require('../../models/action');
const ActionsService = require('../../services/actions');
const UsersService = require('../../services/users');
const errors = require('../../errors');
/**
* Creates an action on a item. If the item is a user flag, sets the user's status to
@@ -11,11 +12,12 @@ const UsersService = require('../../services/users');
* @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 = {}}) => {
const createAction = ({user = {}}, {item_id, item_type, action_type, group_id, metadata = {}}) => {
return ActionsService.insertUserAction({
item_id,
item_type,
user_id: user.id,
group_id,
action_type,
metadata
}).then((action) => {
@@ -58,8 +60,8 @@ module.exports = (context) => {
return {
Action: {
create: () => {},
delete: () => {}
create: () => Promise.reject(errors.ErrNotAuthorized),
delete: () => Promise.reject(errors.ErrNotAuthorized)
}
};
};
+1 -1
View File
@@ -177,7 +177,7 @@ module.exports = (context) => {
return {
Comment: {
create: () => {}
create: () => Promise.reject(errors.ErrNotAuthorized)
}
};
};
+8
View File
@@ -1,4 +1,12 @@
const Action = {
__resolveType({action_type}) {
switch (action_type) {
case 'FLAG':
return 'FlagAction';
case 'LIKE':
return 'LikeAction';
}
},
// This will load the user for the specific action. We'll limit this to the
// admin users only or the current logged in user.
+10 -1
View File
@@ -1,3 +1,12 @@
const ActionSummary = {};
const ActionSummary = {
__resolveType({action_type}) {
switch (action_type) {
case 'FLAG':
return 'FlagActionSummary';
case 'LIKE':
return 'LikeActionSummary';
}
},
};
module.exports = ActionSummary;
+10 -1
View File
@@ -16,7 +16,16 @@ const Comment = {
replyCount({id}, _, {loaders: {Comments}}) {
return Comments.countByParentID.load(id);
},
actions({id}, _, {loaders: {Actions}}) {
actions({id}, _, {user, loaders: {Actions}}) {
// Only return the actions if the user is not an admin.
if (user && user.hasRoles('ADMIN')) {
return Actions.getByID.load(id);
}
return null;
},
action_summaries({id}, _, {loaders: {Actions}}) {
return Actions.getSummariesByItemID.load(id);
},
asset({asset_id}, _, {loaders: {Assets}}) {
+9
View File
@@ -0,0 +1,9 @@
const FlagAction = {
// Stored in the metadata, extract and return.
reason({metadata: {reason}}) {
return reason;
}
};
module.exports = FlagAction;
+7
View File
@@ -0,0 +1,7 @@
const FlagActionSummary = {
reason({group_id}) {
return group_id;
}
};
module.exports = FlagActionSummary;
+3
View File
@@ -0,0 +1,3 @@
const GenericUserError = {};
module.exports = GenericUserError;
+15 -3
View File
@@ -1,21 +1,33 @@
const Action = require('./action');
const ActionSummary = require('./action_summary');
const Action = require('./action');
const Asset = require('./asset');
const Comment = require('./comment');
const Date = require('./date');
const FlagActionSummary = require('./flag_action_summary');
const FlagAction = require('./flag_action');
const GenericUserError = require('./generic_user_error');
const LikeAction = require('./like_action');
const RootMutation = require('./root_mutation');
const RootQuery = require('./root_query');
const Settings = require('./settings');
const UserError = require('./user_error');
const User = require('./user');
const ValidationUserError = require('./validation_user_error');
module.exports = {
Action,
ActionSummary,
Action,
Asset,
Comment,
Date,
FlagActionSummary,
FlagAction,
GenericUserError,
LikeAction,
RootMutation,
RootQuery,
Settings,
User
UserError,
User,
ValidationUserError,
};
+5
View File
@@ -0,0 +1,5 @@
const LikeAction = {
};
module.exports = LikeAction;
+23 -4
View File
@@ -1,12 +1,31 @@
/**
* Wraps up a promise to return an object with the resolution of the promise
* keyed at `key` or an error caught at `errors`.
*/
const wrapResponse = (key) => (promise) => {
return promise.then((value) => {
let res = {};
if (key) {
res[key] = value;
}
return res;
}).catch((err) => ({
errors: [err]
}));
};
const RootMutation = {
createComment(_, {asset_id, parent_id, body}, {mutators: {Comment}}) {
return Comment.create({asset_id, parent_id, body});
return wrapResponse('comment')(Comment.create({asset_id, parent_id, body}));
},
createAction(_, {action}, {mutators: {Action}}) {
return Action.create(action);
createLike(_, {like: {item_id, item_type}}, {mutators: {Action}}) {
return wrapResponse('like')(Action.create({item_id, item_type, action_type: 'LIKE'}));
},
createFlag(_, {flag: {item_id, item_type, reason, message}}, {mutators: {Action}}) {
return wrapResponse('flag')(Action.create({item_id, item_type, action_type: 'FLAG', group_id: reason, metadata: {message}}));
},
deleteAction(_, {id}, {mutators: {Action}}) {
return Action.delete({id});
return wrapResponse(null)(Action.delete({id}));
},
};
+9 -1
View File
@@ -1,7 +1,15 @@
const User = {
actions({id}, _, {loaders: {Actions}}) {
action_summaries({id}, _, {loaders: {Actions}}) {
return Actions.getSummariesByItemID.load(id);
},
actions({id}, _, {user, loaders: {Actions}}) {
// Only return the actions if the user is not an admin.
if (user && user.hasRoles('ADMIN')) {
return Actions.getByID.load(id);
}
},
comments({id}, _, {loaders: {Comments}, user}) {
// If the user is not an admin, only return comment list for the owner of
+11
View File
@@ -0,0 +1,11 @@
const UserError = {
__resolveType({field_name}) {
if (field_name) {
return 'ValidationUserError';
}
return 'GenericUserError';
}
};
module.exports = UserError;
+3
View File
@@ -0,0 +1,3 @@
const ValidationUserError = {};
module.exports = ValidationUserError;
+7
View File
@@ -1,8 +1,15 @@
const tools = require('graphql-tools');
const maskErrors = require('graphql-errors').maskErrors;
const resolvers = require('./resolvers');
const typeDefs = require('./typeDefs');
const schema = tools.makeExecutableSchema({typeDefs, resolvers});
if (process.env.NODE_ENV === 'production') {
// Mask errors that are thrown if we are in a production environment.
maskErrors(schema);
}
module.exports = schema;
+332 -81
View File
@@ -1,43 +1,18 @@
# Establishes the ordering of the content by their created_at time stamp.
enum SORT_ORDER {
# newest to oldest order.
REVERSE_CHRONOLOGICAL
# oldest to newer order.
CHRONOLOGICAL
}
################################################################################
## Custom Scalar Types
################################################################################
# Date represented as an ISO8601 string.
scalar Date
input CommentsQuery {
# current status of a comment.
statuses: [COMMENT_STATUS]
# asset that a comment is on.
asset_id: ID
# the parent of the comment that we want to retrieve.
parent_id: ID
# comments returned will only be ones which have at least one action of this
# type.
action_type: ACTION_TYPE
# limit the number of results to be returned.
limit: Int = 10
# filter by a specific tag name.
tag: [String]
# skip results from the last created_at timestamp.
cursor: Date
# sort the results by created_at.
sort: SORT_ORDER = REVERSE_CHRONOLOGICAL
}
################################################################################
## Users
################################################################################
# Roles that a user can have, these can be combined.
enum USER_ROLES {
# an administrator of the site
ADMIN
@@ -48,13 +23,18 @@ enum USER_ROLES {
# Any person who can author comments, create actions, and view comments on a
# stream.
type User {
# The ID of the User.
id: ID!
# display name of a user.
displayName: String!
# actions against a specific user.
actions: [ActionSummary]
# Action summaries against the user.
action_summaries: [ActionSummary]
# Actions completed on the parent.
actions: [Action]
# the current roles of the user.
roles: [USER_ROLES]
@@ -77,10 +57,71 @@ type Tag {
created_at: Date!
}
################################################################################
## Comments
################################################################################
# The statuses that a comment may have.
enum COMMENT_STATUS {
# The comment has been accepted by a moderator.
ACCEPTED
# The comment has been rejected by a moderator.
REJECTED
# The comment was created while the asset's premoderation option was on, and
# new comments that haven't been moderated yet are referred to as
# "premoderated" or "premod" comments.
PREMOD
}
# The types of action there are as enum's.
enum ACTION_TYPE {
# Represents a LikeAction.
LIKE
# Represents a FlagAction.
FLAG
}
# CommentsQuery allows the ability to query comments by a specific methods.
input CommentsQuery {
# current status of a comment.
statuses: [COMMENT_STATUS]
# asset that a comment is on.
asset_id: ID
# the parent of the comment that we want to retrieve.
parent_id: ID
# comments returned will only be ones which have at least one action of this
# type.
action_type: ACTION_TYPE
# limit the number of results to be returned.
limit: Int = 10
# skip results from the last created_at timestamp.
cursor: Date
# filter by a specific tag name.
tag: [String]
# sort the results by created_at.
sort: SORT_ORDER = REVERSE_CHRONOLOGICAL
}
# Comment is the base representation of user interaction in Talk.
type Comment {
# The ID of the comment.
id: ID!
# the actual comment data.
# The actual comment data.
body: String!
# the tags on the comment
@@ -95,68 +136,153 @@ type Comment {
# the replies that were made to the comment.
replies(sort: SORT_ORDER = CHRONOLOGICAL, limit: Int = 3): [Comment]
# the count of replies on a comment
# The count of replies on a comment.
replyCount: Int
# the actions made against a comment.
actions: [ActionSummary]
# Actions completed on the parent.
actions: [Action]
# the asset that a comment was made on.
# Action summaries against a comment.
action_summaries: [ActionSummary]
# The asset that a comment was made on.
asset: Asset
# the current status of a comment.
# The current status of a comment.
status: COMMENT_STATUS
# the time when the comment was created
# The time when the comment was created
created_at: Date!
}
enum ITEM_TYPE {
ASSETS
COMMENTS
USERS
}
################################################################################
## Actions
################################################################################
enum ACTION_TYPE {
LIKE
FLAG
}
# An action rendered against a parent enity item.
interface Action {
type Action {
# The ID of the action.
id: ID!
action_type: ACTION_TYPE!
item_id: ID!
item_type: ITEM_TYPE!
# The author of the action.
user: User
user: User!
# The time when the Action was updated.
updated_at: Date
# The time when the Action was created.
created_at: Date
}
type ActionSummary {
action_type: ACTION_TYPE!
item_type: ITEM_TYPE!
# A summary of actions based on the specific grouping of the group_id.
interface ActionSummary {
# The count of actions with this group.
count: Int
# The current user's action.
current_user: Action
}
# LikeAction is used by users who "like" a specific entity.
type LikeAction implements Action {
# The ID of the action.
id: ID!
# The author of the action.
user: User
# The time when the Action was updated.
updated_at: Date
# The time when the Action was created.
created_at: Date
}
# LikeActionSummary is counts the amount of "likes" that a specific entity has.
type LikeActionSummary implements ActionSummary {
# The count of likes against the parent entity.
count: Int!
current_user: LikeAction
}
# A FLAG action that contains flag metadata.
type FlagAction implements Action {
# The ID of the Flag Action.
id: ID!
# The reason for which the Flag Action was created.
reason: String
# An optional message sent with the flagging action by the user.
message: String
# The user who created the action.
user: User
# The time when the Flag Action was updated.
updated_at: Date
# The time when the Flag Action was created.
created_at: Date
}
# Summary for Flag Action with a a unique reason.
type FlagActionSummary implements ActionSummary {
# The total count of flags with this reason.
count: Int!
# The reason for which the Flag Action was created.
reason: String
# The flag by the current user against the parent entity with this reason.
current_user: FlagAction
}
################################################################################
## Settings
################################################################################
# The moderation mode of the site.
enum MODERATION_MODE {
# Comments posted while in `PRE` mode will be labeled with a `PREMOD`
# status and will require a moderator decision before being visible.
PRE
# Comments posted while in `POST` will be visible immediately.
POST
}
# Site wide global settings.
type Settings {
# Moderation mode for the site.
moderation: MODERATION_MODE!
# Enables a requirement for email confirmation before a user can login.
requireEmailConfirmation: Boolean
infoBoxEnable: Boolean
infoBoxContent: String
closeTimeout: Int
closedMessage: String
charCountEnable: Boolean
charCount: Int
requireEmailConfirmation: Boolean
}
################################################################################
## Assets
################################################################################
# Where comments are made on.
type Asset {
# The current ID of the asset.
@@ -188,53 +314,178 @@ type Asset {
created_at: Date
}
enum COMMENT_STATUS {
ACCEPTED
REJECTED
PREMOD
################################################################################
## Errors
################################################################################
# Any error rendered due to the user's input.
interface UserError {
# Translation key relating to a translatable string containing details to be
# displayed to the end user.
translation_key: String!
}
# A generic error not related to validation reasons.
type GenericUserError implements UserError {
# Translation key relating to a translatable string containing details to be
# displayed to the end user.
translation_key: String!
}
# A validation error that affects the input.
type ValidationUserError implements UserError {
# Translation key relating to a translatable string containing details to be
# displayed to the end user.
translation_key: String!
# The field in question that caused the error.
field_name: String!
}
################################################################################
## Queries
################################################################################
# Establishes the ordering of the content by their created_at time stamp.
enum SORT_ORDER {
# newest to oldest order.
REVERSE_CHRONOLOGICAL
# oldest to newer order.
CHRONOLOGICAL
}
# All queries that can be executed.
type RootQuery {
# retrieves site wide settings and defaults.
# Site wide settings and defaults.
settings: Settings
# retrieves all assets.
# All assets.
assets: [Asset]
# retrieves a specific asset.
# Find or create an asset by url, or just find with the ID.
asset(id: ID, url: String): Asset
# retrieves comments based on the input query.
# Comments returned based on a query.
comments(query: CommentsQuery!): [Comment]
# retrieves the current logged in user.
# The currently logged in user based on the request.
me: User
}
input CreateActionInput {
# the type of action.
action_type: ACTION_TYPE!
################################################################################
## Mutations
################################################################################
# the type of the item.
item_type: ITEM_TYPE!
# Response defines what can be expected from any response to a mutation action.
interface Response {
# the id of the item that is related to the action.
# An array of errors relating to the mutation that occured.
errors: [UserError]
}
# CreateCommentResponse is returned with the comment that was created and any
# errors that may have occured in the attempt to create it.
type CreateCommentResponse implements Response {
# The comment that was created.
comment: Comment
# An array of errors relating to the mutation that occured.
errors: [UserError]
}
# Used to represent the item type for an action.
enum ACTION_ITEM_TYPE {
# The action references a entity of type Asset.
ASSETS
# The action references a entity of type Comment.
COMMENTS
# The action references a entity of type User.
USERS
}
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!
}
type CreateLikeResponse implements Response {
# The like that was created.
like: LikeAction
# An array of errors relating to the mutation that occured.
errors: [UserError]
}
input CreateFlagInput {
# The item's id for which we are to create a flag.
item_id: ID!
# The type of the item for which we are to create the flag.
item_type: ACTION_ITEM_TYPE!
# The reason for flagging the item.
reason: String!
# An optional message sent with the flagging action by the user.
message: String
}
# CreateFlagResponse is the response returned with possibly some errors
# relating to the creating the flag action attempt and possibly the flag that
# was created.
type CreateFlagResponse implements Response {
# The like that was created.
flag: FlagAction
# An array of errors relating to the mutation that occured.
errors: [UserError]
}
# DeleteActionResponse is the response returned with possibly some errors
# relating to the delete action attempt.
type DeleteActionResponse implements Response {
# An array of errors relating to the mutation that occured.
errors: [UserError]
}
# All mutations for the application are defined on this object.
type RootMutation {
# creates a comment on the asset.
createComment(asset_id: ID!, parent_id: ID, body: String!): Comment
# creates an action based on an input.
createAction(action: CreateActionInput!): Action
# Creates a comment on the asset.
createComment(asset_id: ID!, parent_id: ID, body: String!): CreateCommentResponse
# delete an action based on the action id.
deleteAction(id: ID!): Boolean
# Creates a like on an entity.
createLike(like: CreateLikeInput!): CreateLikeResponse
# Creates a flag on an entity.
createFlag(flag: CreateFlagInput!): CreateFlagResponse
# Delete an action based on the action id.
deleteAction(id: ID!): DeleteActionResponse
}
################################################################################
## Schema
################################################################################
schema {
query: RootQuery
mutation: RootMutation