Skip to content

Commit

Permalink
Merge pull request #880 from Hylozoic/NODE-858-reacts-on-comments
Browse files Browse the repository at this point in the history
Node 858 reacts on comments
  • Loading branch information
thomasgwatson authored Dec 1, 2022
2 parents 69f0a0c + 1ea7852 commit e572d86
Show file tree
Hide file tree
Showing 25 changed files with 436 additions and 114 deletions.
8 changes: 4 additions & 4 deletions api/graphql/filters.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,17 +162,17 @@ export const postFilter = (userId, isAdmin) => relation => {
})
}

// Only can see votes from active posts that are public or are in a group that the person is a member of
export const voteFilter = userId => relation => {
// Only can see reactions from active posts that are public or are in a group that the person is a member of
export const reactionFilter = userId => relation => {
return relation.query(q => {
q.join('groups_posts', 'votes.post_id', 'groups_posts.post_id')
q.join('groups_posts', 'reactions.entity_id', 'groups_posts.post_id')
q.join('posts', 'posts.id', 'groups_posts.post_id')
q.where('posts.active', true)
q.andWhere('reactions.entity_type', 'post')
q.andWhere(q2 => {
const selectIdsForMember = Group.selectIdsForMember(userId)
q.whereIn('groups_posts.group_id', selectIdsForMember)
q.orWhere('posts.is_public', true)
})
})
}

8 changes: 7 additions & 1 deletion api/graphql/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {
deleteGroupTopic,
deletePost,
deleteProjectRole,
deleteReaction,
deleteSavedSearch,
expireInvitation,
findOrCreateLinkPreviewByUrl,
Expand All @@ -60,12 +61,13 @@ import {
messageGroupModerators,
pinPost,
processStripeToken,
reactOn,
reactivateUser,
regenerateAccessCode,
registerDevice,
registerStripeAccount,
reinviteAll,
rejectGroupRelationshipInvite,
reactivateUser,
register,
reorderPostInCollection,
removeMember,
Expand Down Expand Up @@ -347,6 +349,8 @@ export function makeMutations (expressContext, userId, isAdmin, fetchOne) {

deleteProjectRole: (root, { id }) => deleteProjectRole(userId, id),

deleteReaction: (root, { entityId, data }) => deleteReaction(entityId, userId, data),

deleteSavedSearch: (root, { id }) => deleteSavedSearch(id),

expireInvitation: (root, {invitationId}) =>
Expand Down Expand Up @@ -385,6 +389,8 @@ export function makeMutations (expressContext, userId, isAdmin, fetchOne) {
processStripeToken: (root, { postId, token, amount }) =>
processStripeToken(userId, postId, token, amount),

reactOn: (root, { entityId, data }) => reactOn(userId, entityId, data),

reactivateMe: (root) => reactivateUser({ userId }),

regenerateAccessCode: (root, { groupId }) =>
Expand Down
53 changes: 38 additions & 15 deletions api/graphql/makeModels.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
messageFilter,
personFilter,
postFilter,
voteFilter
reactionFilter
} from './filters'
import { LOCATION_DISPLAY_PRECISION } from '../../lib/constants'
import InvitationService from '../services/InvitationService'
Expand Down Expand Up @@ -134,7 +134,7 @@ export default function makeModels (userId, isAdmin, apiClient) {
{comments: {querySet: true}},
{skills: {querySet: true}},
{skillsToLearn: {querySet: true}},
{votes: {querySet: true}}
{reactions: {querySet: true}}
],
filter: nonAdminFilter(apiFilter(personFilter(userId))),
isDefaultTypeForTable: true,
Expand Down Expand Up @@ -172,7 +172,8 @@ export default function makeModels (userId, isAdmin, apiClient) {
getters: {
commenters: (p, { first }) => p.getCommenters(first, userId),
commentersTotal: p => p.getCommentersTotal(userId),
myVote: p => userId ? p.userVote(userId).then(v => !!v) : false,
myReactions: p => userId ? p.postReactions(userId).fetch() : [],
myVote: p => userId ? p.userVote(userId).then(v => !!v) : false, // Remove once Mobile has been updated
myEventResponse: p =>
userId && p.isEvent() ? p.userEventInvitation(userId)
.then(eventInvitation => eventInvitation ? eventInvitation.get('response') : '')
Expand All @@ -188,6 +189,7 @@ export default function makeModels (userId, isAdmin, apiClient) {
{ eventInvitations: { querySet: true } },
'linkPreview',
'postMemberships',
'postReactions',
{
media: {
alias: 'attachments',
Expand Down Expand Up @@ -610,15 +612,19 @@ export default function makeModels (userId, isAdmin, apiClient) {
],
relations: [
'post',
{user: {alias: 'creator'}},
{childComments: { querySet: true }},
{media: {
alias: 'attachments',
arguments: ({ type }) => [type]
}}
{ user: { alias: 'creator' } },
{ childComments: { querySet: true } },
{
media: {
alias: 'attachments',
arguments: ({ type }) => [type]
}
}
],
getters: {
parentComment: (c) => c.parentComment().fetch()
parentComment: (c) => c.parentComment().fetch(),
myReactions: c => userId ? c.commentReactions(userId).fetch() : [],
commentReactions: c => c.commentReactions().fetch()
},
filter: nonAdminFilter(commentFilter(userId)),
isDefaultTypeForTable: true
Expand Down Expand Up @@ -678,16 +684,33 @@ export default function makeModels (userId, isAdmin, apiClient) {
filter: messageFilter(userId)
},

Vote: {
model: Vote,
Reaction: {
model: Reaction,
getters: {
createdAt: r => r.get('date_reacted'),
emojiBase: r => r.get('emoji_base'),
emojiFull: r => r.get('emoji_full'),
emojiLabel: r => r.get('emoji_label'),
entityId: r => r.get('entity_id'),
entityType: r => r.get('entity_type')
},
isDefaultTypeForTable: true,
relations: [
'post',
'user'
],
filter: nonAdminFilter(reactionFilter('reactions', userId))
},
Vote: { // TO BE REMOVED ONCE MOBILE IS UPDATED
model: Reaction,
getters: {
createdAt: v => v.get('date_voted')
createdAt: v => v.get('date_reacted')
},
relations: [
'post',
{user: {alias: 'voter'}}
{ user: { alias: 'voter' } }
],
filter: nonAdminFilter(voteFilter('votes', userId))
filter: nonAdminFilter(reactionFilter('reactions', userId))
},

GroupTopic: {
Expand Down
26 changes: 26 additions & 0 deletions api/graphql/mutations/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,32 @@ export function messageGroupModerators (userId, groupId) {
return Group.messageModerators(userId, groupId)
}

export function reactOn (userId, entityId, data) {
const lookUp = {
post: Post,
comment: Comment
}
const { entityType } = data
if (!['post', 'comment'].includes(entityType)) {
throw new Error('entityType invalid: you need to say its a post or a comment')
}
return lookUp[entityType].find(entityId)
.then(entity => entity.reaction(userId, data))
}

export function deleteReaction (entityId, userId, data) {
const lookUp = {
post: Post,
comment: Comment
}
const { entityType } = data
if (!['post', 'comment'].includes(entityType)) {
throw new Error('entityType invalid: you need to say its a post or a comment')
}
return lookUp[entityType].find(entityId)
.then(entity => entity.deleteReaction(userId, data))
}

export async function removePost (userId, postId, groupIdOrSlug) {
const group = await Group.find(groupIdOrSlug)
return Promise.join(
Expand Down
3 changes: 1 addition & 2 deletions api/graphql/mutations/post.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,10 @@ export function unfulfillPost (userId, postId) {
.then(() => ({success: true}))
}

export function vote (userId, postId, isUpvote) {
export function vote (userId, postId, isUpvote) { // TODO: remove after mobile brought back into sync
return Post.find(postId)
.then(post => post.vote(userId, isUpvote))
}

export function deletePost (userId, postId) {
return Post.find(postId)
.then(post => {
Expand Down
68 changes: 67 additions & 1 deletion api/graphql/schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,12 @@ type Comment {
post: Post
# The content of the comment
text: String
# Not used
commentReactionsTotal: Int
# All reactions on a comment
commentReactions: [Reaction]
# The reactions of the logged-in user to this
myReactions: [Reaction]
}

# Query Sets are used for pagination, returning the total # of results for a query along with the current set of items and whether there is more to load in the final set
Expand Down Expand Up @@ -1432,6 +1438,7 @@ type Person {
offset: Int,
# Determines sort order, either 'ASC' or 'DESC'
order: String,

# Only load posts whose content matches this search string
search: String,
# Sort posts by 'created', 'start_time', 'updated', 'votes'
Expand All @@ -1447,6 +1454,9 @@ type Person {
# Project posts that this person is a member of
projects: PostQuerySet

# This person's reactions
reactions(first: Int, offset: Int, order: String): ReactionQuerySet

# The set of Skills this person has added to their profile
skills(first: Int, cursor: ID): SkillQuerySet

Expand Down Expand Up @@ -1517,8 +1527,13 @@ type Post {
locationObject: Location
# Logged in user's attendance response to an event post: 'yes', 'maybe' or 'no'
myEventResponse: String
# Whether the logged in user has upvoted the post or not
# The reactions of the logged-in user to this
myReactions: [Reaction]
# Whether the logged-in user has upvoted the post or not
myVote: Boolean
# Number of people that have one or more reactions to a post
peopleReactedTotal: Int
postReactionsTotal: Int
# Number of total "members" of the post. Used for Projects only right now
postMembershipsTotal: Int
# Link to a project management tool or service. Only used for Projects right now
Expand Down Expand Up @@ -1567,6 +1582,9 @@ type Post {
# The post's "memberships" in the groups it has been posted in
postMemberships: [PostMembership]

# reactions to this post
postReactions: [Reaction]

# The topics that have been added to this post
topics: [Topic]
}
Expand Down Expand Up @@ -1607,6 +1625,38 @@ type Question {
text: String
}

# Emoji reactions to posts and comments
type Reaction {
# when the reaction happened
createdAt: Date
# Just the basic emoji, no modifiers. UTF-16 (JS default) encoding for an emojis code-points (like '\uD83D\uDE00')
emojiBase: String
# The full emoji, with any modifiers (eg: for skin). UTF-16 (JS default) encoding for an emojis code-points (like '\uD83D\uDE00')
emojiFull: String
# The EN locale string for an emoji, like :thumbs_up:
emojiLabel: String
# id for the entity associated with the reactions
entityId: ID
# What type of entity a reaction is associated with. Currently only 'post' or 'comment'
entityType: String
# id for the reaction
id: ID
# id for the user who reacted
userId: ID

# The post associated with a reaction
post: Post
# The user associated with a reaction
user: Person
}

# Query Sets are used for pagination, returning the total # of results for a query along with the current set of items and whether there is more to load in the final set
type ReactionQuerySet {
total: Int
hasMore: Boolean
items: [Reaction]
}

# A saved set of search/filter parameters for the map that can be reloaded later by a user
type SavedSearch {
id: ID
Expand Down Expand Up @@ -1840,6 +1890,8 @@ type Mutation {
deletePost(id: ID): GenericResult
# Remove a person as a member from a project Post
deleteProjectRole(id: ID): GenericResult
# Delete a reaction you created
deleteReaction(entityId: ID, data: ReactionInput): Post
# Delete a SavedSearch you created
deleteSavedSearch(id: ID): ID
# Set an invitation to join a group as expired. Only a group moderator can call this.
Expand Down Expand Up @@ -1880,6 +1932,8 @@ type Mutation {
pinPost(postId: ID, groupId: ID): GenericResult
# NOT USED RIGHT NOW
processStripeToken(postId: ID, token: String, amount: Int): GenericResult
# react to a post or comment
reactOn(entityId: ID, data: ReactionInput): Post
# For a deactivated user to reactivate their account.
reactivateMe(id: ID): GenericResult
# Regenerate the invite URL used to invite people to a Group. Only a group moderator can call this.
Expand Down Expand Up @@ -2461,6 +2515,18 @@ input QuestionAnswerInput {
questionId: Int
}

# Inputs for emoji reactions to posts and comments
input ReactionInput {
# when the reaction happened
createdAt: Date
# The full emoji, with any modifiers (eg: for skin). UTF-16 (JS default) encoding for an emojis code-points (like '\uD83D\uDE00')
emojiFull: String
# id for the entity associated with the reactions
entityId: ID
# What type of entity a reaction is associated with. Currently only 'post' or 'comment'
entityType: String
}

# A SavedSearch on the Map
input SavedSearchInput {
# The geographic bounding box
Expand Down
Loading

0 comments on commit e572d86

Please sign in to comment.