Skip to content
This repository was archived by the owner on Oct 11, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ desktop/release
public/uploads
cypress/screenshots/
cypress/videos/
cacert
61 changes: 61 additions & 0 deletions api/migrations/20180823115847-add-users-communities-indexes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
exports.up = function(r, conn) {
return Promise.all([
r
.table('usersCommunities')
.indexCreate('communityIdAndIsMember', [
r.row('communityId'),
r.row('isMember'),
])
.run(conn),
r
.table('usersCommunities')
.indexCreate('communityIdAndIsMemberAndReputation', [
r.row('communityId'),
r.row('isMember'),
r.row('reputation'),
])
.run(conn),
r
.table('usersCommunities')
.indexCreate('communityIdAndIsModerator', [
r.row('communityId'),
r.row('isModerator'),
])
.run(conn),
r
.table('usersCommunities')
.indexCreate('communityIdAndIsOwner', [
r.row('communityId'),
r.row('isOwner'),
])
.run(conn),
r
.table('usersCommunities')
.indexCreate('communityIdAndIsTeamMember', [
r.row('communityId'),
r.row('isOwner').or(r.row('isModerator')),
])
.run(conn),
]);
};

exports.down = function(r, conn) {
return Promise.all([
r
.table('usersCommunities')
.indexDrop('communityIdAndIsMember')
.run(conn),
r
.table('usersCommunities')
.indexDrop('communityIdAndIsModerator')
.run(conn),
r
.table('usersCommunities')
.indexDrop('communityIdAndIsOwner')
.run(conn),
r
.table('usersCommunities')
.indexDrop('communityIdAndIsTeamMember')
.run(conn),
]);
};
68 changes: 42 additions & 26 deletions api/models/usersCommunities.js
Original file line number Diff line number Diff line change
Expand Up @@ -488,71 +488,87 @@ export const blockPendingMemberInCommunity = async (
type Options = { first: number, after: number };

// prettier-ignore
export const getMembersInCommunity = (communityId: string, options: Options, filter: Object): Promise<Array<string>> => {
export const getMembersInCommunity = (communityId: string, options: Options): Promise<Array<string>> => {
const { first, after } = options
return db
.table('usersCommunities')
.getAll(communityId, { index: 'communityId' })
.filter(filter ? filter : { isMember: true })
.orderBy(db.desc('reputation'))
.between([communityId, true, db.minval], [communityId, true, db.maxval], {
index: 'communityIdAndIsMemberAndReputation',
leftBound: 'open',
rightBound: 'open',
})
.orderBy({ index: db.desc('communityIdAndIsMemberAndReputation') })
.skip(after || 0)
.limit(first || 999999)
// return an array of the userIds to be loaded by gql
.limit(first || 25)
.map(userCommunity => userCommunity('userId'))
.run()
};

// prettier-ignore
export const getBlockedUsersInCommunity = (communityId: string): Promise<Array<string>> => {
export const getBlockedUsersInCommunity = (communityId: string, options: Options): Promise<Array<string>> => {
return (
db
.table('usersCommunities')
.getAll(communityId, { index: 'communityId' })
.getAll([communityId, false], { index: 'communityIdAndIsMember' })
.filter({ isBlocked: true })
// return an array of the userIds to be loaded by gql
.skip(options.after || 0)
.limit(options.first || 25)
.map(userCommunity => userCommunity('userId'))
.run()
);
};

// prettier-ignore
export const getPendingUsersInCommunity = (communityId: string): Promise<Array<string>> => {
export const getPendingUsersInCommunity = (communityId: string, options: Options): Promise<Array<string>> => {
return (
db
.table('usersCommunities')
.getAll(communityId, { index: 'communityId' })
.getAll([communityId, false], { index: 'communityIdAndIsMember' })
.filter({ isPending: true })
// return an array of the userIds to be loaded by gql
.skip(options.after || 0)
.limit(options.first || 25)
.map(userCommunity => userCommunity('userId'))
.run()
);
};

// prettier-ignore
export const getModeratorsInCommunity = (communityId: string): Promise<Array<string>> => {
export const getModeratorsInCommunity = (communityId: string, options: Options): Promise<Array<string>> => {
return (
db
.table('usersCommunities')
.getAll(communityId, { index: 'communityId' })
.filter({ isModerator: true })
// return an array of the userIds to be loaded by gql
.getAll([communityId, true], { index: 'communityIdAndIsModerator' })
.skip(options.after || 0)
.limit(options.first || 25)
.map(userCommunity => userCommunity('userId'))
.run()
);
};

export const getOwnersInCommunity = (
communityId: string
communityId: string,
options: Options
): Promise<Array<string>> => {
return (
db
.table('usersCommunities')
.getAll(communityId, { index: 'communityId' })
.filter({ isOwner: true })
// return an array of the userIds to be loaded by gql
.map(userCommunity => userCommunity('userId'))
.run()
);
return db
.table('usersCommunities')
.getAll([communityId, true], { index: 'communityIdAndIsOwner' })
.skip(options.after || 0)
.limit(options.first || 25)
.map(userCommunity => userCommunity('userId'))
.run();
};

export const getTeamMembersInCommunity = (
communityId: string,
options: Options
): Promise<Array<string>> => {
return db
.table('usersCommunities')
.getAll([communityId, true], { index: 'communityIdAndIsTeamMember' })
.skip(options.after || 0)
.limit(options.first || 25)
.map(userCommunity => userCommunity('userId'))
.run();
};

export const DEFAULT_USER_COMMUNITY_PERMISSIONS = {
Expand Down
39 changes: 26 additions & 13 deletions api/queries/community/members.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ import type { GraphQLContext } from '../../';
import type { PaginationOptions } from '../../utils/paginate-arrays';
import { encode, decode } from '../../utils/base64';
import { canViewCommunity } from '../../utils/permissions';
const { getMembersInCommunity } = require('../../models/usersCommunities');
import {
getMembersInCommunity,
getOwnersInCommunity,
getModeratorsInCommunity,
getTeamMembersInCommunity,
getPendingUsersInCommunity,
getBlockedUsersInCommunity,
} from '../../models/usersCommunities';

type MembersFilterType = {
isMember?: boolean,
Expand All @@ -16,14 +23,14 @@ type MembersFilterType = {

type Args = {
...$Exact<PaginationOptions>,
filter: MembersFilterType,
filter?: MembersFilterType,
};

export default async (root: DBCommunity, args: Args, ctx: GraphQLContext) => {
const { id } = root;
const { user, loaders } = ctx;

if (!await canViewCommunity(user, id, loaders)) {
if (!(await canViewCommunity(user, id, loaders))) {
return {
pageInfo: {
hasNextPage: false,
Expand All @@ -32,23 +39,29 @@ export default async (root: DBCommunity, args: Args, ctx: GraphQLContext) => {
};
}

const { first = 10, after, filter } = args;
const { first = 10, after, filter = {} } = args;
const cursor = decode(after);
// Get the index from the encoded cursor, asdf234gsdf-2 => ["-2", "2"]
const lastDigits = cursor.match(/-(\d+)$/);
const lastUserIndex =
lastDigits && lastDigits.length > 0 && parseInt(lastDigits[1], 10);
(lastDigits && lastDigits.length > 0 && parseInt(lastDigits[1], 10)) || 0;

// Note @brian: this is a shitty hack, but if we want to show both
// moderators and admins in a single list, I need to tweak the inbound
// filter here
let dbfilter = filter;
if (filter && (filter.isOwner && filter.isModerator)) {
dbfilter = row => row('isModerator').or(row('isOwner'));
let query;
if (filter.isBlocked) {
query = getBlockedUsersInCommunity;
} else if (filter.isPending) {
query = getPendingUsersInCommunity;
} else if (filter.isModerator && filter.isOwner) {
query = getTeamMembersInCommunity;
} else if (filter.isModerator) {
query = getModeratorsInCommunity;
} else if (filter.isOwner) {
query = getOwnersInCommunity;
} else {
query = getMembersInCommunity;
}

// $FlowFixMe
return getMembersInCommunity(id, { first, after: lastUserIndex }, dbfilter)
return query(id, { first, after: lastUserIndex })
.then(users => {
const permissionsArray = users.map(userId => [userId, id]);
// $FlowIssue
Expand Down
16 changes: 8 additions & 8 deletions api/test/__snapshots__/community.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -111,41 +111,41 @@ Object {
},
},
Object {
"cursor": "OC0y",
"cursor": "Mi0y",
"node": Object {
"isBlocked": false,
"isMember": true,
"isModerator": false,
"isOwner": false,
"reputation": 100,
"user": Object {
"id": "8",
"id": "2",
},
},
},
Object {
"cursor": "MS0z",
"cursor": "OC0z",
"node": Object {
"isBlocked": false,
"isMember": true,
"isModerator": false,
"isOwner": true,
"isOwner": false,
"reputation": 100,
"user": Object {
"id": "1",
"id": "8",
},
},
},
Object {
"cursor": "Mi00",
"cursor": "MS00",
"node": Object {
"isBlocked": false,
"isMember": true,
"isModerator": false,
"isOwner": false,
"isOwner": true,
"reputation": 100,
"user": Object {
"id": "2",
"id": "1",
},
},
},
Expand Down
10 changes: 4 additions & 6 deletions athena/models/usersCommunities.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ export const getMembersInCommunity = (
): Promise<Array<string>> => {
return db
.table('usersCommunities')
.getAll(communityId, { index: 'communityId' })
.filter({ isMember: true, receiveNotifications: true })
.getAll([communityId, true], { index: 'communityIdAndIsMember' })
.filter({ receiveNotifications: true })
.run()
.then(users => users.map(user => user.userId));
};
Expand All @@ -17,8 +17,7 @@ export const getOwnersInCommunity = (
): Promise<Array<string>> => {
return db
.table('usersCommunities')
.getAll(communityId, { index: 'communityId' })
.filter({ isOwner: true })
.getAll([communityId, true], { index: 'communityIdAndIsOwner' })
.run()
.then(users => users.map(user => user.userId));
};
Expand All @@ -28,8 +27,7 @@ export const getModeratorsInCommunity = (
): Promise<Array<string>> => {
return db
.table('usersCommunities')
.getAll(communityId, { index: 'communityId' })
.filter({ isModerator: true })
.getAll([communityId, true], { index: 'communityIdAndIsModerator' })
.run()
.then(users => users.map(user => user.userId));
};
Expand Down
21 changes: 0 additions & 21 deletions cacert

This file was deleted.

3 changes: 1 addition & 2 deletions chronos/models/community.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ export const getTopCommunities = (amount: number): Array<Object> => {
communityIds.map(community => {
return db
.table('usersCommunities')
.getAll(community, { index: 'communityId' })
.filter({ isMember: true })
.getAll([community, true], { index: 'communityIdAndIsMember' })
.count()
.run()
.then(count => {
Expand Down
3 changes: 1 addition & 2 deletions mercury/models/usersCommunities.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ export const updateReputation = (
): Promise<Object> => {
return db
.table('usersCommunities')
.getAll(userId, { index: 'userId' })
.filter({ communityId })
.getAll([userId, communityId], { index: 'userIdAndCommunityId' })
.update({
reputation: db.row('reputation').add(score),
})
Expand Down
3 changes: 1 addition & 2 deletions pluto/models/usersCommunities.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { db } from 'api/models/db';
export const removeAllCommunityModerators = (communityId: string) => {
return db
.table('usersCommunities')
.getAll(communityId, { index: 'communityId' })
.filter({ isModerator: true })
.getAll([communityId, true], { index: 'communityIdAndIsModerator' })
.update({ isModerator: false })
.run();
};