Skip to content

Commit ceece7d

Browse files
committed
Merge branch 'dev'
2 parents 1fc4fb5 + 739cc1a commit ceece7d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+5683
-5163
lines changed

APIs.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ NOTE: you will want to pass _either_ a slug _or_ an id to query by. If you pass
8282
}
8383
```
8484

85-
Example GraphQL mutation: __Updating a Group__ (only will succeed on groups that the user is a moderator of)
85+
Example GraphQL mutation: __Updating a Group__ (only will succeed on groups that the user is an administrator of)
8686
```
8787
{
8888
"query": "mutation ($id: ID, $changes: GroupInput) { updateGroup(id: $id, changes: $changes) { id name slug } }",
@@ -204,8 +204,8 @@ Example GraphQL mutation:
204204
}
205205
}
206206
],
207-
"moderatorDescriptor": "Steward", // Default is Moderator
208-
"moderatorDescriptorPlural": "Stewards", // Default is Moderators
207+
"stewardDescriptor": "Steward", // Default is Steward
208+
"stewardDescriptorPlural": "Stewards", // Default is Stewards
209209
"settings": {
210210
locationDisplayPrecision: precise, // precise => precise location displayed, near => location text shows nearby town/city and coordinate shifted, region => location not shown on map at all and location text shows nearby city/region
211211
publicMemberDirectory: false, // Boolean

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
66

77
## Unreleased
88

9+
## [5.9.0] - 2024-07-23
10+
11+
### Added
12+
- __More powerful Roles & Responsibilities__: Groups can now have roles that have specific responsibilities, and members can have multiple roles in a group. Roles can be assigned to members by group Coordinators (described below). There are 4 built in System responsibilities: Administration (super admin that can do everything and change all group settings), Manage Content (can remove posts from the group, and edit the Explore page), Remove Members (boot members from the group), and Add Members (full access to the invite and join request functionality for the group). There are also 3 built in Common Roles that all groups have: Coordinators have full Administration powers, Moderators can Manage Content and Remove Members, and Hosts can Add Members. Groups can also add custom roles with custom responsibilities defined by the group, or custom roles that include the system responsibilities.
13+
914
## [5.8.0] - 2024-07-03
1015

1116
### Added

CONTRIBUTING.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Please check out the [contrbution guide in the EVO](https://github.com/Hylozoic/hylo-evo/blob/dev/CONTRIBUTING.md) repo for more details

api/controllers/ExportController.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ module.exports = {
1818
// auth check
1919
let ok = false
2020
try {
21-
ok = await GroupMembership.hasModeratorRole(req.session.userId, p.groupId)
21+
ok = await GroupMembership.hasResponsibility(req.session.userId, p.groupId, Responsibility.constants.RESP_ADMINISTRATION)
2222
} catch (err) {
2323
return res.status(422).send({ error: err.message ? err.message : err })
2424
}

api/controllers/UserController.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,14 @@ module.exports = {
2424
let message = locales[locale].apiInviteMessageContent(req.api_client)
2525
let subject = locales[locale].apiInviteMessageSubject(group.get('name'))
2626
if (req.api_client) {
27-
const client = await (new OIDCAdapter("Client")).find(req.api_client.id)
27+
const client = await (new OIDCAdapter('Client')).find(req.api_client.id)
2828
if (!client) {
2929
return res.status(403).json({ error: 'Unauthorized' })
3030
}
3131
subject = client.invite_subject || locales[locale].clientInviteSubjectDefault(group.get('name'))
32-
message = client.invite_message || locales[locale].clientInviteMessageDefault({userName: user.get('name'), groupName: group.get('name')})
32+
message = client.invite_message || locales[locale].clientInviteMessageDefault({ userName: user.get('name'), groupName: group.get('name') })
3333
}
34-
const inviteBy = await group.moderators().fetchOne()
34+
const inviteBy = await group.stewards().fetchOne()
3535

3636
await InvitationService.create({
3737
groupId: group.id,
@@ -50,7 +50,7 @@ module.exports = {
5050

5151
const attrs = { name, email: email ? email.toLowerCase() : null, email_validated: false, active: false, group }
5252
if (isModeratorVal) {
53-
attrs.role = GroupMembership.Role.MODERATOR
53+
attrs.role = GroupMembership.Role.MODERATOR // This is ultimately fed to Group.addMembers, which handles mod -> Coordinator. TODO: RESP, fix this
5454
}
5555

5656
return User.create(attrs)

api/graphql/filters.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -35,20 +35,21 @@ export const groupFilter = userId => relation => {
3535
const selectIdsForMember = Group.selectIdsForMember(userId)
3636
const parentGroupIds = GroupRelationship.parentIdsFor(selectIdsForMember)
3737
const childGroupIds = GroupRelationship.childIdsFor(selectIdsForMember)
38-
const selectModeratedGroupIds = Group.selectIdsForMember(userId, { 'group_memberships.role': GroupMembership.Role.MODERATOR })
39-
const childrenOfModeratedGroupIds = GroupRelationship.childIdsFor(selectModeratedGroupIds)
38+
// You can see all related groups, even hidden ones, if you are a group Administrator
39+
const selectStewardedGroupIds = Group.selectIdsByResponsibilities(userId, [Responsibility.Common.RESP_ADMINISTRATION])
40+
const childrenOfStewardedGroupIds = GroupRelationship.childIdsFor(selectStewardedGroupIds)
4041

4142
// Can see groups you are a member of...
4243
q2.whereIn('groups.id', selectIdsForMember)
4344
// + their parent groups
4445
q2.orWhereIn('groups.id', parentGroupIds)
45-
// + child groups that are not hidden, except moderators of a group can see its hidden children
46+
// + child groups that are not hidden, except Admininstrators of a group can see its hidden children
4647
q2.orWhere(q3 => {
4748
q3.where(q4 => {
4849
q4.whereIn('groups.id', childGroupIds)
4950
q4.andWhere('groups.visibility', '!=', Group.Visibility.HIDDEN)
5051
})
51-
q3.orWhereIn('groups.id', childrenOfModeratedGroupIds)
52+
q3.orWhereIn('groups.id', childrenOfStewardedGroupIds)
5253
})
5354
// + all public groups
5455
q2.orWhere(q5 => {
@@ -125,7 +126,7 @@ export const personFilter = userId => relation => relation.query(q => {
125126
q3.select('group_memberships.user_id')
126127
q3.whereIn('group_memberships.group_id', Group.selectIdsForMember(userId))
127128
})
128-
const sharedConnections = UserConnection.query(ucq =>{
129+
const sharedConnections = UserConnection.query(ucq => {
129130
ucq.select('other_user_id')
130131
ucq.where('user_connections.user_id', userId)
131132
})

api/graphql/index.js

+25-5
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import mixpanel from '../../lib/mixpanel'
77
import {
88
acceptGroupRelationshipInvite,
99
acceptJoinRequest,
10+
addGroupResponsibility,
1011
addGroupRole,
1112
addMember,
1213
addModerator,
1314
addPeopleToProjectRole,
1415
addPostToCollection,
16+
addResponsibilityToRole,
1517
addProposalVote,
1618
addRoleToMember,
1719
addSkill,
@@ -42,6 +44,7 @@ import {
4244
deleteComment,
4345
deleteGroup,
4446
deleteGroupRelationship,
47+
deleteGroupResponsibility,
4548
deleteGroupTopic,
4649
deletePost,
4750
deleteProjectRole,
@@ -63,7 +66,7 @@ import {
6366
logout,
6467
markActivityRead,
6568
markAllActivitiesRead,
66-
messageGroupModerators,
69+
messageGroupStewards,
6770
pinPost,
6871
processStripeToken,
6972
reactOn,
@@ -79,6 +82,7 @@ import {
7982
removeModerator,
8083
removePost,
8184
removePostFromCollection,
85+
removeResponsibilityFromRole,
8286
removeRoleFromMember,
8387
removeProposalVote,
8488
removeSkill,
@@ -96,6 +100,7 @@ import {
96100
unlinkAccount,
97101
updateComment,
98102
updateGroup,
103+
updateGroupResponsibility,
99104
updateGroupRole,
100105
updateGroupTopic,
101106
updateGroupTopicFollow,
@@ -215,6 +220,7 @@ export function makeAuthenticatedQueries (userId, fetchOne, fetchMany) {
215220
InvitationService.check(invitationToken, accessCode),
216221
collection: (root, { id }) => fetchOne('Collection', id),
217222
comment: (root, { id }) => fetchOne('Comment', id),
223+
commonRoles: (root, args) => CommonRole.fetchAll(args),
218224
connections: (root, args) => fetchMany('PersonConnection', args),
219225
group: async (root, { id, slug, updateLastViewed }) => {
220226
// you can specify id or slug, but not both
@@ -254,6 +260,7 @@ export function makeAuthenticatedQueries (userId, fetchOne, fetchMany) {
254260
person: (root, { id, email }) => fetchOne('Person', id || email, id ? 'id' : 'email'),
255261
post: (root, { id }) => fetchOne('Post', id),
256262
posts: (root, args) => fetchMany('Post', args),
263+
responsibilities: (root, args) => Responsibility.fetchAll(args),
257264
savedSearches: (root, args) => fetchMany('SavedSearch', args),
258265
search: (root, args) => {
259266
if (!args.first) args.first = 20
@@ -296,6 +303,8 @@ export function makeMutations (expressContext, userId, isAdmin, fetchOne) {
296303

297304
acceptJoinRequest: (root, { joinRequestId }) => acceptJoinRequest(userId, joinRequestId),
298305

306+
addGroupResponsibility: (root, { groupId, title, description }) => addGroupResponsibility({ userId, groupId, title, description }),
307+
299308
addGroupRole: (root, { groupId, color, name, description, emoji }) => addGroupRole({ userId, groupId, color, name, description, emoji }),
300309

301310
addModerator: (root, { personId, groupId }) =>
@@ -309,7 +318,10 @@ export function makeMutations (expressContext, userId, isAdmin, fetchOne) {
309318

310319
addProposalVote: (root, { postId, optionId }) => addProposalVote({ userId, postId, optionId }),
311320

312-
addRoleToMember: (root, { personId, groupRoleId, groupId }) => addRoleToMember({ userId, personId, groupRoleId, groupId }),
321+
addResponsibilityToRole: (root, { responsibilityId, roleId, groupId }) =>
322+
addResponsibilityToRole({ userId, responsibilityId, roleId, groupId }),
323+
324+
addRoleToMember: (root, { personId, roleId, groupId, isCommonRole = false }) => addRoleToMember({ userId, personId, roleId, groupId, isCommonRole }),
313325

314326
addSkill: (root, { name }) => addSkill(userId, name),
315327
addSkillToLearn: (root, { name }) => addSkillToLearn(userId, name),
@@ -366,6 +378,9 @@ export function makeMutations (expressContext, userId, isAdmin, fetchOne) {
366378

367379
deleteGroupRelationship: (root, { parentId, childId }) => deleteGroupRelationship(userId, parentId, childId),
368380

381+
deleteGroupResponsibility: (root, { responsibilityId, groupId }) =>
382+
deleteGroupResponsibility({ userId, responsibilityId, groupId }),
383+
369384
deleteGroupTopic: (root, { id }) => deleteGroupTopic(userId, id),
370385

371386
deleteMe: (root) => deleteUser({ sessionId, userId }),
@@ -408,7 +423,7 @@ export function makeMutations (expressContext, userId, isAdmin, fetchOne) {
408423

409424
markAllActivitiesRead: (root) => markAllActivitiesRead(userId),
410425

411-
messageGroupModerators: (root, { groupId }) => messageGroupModerators(userId, groupId),
426+
messageGroupStewards: (root, { groupId }) => messageGroupStewards(userId, groupId),
412427

413428
pinPost: (root, { postId, groupId }) =>
414429
pinPost(userId, postId, groupId),
@@ -445,9 +460,11 @@ export function makeMutations (expressContext, userId, isAdmin, fetchOne) {
445460
removePostFromCollection: (root, { collectionId, postId }) =>
446461
removePostFromCollection(userId, collectionId, postId),
447462

448-
removeProposalVote: (root, { postId, optionId }) => removeProposalVote({ userId, postId, optionId }),
463+
removeResponsibilityFromRole: (root, { roleResponsibilityId, groupId }) =>
464+
removeResponsibilityFromRole({ userId, roleResponsibilityId, groupId }),
449465

450-
removeRoleFromMember: (root, { groupRoleId, personId, groupId }) => removeRoleFromMember({ groupRoleId, personId, userId, groupId }),
466+
removeRoleFromMember: (root, { roleId, personId, groupId, isCommonRole }) => removeRoleFromMember({ roleId, personId, userId, groupId, isCommonRole }),
467+
removeProposalVote: (root, { postId, optionId }) => removeProposalVote({ userId, postId, optionId }),
451468

452469
removeSkill: (root, { id, name }) => removeSkill(userId, id || name),
453470
removeSkillToLearn: (root, { id, name }) => removeSkillToLearn(userId, id || name),
@@ -479,6 +496,9 @@ export function makeMutations (expressContext, userId, isAdmin, fetchOne) {
479496
unlinkAccount: (root, { provider }) =>
480497
unlinkAccount(userId, provider),
481498

499+
updateGroupResponsibility: (root, { groupId, responsibilityId, title, description }) =>
500+
updateGroupResponsibility({ userId, groupId, responsibilityId, title, description }),
501+
482502
updateGroupRole: (root, { groupRoleId, color, name, description, emoji, active, groupId }) =>
483503
updateGroupRole({ userId, groupRoleId, color, name, description, emoji, active, groupId }),
484504

api/graphql/index.test.js

+13-13
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import { createRequestHandler, makeMutations, makeAuthenticatedQueries } from '.
44
import '../../test/setup'
55
import factories from '../../test/setup/factories'
66
import { mockify, spyify, unspyify } from '../../test/setup/helpers'
7-
import { some, sortBy } from 'lodash/fp'
7+
import { some } from 'lodash/fp'
88
import { updateFollowers } from '../models/post/util'
99

1010
describe('graphql request handler', () => {
11-
var handler,
11+
let handler,
1212
req, res,
1313
user, user2,
1414
group,
@@ -20,7 +20,7 @@ describe('graphql request handler', () => {
2020
user = factories.user()
2121
user2 = factories.user()
2222
group = factories.group()
23-
post = factories.post({ type: Post.Type.DISCUSSION})
23+
post = factories.post({ type: Post.Type.DISCUSSION })
2424
post2 = factories.post({ type: Post.Type.REQUEST })
2525
comment = factories.comment()
2626
media = factories.media()
@@ -43,16 +43,16 @@ describe('graphql request handler', () => {
4343
group.posts().attach(post2),
4444
group.addMembers([user.id, user2.id]).then((memberships) => {
4545
const earlier = new Date(new Date().getTime() - 86400000)
46-
return memberships[0].save({ created_at: earlier }, {patch: true})
46+
return memberships[0].save({ created_at: earlier }, { patch: true })
4747
})
4848
])
49-
.then(() => Promise.all([
50-
updateFollowers(post),
51-
updateFollowers(post2)
52-
]))
49+
.then(() => Promise.all([
50+
updateFollowers(post),
51+
updateFollowers(post2)
52+
]))
5353
})
5454

55-
after(async function() {
55+
after(async function () {
5656
await groupExtension.destroy()
5757
await extension.destroy()
5858
})
@@ -62,7 +62,7 @@ describe('graphql request handler', () => {
6262
req.url = '/noo/graphql'
6363
req.method = 'POST'
6464
req.headers = {
65-
'Content-Type': 'application/json',
65+
'Content-Type': 'application/json'
6666
},
6767
req.session = {
6868
userId: user.id,
@@ -557,7 +557,7 @@ describe('graphql request handler', () => {
557557
`,
558558
serverContext: { req, res }
559559
})
560-
560+
561561
return expect(executionResult).to.deep.nested.include({
562562
data: {
563563
sendEmailVerification: {
@@ -579,7 +579,7 @@ describe('graphql request handler', () => {
579579
`,
580580
serverContext: { req, res }
581581
})
582-
582+
583583
return expect(executionResult).to.deep.nested.include({
584584
data: {
585585
sendEmailVerification: {
@@ -604,7 +604,7 @@ describe('graphql request handler', () => {
604604
`,
605605
serverContext: { req, res }
606606
})
607-
607+
608608
return expect(executionResult).to.deep.nested.include({
609609
data: {
610610
sendEmailVerification: {

0 commit comments

Comments
 (0)