diff --git a/src/lib/collections.ts b/src/lib/collections.ts index 89375d0..daa0d49 100644 --- a/src/lib/collections.ts +++ b/src/lib/collections.ts @@ -4,13 +4,13 @@ import uniq from 'lodash/uniq' import { DatabaseItem } from '../types' import { containerFor, createQuerySpec, queryItems } from './database' -import { User, UserSubscription, UserBlock } from '../types/collections' +import { User, UserSubscription, UserBlock, UserItemType } from '../types/collections' export async function getUsers(client: CosmosClient, ids: string[], logger?: Logger): Promise { return await queryItems({ container: containerFor(client, 'Users'), query: createQuerySpec( - 'SELECT u.id, u.name, u.imageUrl, u.coverImageUrl, u.group, u.created FROM Users u WHERE ARRAY_CONTAINS(@ids, u.id)', + `SELECT u.id, u.name, u.imageUrl, u.coverImageUrl, u['group'], u.created FROM Users u WHERE ARRAY_CONTAINS(@ids, u.id)`, { ids: uniq(ids), } @@ -20,7 +20,7 @@ export async function getUsers(client: CosmosClient, ids: string[], logger?: Log } export async function getUsersFromItems(client: CosmosClient, items: T[], logger?: Logger): Promise { - return await getUsers(client, items.map(i => i.id)) + return await getUsers(client, items.map(i => i.id), logger) } export async function getApprovedSubscriptions(client: CosmosClient, from: string, to: string, logger?: Logger): Promise { @@ -30,9 +30,9 @@ export async function getApprovedSubscriptions(client: CosmosClient, from: strin `SELECT u.id FROM Users u WHERE u.subscriberId = @to u.pk = @from AND - u.t = 'subscription' AND + u.t = @type AND u.pending = false`, - { from, to } + { from, to, type: UserItemType.Subscription } ), logger, }) @@ -44,9 +44,9 @@ export async function getUserBlocks(client: CosmosClient, from: string, to: stri query: createQuerySpec( `SELECT u.id FROM Users u WHERE u.pk = @from - AND u.t = 'block' + AND u.t = @type AND ARRAY_CONTAINS(@to, u.blockedId)`, - { from, to } + { from, to, type: UserItemType.Block } ), logger, }) diff --git a/src/plugins/api/groups.ts b/src/plugins/api/groups.ts index cacfab6..334420c 100644 --- a/src/plugins/api/groups.ts +++ b/src/plugins/api/groups.ts @@ -12,8 +12,9 @@ import merge from 'lodash/merge' import { Server, IncomingMessage, ServerResponse } from 'http' import { MIN_ID_LENGTH, MAX_NAME_LENGTH, GROUP_LISTING_PARTITION_KEY } from '../../constants' -import { errorSchema, groupListingSchema } from '../../schemas' +import { errorSchema, groupListingSchema, userSchema } from '../../schemas' import { unauthorizedError, badRequestError, notFoundError, serverError } from '../../lib/errors' +import { getUsers } from '../../lib/collections' import { containerFor, createQuerySpec, queryItems, getItem, normalize } from '../../lib/database' import { @@ -489,12 +490,12 @@ function listRoute(server: FastifyInstance( - `SELECT * FROM GroupDirectory d WHERE d.pk = ${GROUP_LISTING_PARTITION_KEY} ${registrationString} ORDER BY d.${sort}`, + `SELECT * FROM GroupDirectory d WHERE d.pk = '${GROUP_LISTING_PARTITION_KEY}' ${registrationString} ORDER BY d.${sort}`, { maxItemCount: 40, continuation, @@ -510,6 +511,85 @@ function listRoute(server: FastifyInstance) { + interface Query { + type?: string + continuation?: string + } + + const options: RouteShorthandOptions = { + schema: { + querystring: { + type: 'object', + properties: { + type: { + type: 'string', + enum: ['admin', 'moderator', 'member'], + }, + continuation: { type: 'string' }, + }, + }, + response: { + 200: { + type: 'object', + properties: { + members: { + type: 'array', + items: userSchema, + }, + continuation: { type: 'string' }, + } + }, + 400: errorSchema, + } + }, + } + + server.get('/api/group/:id/members', options, async (request, reply) => { + if (!server.database) return serverError(reply) + + const groupContainer = containerFor(server.database.client, 'Groups') + + const group = await getItem({ + container: groupContainer, + id: request.params.id, + logger: request.log + }) + if (!group) return notFoundError(reply) + + const { type, continuation } = request.query + let typeString = '' + + if (type) { + typeString = `AND g.membership = '${type}'` + } + + const container = containerFor(server.database.client, 'Groups') + const { resources: memberships, requestCharge, continuation: newContinuation } = await container.items.query( + `SELECT g.userId, g.membership FROM Groups g WHERE g.pk = '${group.id}' AND g.t = '${GroupItemType.Membership}' ${typeString} ORDER BY g.created DESC`, + { + maxItemCount: 100, + continuation, + } + ).fetchAll() + + request.log.trace('Query: %d', requestCharge) + const users = await getUsers(server.database.client, memberships.map(membership => membership.userId), request.log) + + return { + members: users.map(user => { + const m = memberships.find(membership => membership.userId === user.id) + + return { + ...user, + membership: m ? m.membership : undefined, + } + }), + continuation: newContinuation, + } + }) +} + const plugin: Plugin = async server => { availabilityRoute(server) createRoute(server) @@ -518,6 +598,7 @@ const plugin: Plugin = a unblockRoute(server) activateRoute(server) listRoute(server) + membersRoute(server) } export default plugin diff --git a/src/schemas.ts b/src/schemas.ts index 9227781..5e9147a 100644 --- a/src/schemas.ts +++ b/src/schemas.ts @@ -39,7 +39,9 @@ export const userSchema: JSONSchema = { }, }, created: { type: 'number' }, + subscription: { type: 'string' }, + membership: { type: 'string' }, }, }