diff --git a/client/coral-embed-stream/src/containers/Stream.js b/client/coral-embed-stream/src/containers/Stream.js
index f6e1fd9ec..32b9c33c7 100644
--- a/client/coral-embed-stream/src/containers/Stream.js
+++ b/client/coral-embed-stream/src/containers/Stream.js
@@ -83,7 +83,7 @@ class StreamContainer extends React.Component {
}
});
- this.subscriptions.push(sub1, sub2);
+ this.subscriptions = [sub1, sub2];
}
unsubscribe() {
diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js
index e212af99b..eaaee3034 100644
--- a/client/coral-framework/actions/auth.js
+++ b/client/coral-framework/actions/auth.js
@@ -311,11 +311,11 @@ export const checkLogin = () => (dispatch) => {
throw new Error('Not logged in');
}
- dispatch(checkLoginSuccess(result.user));
-
// Reset the websocket.
resetWebsocket();
+ dispatch(checkLoginSuccess(result.user));
+
// Display create username dialog if necessary.
if (result.user.canEditName && result.user.status !== 'BANNED') {
dispatch(showCreateUsernameDialog());
diff --git a/client/coral-framework/hocs/index.js b/client/coral-framework/hocs/index.js
index ef4c2ac74..01b3daff4 100644
--- a/client/coral-framework/hocs/index.js
+++ b/client/coral-framework/hocs/index.js
@@ -1,5 +1,4 @@
export {default as withFragments} from './withFragments';
export {default as withMutation} from './withMutation';
export {default as withQuery} from './withQuery';
-export {default as withReaction} from './withReaction';
diff --git a/client/coral-framework/hocs/withReaction.js b/client/coral-framework/hocs/withReaction.js
deleted file mode 100644
index ca373b6bd..000000000
--- a/client/coral-framework/hocs/withReaction.js
+++ /dev/null
@@ -1,240 +0,0 @@
-import React from 'react';
-import get from 'lodash/get';
-import uuid from 'uuid/v4';
-import {connect} from 'react-redux';
-import {bindActionCreators} from 'redux';
-import {getDisplayName} from '../helpers/hoc';
-import {compose, gql, graphql} from 'react-apollo';
-import withFragments from 'coral-framework/hocs/withFragments';
-import {showSignInDialog} from 'coral-framework/actions/auth';
-import {capitalize} from 'coral-framework/helpers/strings';
-import {getMyActionSummary, getTotalActionCount} from 'coral-framework/utils';
-
-export default (reaction) => (WrappedComponent) => {
- if (typeof reaction !== 'string') {
- console.error('Reaction must be a valid string');
- return null;
- }
-
- reaction = reaction.toLowerCase();
-
- class WithReactions extends React.Component {
- render() {
- const {comment} = this.props;
-
- const reactionSummary = getMyActionSummary(
- `${capitalize(reaction)}ActionSummary`,
- comment
- );
-
- const count = getTotalActionCount(
- `${capitalize(reaction)}ActionSummary`,
- comment
- );
-
- const alreadyReacted = () => !!reactionSummary;
-
- const withReactionProps = {reactionSummary, count, alreadyReacted};
-
- return ;
- }
- }
-
- const isReaction = (a) =>
- a.__typename === `${capitalize(reaction)}ActionSummary`;
-
- const COMMENT_FRAGMENT = gql`
- fragment ${capitalize(reaction)}Button_updateFragment on Comment {
- action_summaries {
- ... on ${capitalize(reaction)}ActionSummary {
- count
- current_user {
- id
- }
- }
- }
- }
- `;
-
- const withDeleteReaction = graphql(
- gql`
- mutation deleteReaction($id: ID!) {
- deleteAction(id:$id) {
- errors {
- translation_key
- }
- }
- }
- `,
- {
- props: ({mutate, ownProps}) => ({
- deleteReaction: () => {
-
- const reactionSummary = getMyActionSummary(
- `${capitalize(reaction)}ActionSummary`,
- ownProps.comment
- );
-
- const reactionData = {
- id: reactionSummary.current_user.id,
- commentId: ownProps.comment.id
- };
-
- return mutate({
- variables: {id: reactionData.id},
- optimisticResponse: {
- deleteAction: {
- __typename: 'DeleteActionResponse',
- errors: null
- }
- },
- update: (proxy) => {
- const fragmentId = `Comment_${reactionData.commentId}`;
-
- // Read the data from our cache for this query.
- const data = proxy.readFragment({
- fragment: COMMENT_FRAGMENT,
- id: fragmentId
- });
-
- // Check whether we liked this comment.
- const idx = data.action_summaries.findIndex(isReaction);
- if (
- idx < 0 ||
- get(data.action_summaries[idx], 'current_user.id') !== reactionData.id
- ) {
- return;
- }
-
- data.action_summaries[idx] = {
- ...data.action_summaries[idx],
- count: data.action_summaries[idx].count - 1,
- current_user: null
- };
-
- // Write our data back to the cache.
- proxy.writeFragment({
- fragment: COMMENT_FRAGMENT,
- id: fragmentId,
- data
- });
- }
- });
- }
- })
- }
- );
-
- const withPostReaction = graphql(
- gql`
- mutation create${capitalize(reaction)}($${reaction}: Create${capitalize(reaction)}Input!) {
- create${capitalize(reaction)}(${reaction}: $${reaction}) {
- ${reaction} {
- id
- }
- errors {
- translation_key
- }
- }
- }
- `,
- {
- props: ({mutate, ownProps}) => ({
- postReaction: () => {
-
- const reactionData = {
- item_id: ownProps.comment.id,
- item_type: 'COMMENTS'
- };
-
- return mutate({
- variables: {[reaction]: reactionData},
- optimisticResponse: {
- [`create${capitalize(reaction)}`]: {
- __typename: `Create${capitalize(reaction)}Response`,
- errors: null,
- [reaction]: {
- __typename: `${capitalize(reaction)}Action`,
- id: uuid()
- }
- }
- },
- update: (proxy, mutationResult) => {
- const fragmentId = `Comment_${reactionData.item_id}`;
-
- // Read the data from our cache for this query.
- const data = proxy.readFragment({
- fragment: COMMENT_FRAGMENT,
- id: fragmentId
- });
-
- // Add our comment from the mutation to the end.
- let idx = data.action_summaries.findIndex(isReaction);
-
- // Check whether we already reactioned this comment.
- if (idx >= 0 && data.action_summaries[idx].current_user) {
- return;
- }
-
- if (idx < 0) {
-
- // Add initial action when it doesn't exist.
- data.action_summaries.push({
- __typename: `${capitalize(reaction)}ActionSummary`,
- count: 0,
- current_user: null
- });
- idx = data.action_summaries.length - 1;
- }
-
- data.action_summaries[idx] = {
- ...data.action_summaries[idx],
- count: data.action_summaries[idx].count + 1,
- current_user: mutationResult.data[
- `create${capitalize(reaction)}`
- ][reaction]
- };
-
- // Write our data back to the cache.
- proxy.writeFragment({
- fragment: COMMENT_FRAGMENT,
- id: fragmentId,
- data
- });
- }
- });
- }
- })
- }
- );
-
- const mapStateToProps = (state) => ({
- user: state.auth.toJS().user,
- });
-
- const mapDispatchToProps = (dispatch) =>
- bindActionCreators({showSignInDialog}, dispatch);
-
- const enhance = compose(
- withFragments({
- comment: gql`
- fragment ${capitalize(reaction)}Button_comment on Comment {
- action_summaries {
- ... on ${capitalize(reaction)}ActionSummary {
- count
- current_user {
- id
- }
- }
- }
- }`
- }),
- connect(mapStateToProps, mapDispatchToProps),
- withDeleteReaction,
- withPostReaction
- );
-
- WithReactions.displayName = `WithReactions(${getDisplayName(WrappedComponent)})`;
-
- return enhance(WithReactions);
-};
diff --git a/client/coral-framework/services/client.js b/client/coral-framework/services/client.js
index 70408248c..26d8e391f 100644
--- a/client/coral-framework/services/client.js
+++ b/client/coral-framework/services/client.js
@@ -1,7 +1,7 @@
import ApolloClient, {addTypename} from 'apollo-client';
import {networkInterface} from './transport';
import {SubscriptionClient, addGraphQLSubscriptions} from 'subscriptions-transport-ws';
-import {SUBSCRIPTION_END} from 'subscriptions-transport-ws/dist/messageTypes';
+import MessageTypes from 'subscriptions-transport-ws/dist/message-types';
import {getAuthToken} from '../helpers/request';
let client, wsClient = null, wsClientToken = null;
@@ -13,18 +13,17 @@ export function resetWebsocket() {
return;
}
- // Unsubscribe from all the active subscriptions.
- Object.keys(wsClient.subscriptions).forEach((id) => {
-
- // Create the message.
- let message = {id: parseInt(id), type: SUBSCRIPTION_END};
-
- // Send the unsubscribe message.
- wsClient.client.send(JSON.stringify(message));
- });
-
- // Close the client, this will trigger a reconnect.
+ // Close socket connection which will also unregister subscriptions on the server-side.
wsClient.close();
+
+ // Reconnect to the server.
+ wsClient.connect();
+
+ // Reregister all subscriptions (uses non public api).
+ // See: https://github.com/apollographql/subscriptions-transport-ws/issues/171
+ Object.keys(wsClient.operations).forEach((id) => {
+ wsClient.sendMessage(id, MessageTypes.GQL_START, wsClient.operations[id].options);
+ });
}
export function getClient() {
@@ -35,6 +34,7 @@ export function getClient() {
const protocol = location.protocol === 'https:' ? 'wss' : 'ws';
wsClient = new SubscriptionClient(`${protocol}://${location.host}/api/v1/live`, {
reconnect: true,
+ lazy: true,
connectionParams: {
get token() {
diff --git a/graph/mutators/action.js b/graph/mutators/action.js
index 0d1cd49f8..de9efea1a 100644
--- a/graph/mutators/action.js
+++ b/graph/mutators/action.js
@@ -39,7 +39,7 @@ const createAction = async ({user = {}}, {item_id, item_type, action_type, group
* @return {Promise} resolves when the action is deleted
*/
const deleteAction = ({user}, {id}) => {
- return ActionModel.remove({
+ return ActionModel.findOneAndRemove({
id,
user_id: user.id
});
diff --git a/graph/resolvers/action.js b/graph/resolvers/action.js
index 8ca05daf8..ce89b9d5c 100644
--- a/graph/resolvers/action.js
+++ b/graph/resolvers/action.js
@@ -13,6 +13,7 @@ const Action = {
// This will load the user for the specific action. We'll limit this to the
// admin users only or the current logged in user.
user({user_id}, _, {loaders: {Users}, user}) {
+ console.log(user);
if (user && (user.can(SEARCH_OTHER_USERS) || user_id === user.id)) {
return Users.getByID.load(user_id);
}
diff --git a/graph/subscriptions.js b/graph/subscriptions.js
index 0cc37e841..3a61b6b39 100644
--- a/graph/subscriptions.js
+++ b/graph/subscriptions.js
@@ -50,7 +50,7 @@ const createSubscriptionManager = (server) => new SubscriptionServer({
connection.upgradeReq.headers['authorization'] = `Bearer ${token}`;
}
},
- onSubscribe: (parsedMessage, baseParams, connection) => {
+ onOperation: (parsedMessage, baseParams, connection) => {
// Cache the upgrade request.
let upgradeReq = connection.upgradeReq;
@@ -58,15 +58,15 @@ const createSubscriptionManager = (server) => new SubscriptionServer({
// Attach the context per request.
baseParams.context = async () => {
let req;
-
+
try {
req = await deserializeUser(upgradeReq);
} catch (e) {
- console.error(e);
+ console.error(connection);
return new Context({}, pubsub);
}
-
+
return new Context(req, pubsub);
};
diff --git a/package.json b/package.json
index d78ba8713..f623b94ed 100644
--- a/package.json
+++ b/package.json
@@ -81,7 +81,7 @@
"graphql-errors": "^2.1.0",
"graphql-redis-subscriptions": "^1.1.5",
"graphql-server-express": "^0.6.0",
- "graphql-subscriptions": "^0.3.1",
+ "graphql-subscriptions": "^0.4.3",
"graphql-tools": "^0.10.1",
"helmet": "^3.5.0",
"immutability-helper": "^2.2.0",
@@ -116,7 +116,7 @@
"semver": "^5.3.0",
"simplemde": "^1.11.2",
"snake-case": "^2.1.0",
- "subscriptions-transport-ws": "^0.5.5-alpha.0",
+ "subscriptions-transport-ws": "^0.7.1",
"timekeeper": "^1.0.0",
"uuid": "^3.0.1",
"yaml-loader": "^0.4.0",
diff --git a/plugin-api/beta/client/hocs/index.js b/plugin-api/beta/client/hocs/index.js
index 7ec875656..7be682670 100644
--- a/plugin-api/beta/client/hocs/index.js
+++ b/plugin-api/beta/client/hocs/index.js
@@ -1 +1 @@
-export {withReaction} from 'coral-framework/hocs';
+export {default as withReaction} from './withReaction';
diff --git a/plugin-api/beta/client/hocs/withReaction.js b/plugin-api/beta/client/hocs/withReaction.js
new file mode 100644
index 000000000..67a042081
--- /dev/null
+++ b/plugin-api/beta/client/hocs/withReaction.js
@@ -0,0 +1,337 @@
+import React from 'react';
+import get from 'lodash/get';
+import uuid from 'uuid/v4';
+import {connect} from 'react-redux';
+import {bindActionCreators} from 'redux';
+import {getDisplayName} from 'coral-framework/helpers/hoc';
+import {compose, gql, graphql} from 'react-apollo';
+import withFragments from 'coral-framework/hocs/withFragments';
+import {showSignInDialog} from 'coral-framework/actions/auth';
+import {capitalize} from 'coral-framework/helpers/strings';
+import {getMyActionSummary, getTotalActionCount} from 'coral-framework/utils';
+import * as PropTypes from 'prop-types';
+
+export default (reaction) => (WrappedComponent) => {
+ if (typeof reaction !== 'string') {
+ console.error('Reaction must be a valid string');
+ return null;
+ }
+
+ let instances = 0;
+ let createdSubscription = null;
+ let deletedSubscription = null;
+
+ reaction = reaction.toLowerCase();
+ const Reaction = capitalize(reaction);
+
+ const COMMENT_FRAGMENT = gql`
+ fragment ${Reaction}Button_updateFragment on Comment {
+ action_summaries {
+ ... on ${Reaction}ActionSummary {
+ count
+ current_user {
+ id
+ }
+ }
+ }
+ }
+ `;
+
+ const isReaction = (a) =>
+ a.__typename === `${Reaction}ActionSummary`;
+
+ const addReactionToStore = (proxy, {action, self}) => {
+ const fragmentId = `Comment_${action.item_id}`;
+
+ // Read the data from our cache for this query.
+ const data = proxy.readFragment({
+ fragment: COMMENT_FRAGMENT,
+ id: fragmentId
+ });
+
+ // Add our comment from the mutation to the end.
+ let idx = data.action_summaries.findIndex(isReaction);
+
+ // Check whether we already reactioned this comment.
+ if (self && idx >= 0 && data.action_summaries[idx].current_user) {
+ return;
+ }
+
+ if (idx < 0) {
+
+ // Add initial action when it doesn't exist.
+ data.action_summaries.push({
+ __typename: `${Reaction}ActionSummary`,
+ count: 0,
+ current_user: null
+ });
+ idx = data.action_summaries.length - 1;
+ }
+
+ data.action_summaries[idx] = {
+ ...data.action_summaries[idx],
+ count: data.action_summaries[idx].count + 1,
+ current_user: self ? action : data.action_summaries[idx].current_user
+ };
+
+ // Write our data back to the cache.
+ proxy.writeFragment({
+ fragment: COMMENT_FRAGMENT,
+ id: fragmentId,
+ data
+ });
+ };
+
+ const deleteReactionFromStore = (proxy, {action, self}) => {
+ const fragmentId = `Comment_${action.item_id}`;
+
+ // Read the data from our cache for this query.
+ const data = proxy.readFragment({
+ fragment: COMMENT_FRAGMENT,
+ id: fragmentId
+ });
+
+ // Check whether we liked this comment.
+ const idx = data.action_summaries.findIndex(isReaction);
+
+ if (
+ self &&
+ (idx < 0 || get(data.action_summaries[idx], 'current_user.id') !== action.id)
+ ) {
+ return;
+ }
+
+ data.action_summaries[idx] = {
+ ...data.action_summaries[idx],
+ count: data.action_summaries[idx].count - 1,
+ current_user: self ? null : data.action_summaries[idx].current_user,
+ };
+
+ // Write our data back to the cache.
+ proxy.writeFragment({
+ fragment: COMMENT_FRAGMENT,
+ id: fragmentId,
+ data
+ });
+ };
+
+ const REACTION_CREATED_SUBSCRIPTION = gql`
+ subscription ${Reaction}ActionCreated($assetId: ID!) {
+ ${reaction}ActionCreated(asset_id: $assetId) {
+ id
+ user {
+ id
+ }
+ item_id
+ }
+ }
+ `;
+
+ const REACTION_DELETED_SUBSCRIPTION = gql`
+ subscription ${Reaction}ActionDeleted($assetId: ID!) {
+ ${reaction}ActionDeleted(asset_id: $assetId) {
+ id
+ user {
+ id
+ }
+ item_id
+ }
+ }
+ `;
+
+ class WithReactions extends React.Component {
+
+ static contextTypes = {
+ client: PropTypes.object.isRequired,
+ };
+
+ constructor(props, context) {
+ super(props, context);
+ if (instances === 0) {
+ createdSubscription = context.client.subscribe({
+ query: REACTION_CREATED_SUBSCRIPTION,
+ variables: {
+ assetId: this.props.root.asset.id,
+ },
+ }).subscribe({
+ next: this.onReactionCreated,
+ error(err) { console.error('err', err); },
+ });
+
+ deletedSubscription = context.client.subscribe({
+ query: REACTION_DELETED_SUBSCRIPTION,
+ variables: {
+ assetId: this.props.root.asset.id,
+ },
+ }).subscribe({
+ next: this.onReactionDeleted,
+ error(err) { console.error('err', err); },
+ });
+ }
+ instances++;
+ }
+
+ onReactionCreated = ({[`${reaction}ActionCreated`]: action}) => {
+ if (this.props.user && action.user && this.props.user.id === action.user.id) {
+ return;
+ }
+ addReactionToStore(this.context.client, {action, self: false});
+ };
+
+ onReactionDeleted = ({[`${reaction}ActionDeleted`]: action}) => {
+ if (this.props.user && action.user && this.props.user.id === action.user.id) {
+ return;
+ }
+ deleteReactionFromStore(this.context.client, {action, self: false});
+ };
+
+ componentWillUnmount() {
+ instances--;
+ if (instances === 0) {
+ try {
+ createdSubscription.unsubscribe();
+ deletedSubscription.unsubscribe();
+ }
+ catch(e) {
+ console.warn(e);
+ }
+ }
+ }
+
+ render() {
+ const {comment} = this.props;
+
+ const reactionSummary = getMyActionSummary(
+ `${Reaction}ActionSummary`,
+ comment
+ );
+
+ const count = getTotalActionCount(
+ `${Reaction}ActionSummary`,
+ comment
+ );
+
+ const alreadyReacted = !!reactionSummary;
+
+ const withReactionProps = {reactionSummary, count, alreadyReacted};
+
+ return ;
+ }
+ }
+
+ const withDeleteReaction = graphql(
+ gql`
+ mutation Delete${Reaction}Action($input: Delete${Reaction}ActionInput!) {
+ delete${Reaction}Action(input: $input) {
+ errors {
+ translation_key
+ }
+ }
+ }
+ `,
+ {
+ props: ({mutate, ownProps}) => ({
+ deleteReaction: () => {
+
+ const reactionSummary = getMyActionSummary(
+ `${Reaction}ActionSummary`,
+ ownProps.comment
+ );
+
+ const id = reactionSummary.current_user.id;
+ const item_id = ownProps.comment.id;
+
+ const input = {id};
+ return mutate({
+ variables: {input},
+ optimisticResponse: {
+ [`delete${Reaction}Action`]: {
+ __typename: `Delete${Reaction}ActionResponse`,
+ errors: null
+ }
+ },
+ update: (proxy) => {
+ deleteReactionFromStore(proxy, {action: {item_id, id}, self: true});
+ }
+ });
+ }
+ })
+ }
+ );
+
+ const withPostReaction = graphql(
+ gql`
+ mutation Create${Reaction}Action($input: Create${Reaction}ActionInput!) {
+ create${Reaction}Action(input: $input) {
+ ${reaction} {
+ id
+ }
+ errors {
+ translation_key
+ }
+ }
+ }
+ `,
+ {
+ props: ({mutate, ownProps}) => ({
+ postReaction: () => {
+
+ const input = {
+ item_id: ownProps.comment.id,
+ };
+
+ return mutate({
+ variables: {input},
+ optimisticResponse: {
+ [`create${Reaction}Action`]: {
+ __typename: `Create${Reaction}ActionResponse`,
+ errors: null,
+ [reaction]: {
+ __typename: `${Reaction}Action`,
+ id: uuid()
+ }
+ }
+ },
+ update: (proxy, {data: {[`create${Reaction}Action`]: {[reaction]: action}}}) => {
+ const a = {
+ ...action,
+ item_id: input.item_id,
+ };
+ addReactionToStore(proxy, {action: a, self: true});
+ }
+ });
+ }
+ })
+ }
+ );
+
+ const mapStateToProps = (state) => ({
+ user: state.auth.toJS().user,
+ });
+
+ const mapDispatchToProps = (dispatch) =>
+ bindActionCreators({showSignInDialog}, dispatch);
+
+ const enhance = compose(
+ withFragments({
+ comment: gql`
+ fragment ${Reaction}Button_comment on Comment {
+ action_summaries {
+ ... on ${Reaction}ActionSummary {
+ count
+ current_user {
+ id
+ }
+ }
+ }
+ }`
+ }),
+ connect(mapStateToProps, mapDispatchToProps),
+ withDeleteReaction,
+ withPostReaction
+ );
+
+ WithReactions.displayName = `WithReactions(${getDisplayName(WrappedComponent)})`;
+
+ return enhance(WithReactions);
+};
diff --git a/plugin-api/beta/server/getReactionConfig.js b/plugin-api/beta/server/getReactionConfig.js
index 101c86782..b157742ad 100644
--- a/plugin-api/beta/server/getReactionConfig.js
+++ b/plugin-api/beta/server/getReactionConfig.js
@@ -1,4 +1,5 @@
const wrapResponse = require('../../../graph/helpers/response');
+const {SEARCH_OTHER_USERS} = require('../../../perms/constants');
function getReactionConfig(reaction) {
const Reaction = reaction.charAt(0).toUpperCase() + reaction.slice(1);
@@ -6,82 +7,142 @@ function getReactionConfig(reaction) {
const typeDefs = `
enum ACTION_TYPE {
- # Represents a ${Reaction}.
- ${REACTION}
+ # Represents a ${Reaction}.
+ ${REACTION}
}
enum ASSET_METRICS_SORT {
- # Represents a ${Reaction}Action.
- ${REACTION}
+ # Represents a ${Reaction}Action.
+ ${REACTION}
}
- input Create${Reaction}Input {
+ input Create${Reaction}ActionInput {
- # The item's id for which we are to create a ${reaction}.
- item_id: ID!
+ # The item's id for which we are to create a ${reaction}.
+ item_id: ID!
+ }
- # The type of the item for which we are to create the ${reaction}.
- item_type: ACTION_ITEM_TYPE!
+ input Delete${Reaction}ActionInput {
+
+ # The item's id for which we are deleting a ${reaction}.
+ id: ID!
}
# ${Reaction}Action is used by users who "${reaction}" a specific entity.
type ${Reaction}Action implements Action {
- # The ID of the action.
- id: ID!
+ # The ID of the action.
+ id: ID!
- # The author of the action.
- user: User
+ # The author of the action.
+ user: User
- # The time when the Action was updated.
- updated_at: Date
+ # The time when the Action was updated.
+ updated_at: Date
- # The time when the Action was created.
- created_at: Date
+ # The time when the Action was created.
+ created_at: Date
+
+ # The item's id for which the Action was created.
+ item_id: ID!
}
type ${Reaction}ActionSummary implements ActionSummary {
- # The count of actions with this group.
- count: Int
+ # The count of actions with this group.
+ count: Int
- # The current user's action.
- current_user: ${Reaction}Action
+ # The current user's action.
+ current_user: ${Reaction}Action
}
# A summary of counts related to all the ${Reaction}s on an Asset.
type ${Reaction}AssetActionSummary implements AssetActionSummary {
- # Number of ${reaction}s associated with actionable types on this this Asset.
- actionCount: Int
+ # Number of ${reaction}s associated with actionable types on this this Asset.
+ actionCount: Int
- # Number of unique actionable types that are referenced by the ${reaction}s.
- actionableItemCount: Int
+ # Number of unique actionable types that are referenced by the ${reaction}s.
+ actionableItemCount: Int
}
- type Create${Reaction}Response implements Response {
+ type Create${Reaction}ActionResponse implements Response {
- # The ${reaction} that was created.
- ${reaction}: ${Reaction}Action
+ # The ${reaction} that was created.
+ ${reaction}: ${Reaction}Action
- # An array of errors relating to the mutation that occurred.
- errors: [UserError!]
+ # An array of errors relating to the mutation that occurred.
+ errors: [UserError!]
+ }
+
+ type Delete${Reaction}ActionResponse implements Response {
+
+ # The ${reaction} that was created.
+ ${reaction}: ${Reaction}Action
+
+ # An array of errors relating to the mutation that occurred.
+ errors: [UserError!]
}
type RootMutation {
- # Creates a ${reaction} on an entity.
- create${Reaction}(${reaction}: Create${Reaction}Input!): Create${Reaction}Response
+ # Creates a ${reaction} on an entity.
+ create${Reaction}Action(input: Create${Reaction}ActionInput!): Create${Reaction}ActionResponse
+ delete${Reaction}Action(input: Delete${Reaction}ActionInput!): Delete${Reaction}ActionResponse
+ }
+
+ type Subscription {
+
+ # Subscribe to ${reaction}s.
+ ${reaction}ActionCreated(asset_id: ID!): ${Reaction}Action
+
+ # Subscribe to ${reaction} removals.
+ ${reaction}ActionDeleted(asset_id: ID!): ${Reaction}Action
}
`;
return {
typeDefs,
resolvers: {
+ Subscription: {
+ [`${reaction}ActionCreated`]: ({action}) => {
+ return action;
+ },
+ [`${reaction}ActionDeleted`]: ({action}) => {
+ return action;
+ },
+ },
+ [`${Reaction}Action`]: {
+
+ // This will load the user for the specific action. We'll limit this to the
+ // admin users only or the current logged in user.
+ user({user_id}, _, {loaders: {Users}, user}) {
+ if (user && (user.can(SEARCH_OTHER_USERS) || user_id === user.id)) {
+ return Users.getByID.load(user_id);
+ }
+ }
+ },
RootMutation: {
- [`create${Reaction}`]: (_, {[reaction]: {item_id, item_type}}, {mutators: {Action}}) => {
- return wrapResponse(reaction)(Action.create({item_id, item_type, action_type: REACTION}));
+ [`create${Reaction}Action`]: (_, {input: {item_id}}, {mutators: {Action}, pubsub, loaders: {Comments}}) => {
+ const response = Comments.get.load(item_id).then((comment) => {
+ return Action.create({item_id, item_type: 'COMMENTS', action_type: REACTION})
+ .then((action) => {
+ pubsub.publish(`${reaction}ActionCreated`, {action, comment});
+ return Promise.resolve(action);
+ });
+ });
+ return wrapResponse(reaction)(response);
+ },
+ [`delete${Reaction}Action`]: (_, {input: {id}}, {mutators: {Action}, pubsub, loaders: {Comments}}) => {
+ const response = Action.delete({id})
+ .then((action) => {
+ return Comments.get.load(action.item_id).then((comment) => {
+ pubsub.publish(`${reaction}ActionDeleted`, {action, comment});
+ return Promise.resolve(action);
+ });
+ });
+ return wrapResponse(reaction)(response);
}
},
},
@@ -106,7 +167,19 @@ function getReactionConfig(reaction) {
}
}
}
- }
+ },
+ setupFunctions: {
+ [`${reaction}ActionCreated`]: (options, args) => ({
+ [`${reaction}ActionCreated`]: {
+ filter: ({comment}) => comment.asset_id === args.asset_id,
+ },
+ }),
+ [`${reaction}ActionDeleted`]: (options, args) => ({
+ [`${reaction}ActionDeleted`]: {
+ filter: ({comment}) => comment.asset_id === args.asset_id,
+ },
+ }),
+ },
};
}
diff --git a/plugins/coral-plugin-like/client/LikeButton.js b/plugins/coral-plugin-like/client/LikeButton.js
index fac4fdfca..5b99f7df4 100644
--- a/plugins/coral-plugin-like/client/LikeButton.js
+++ b/plugins/coral-plugin-like/client/LikeButton.js
@@ -28,7 +28,7 @@ class LikeButton extends React.Component {
return;
}
- if (alreadyReacted()) {
+ if (alreadyReacted) {
deleteReaction();
} else {
postReaction();
@@ -40,10 +40,10 @@ class LikeButton extends React.Component {
return (
diff --git a/plugins/coral-plugin-love/client/LoveButton.js b/plugins/coral-plugin-love/client/LoveButton.js
index 8dbf51a6e..0e2b9ff01 100644
--- a/plugins/coral-plugin-love/client/LoveButton.js
+++ b/plugins/coral-plugin-love/client/LoveButton.js
@@ -28,7 +28,7 @@ class LoveButton extends React.Component {
return;
}
- if (alreadyReacted()) {
+ if (alreadyReacted) {
deleteReaction();
} else {
postReaction();
@@ -40,10 +40,10 @@ class LoveButton extends React.Component {
return (
diff --git a/plugins/coral-plugin-respect/client/RespectButton.js b/plugins/coral-plugin-respect/client/RespectButton.js
index 14b0ec057..bde8c9669 100644
--- a/plugins/coral-plugin-respect/client/RespectButton.js
+++ b/plugins/coral-plugin-respect/client/RespectButton.js
@@ -28,7 +28,7 @@ class RespectButton extends React.Component {
return;
}
- if (alreadyReacted()) {
+ if (alreadyReacted) {
deleteReaction();
} else {
postReaction();
@@ -40,11 +40,11 @@ class RespectButton extends React.Component {
return (