diff --git a/src/constants.ts b/src/constants.ts index 7855ca7..6d1f291 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -4,3 +4,4 @@ export const MAX_NAME_LENGTH = 80 export const MIN_PASSWORD_LENGTH = 8 export const SHORT_TEXT_LENGTH = 100 export const SUBSCRIBER_MAX_SIZE = 100 +export const GROUP_LISTING_PARTITION_KEY = 'pk' diff --git a/src/lib/authentication.ts b/src/lib/authentication.ts index 3613746..20644a3 100644 --- a/src/lib/authentication.ts +++ b/src/lib/authentication.ts @@ -1,6 +1,6 @@ import { v1 } from 'uuid' import { JWT } from '../lib/crypto' -import { UserToken } from '../types/collections' +import { UserToken, UserItemType } from '../types/collections' export async function createAccessToken(userId: string): Promise { return await JWT.sign({ sub: userId }, { expiresIn: process.env.TOKEN_EXPIRATION }) @@ -10,7 +10,7 @@ export function createRefreshToken(userId: string, userAgent: string, ip: string return { id: 'r' + v1().replace(/-/g, ''), pk: userId, - t: 'token', + t: UserItemType.Token, userAgent, ip, expires: Date.now() + (1000 * 60 * 60 * 24 * 365), diff --git a/src/plugins/api/authentication.ts b/src/plugins/api/authentication.ts index 5fd79c8..750b090 100644 --- a/src/plugins/api/authentication.ts +++ b/src/plugins/api/authentication.ts @@ -18,7 +18,19 @@ import { containerFor, getItem, queryItems, normalize, createQuerySpec } from '. import { badRequestError, badRequestFormError, unauthorizedError, serverError } from '../../lib/errors' import { tokenFromHeader } from '../../lib/http' -import { User, UserToken, Group, GroupInvitation, GroupPartial, GroupMembership } from '../../types/collections' +import { + User, + UserToken, + Group, + GroupInvitation, + GroupPartial, + GroupMembership, + UserItemType, + UserPrivacyType, + GroupItemType, + GroupMembershipType, + GroupRegistrationType +} from '../../types/collections' import { PluginOptions } from '../../types' function registerRoute(server: FastifyInstance) { @@ -100,13 +112,14 @@ function registerRoute(server: FastifyInstance({ @@ -118,8 +131,8 @@ function registerRoute(server: FastifyInstance({ pk: group.id, - t: 'membership', + t: GroupItemType.Membership, userId: user.id, pending: userPending, - membership: 'member', + membership: GroupMembershipType.Member, invitation, created: Date.now(), }) diff --git a/src/plugins/api/groups.ts b/src/plugins/api/groups.ts index 98f4300..cacfab6 100644 --- a/src/plugins/api/groups.ts +++ b/src/plugins/api/groups.ts @@ -8,13 +8,29 @@ import { DefaultHeaders, } from 'fastify' +import merge from 'lodash/merge' import { Server, IncomingMessage, ServerResponse } from 'http' -import { MIN_ID_LENGTH, MAX_NAME_LENGTH } from '../../constants' +import { MIN_ID_LENGTH, MAX_NAME_LENGTH, GROUP_LISTING_PARTITION_KEY } from '../../constants' import { errorSchema, groupListingSchema } from '../../schemas' import { unauthorizedError, badRequestError, notFoundError, serverError } from '../../lib/errors' import { containerFor, createQuerySpec, queryItems, getItem, normalize } from '../../lib/database' -import { User, Group, GroupListing, GroupMembership, UserBlock, GroupBlock, GroupRegistrationType } from '../../types/collections' + +import { + User, + Group, + GroupListing, + GroupMembership, + UserBlock, + GroupBlock, + GroupRegistrationType, + GroupStatus, + GroupMembershipType, + GroupItemType, + BlockType, + UserItemType +} from '../../types/collections' + import { PluginOptions } from '../../types' function availabilityRoute(server: FastifyInstance) { @@ -127,12 +143,12 @@ function createRoute(server: FastifyInstance('/api/group/:id', options, async (request, reply) => { if (!server.database) return serverError(reply) + const groupContainer = containerFor(server.database.client, 'Groups') + const listing = await getItem({ container: containerFor(server.database.client, 'GroupDirectory'), id: request.params.id, + partitionKey: 'pk', logger: request.log, }) - if (!listing) return notFoundError(reply) - return listing + const group = await getItem({ + container: groupContainer, + id: request.params.id, + logger: request.log, + }) + + const combine = async (group: Group, listing: GroupListing) => { + if (request.viewer) { + const memberships = await queryItems({ + container: groupContainer, + query: createQuerySpec( + ` + SELECT * FROM Groups g WHERE + g.pk = @pk AND + g.t = @type AND + g.userId = @userId AND + g.pending = false + `, + { + pk: group.id, + type: GroupItemType.Membership, + userId: request.viewer.id, + } + ), + logger: request.log, + }) + + if (memberships.length > 0) { + return merge(group, listing, { + membership: memberships[0].membership, + }) + } + } + + return merge(group, listing) + } + + if (!group || !listing) return notFoundError(reply) + return combine(group, listing) }) } @@ -240,15 +296,15 @@ function blockRoute(server: FastifyInstance({ blockedId: group.id, pk: request.viewer.id, - t: 'block', - blockType: 'group', + t: UserItemType.Block, + blockType: BlockType.Group, description: request.body.description, created: Date.now(), }) await groupContainer.items.create({ pk: group.id, - t: 'block', + t: GroupItemType.Block, blockedId: group.id, userId: request.viewer.id, created: Date.now(), @@ -288,9 +344,10 @@ function unblockRoute(server: FastifyInstance({ @@ -303,11 +360,12 @@ function unblockRoute(server: FastifyInstance({ ...group, active: true, - status: 'paid', + status: GroupStatus.Paid, }) const directoryContainer = containerFor(server.database.client, 'GroupDirectory') - const listingItem = directoryContainer.item(request.params.id, 'pk') + const listingItem = directoryContainer.item(request.params.id, GROUP_LISTING_PARTITION_KEY) const { resource: listing, requestCharge: listingRequestCharge } = await listingItem.read() request.log.trace('Get: %d', listingRequestCharge) @@ -371,7 +429,7 @@ function activateRoute(server: FastifyInstance({ id: group.id, name: group.name, - pk: 'pk', + pk: GROUP_LISTING_PARTITION_KEY, registration: group.registration, members: 1, posts: 0, @@ -436,7 +494,7 @@ function listRoute(server: FastifyInstance( - `SELECT * FROM GroupDirectory d WHERE d.pk = 'pk' ${registrationString} ORDER BY d.${sort}`, + `SELECT * FROM GroupDirectory d WHERE d.pk = ${GROUP_LISTING_PARTITION_KEY} ${registrationString} ORDER BY d.${sort}`, { maxItemCount: 40, continuation, diff --git a/src/plugins/api/posts.ts b/src/plugins/api/posts.ts index d6d25d3..053ed9f 100644 --- a/src/plugins/api/posts.ts +++ b/src/plugins/api/posts.ts @@ -27,6 +27,10 @@ import { GroupBlock, PostRelationship, Status, + PostItemType, + UserItemType, + UserPrivacyType, + GroupItemType, } from '../../types/collections' import { PluginOptions } from '../../types' @@ -151,7 +155,7 @@ function doPostRoute(server: FastifyInstance(newPostRelationship) - const query = createQuerySpec(`SELECT u.id FROM Users u WHERE u.pk = @pk AND u.type = 'subscription'`, { pk: request.viewer.id }) + const query = createQuerySpec(`SELECT u.id FROM Users u WHERE u.pk = @pk AND u.t = @type`, { pk: request.viewer.id, type: UserItemType.Subscription }) const subscribers = await queryItems({ container: userContainer, query, @@ -188,7 +192,7 @@ function doPostRoute(server: FastifyInstance({ postId, pk: subscriber.id!, - t: 'timeline', + t: UserItemType.Timeline, created: Date.now(), }) } @@ -244,9 +248,9 @@ function postsByUserRoute(server: FastifyInstance({ @@ -287,7 +291,7 @@ function postsByUserRoute(server: FastifyInstance 0) return unauthorizedError(reply) } - const userPostsQuery = createQuerySpec(`SELECT p.id FROM Users p WHERE p.pk = @user AND p.type = 'post'`, { user: id }) + const userPostsQuery = createQuerySpec(`SELECT p.id FROM Users p WHERE p.pk = @user AND p.t = @type`, { user: id, type: UserItemType.Post }) const userPosts = await queryItems({ container: userContainer, query: userPostsQuery, @@ -408,13 +412,14 @@ function postRoute(server: FastifyInstance({ diff --git a/src/plugins/api/users.ts b/src/plugins/api/users.ts index 6b4814d..20fae27 100644 --- a/src/plugins/api/users.ts +++ b/src/plugins/api/users.ts @@ -10,13 +10,24 @@ import { import { Server, IncomingMessage, ServerResponse } from 'http' -import { MAX_NAME_LENGTH } from '../../constants' -import { userSchema, errorSchema } from '../../schemas' import { unauthorizedError, serverError, notFoundError, badRequestError } from '../../lib/errors' import { getUserBlocks } from '../../lib/collections' import { containerFor, createQuerySpec, queryItems, getItem, normalize } from '../../lib/database' -import { User, UserSubscription, UserBlock, GroupBlock, UserPrivacyType } from '../../types/collections' +import { MAX_NAME_LENGTH } from '../../constants' +import { userSchema, errorSchema } from '../../schemas' + +import { + User, + UserSubscription, + UserBlock, + GroupBlock, + UserPrivacyType, + UserItemType, + GroupItemType, + BlockType, +} from '../../types/collections' + import { PluginOptions } from '../../types' function availabilityRoute(server: FastifyInstance) { @@ -220,9 +231,10 @@ function subscribeRoute(server: FastifyInstance({ container: userContainer, query: subscriptionQuery, logger: request.log }) @@ -231,11 +243,11 @@ function subscribeRoute(server: FastifyInstance({ @@ -262,7 +275,7 @@ function subscribeRoute(server: FastifyInstance({ subscriberId: user.id, pk: request.viewer.id, - t: 'subscription', + t: UserItemType.Subscription, pending, created: Date.now(), }) @@ -298,9 +311,10 @@ function unsubscribeRoute(server: FastifyInstance({ container: userContainer, query: subscriptionQuery, logger: request.log }) @@ -358,15 +372,15 @@ function blockRoute(server: FastifyInstance({ blockedId: user.id, pk: request.viewer.id, - t: 'block', - blockType: 'user', + t: UserItemType.Block, + blockType: BlockType.User, description: request.body.description, created: Date.now(), }) await containerFor(server.database.client, 'Groups').items.create({ pk: user.group.id, - t: 'block', + t: GroupItemType.Block, blockedId: user.id, userId: request.viewer.id, created: Date.now(), @@ -408,9 +422,10 @@ function unblockRoute(server: FastifyInstance({ @@ -424,11 +439,12 @@ function unblockRoute(server: FastifyInstance