Dwayne Harris 5 years ago
parent
commit
2f3d4a2bcd
  1. 1
      src/constants.ts
  2. 4
      src/lib/authentication.ts
  3. 29
      src/plugins/api/authentication.ts
  4. 92
      src/plugins/api/groups.ts
  5. 23
      src/plugins/api/posts.ts
  6. 46
      src/plugins/api/users.ts
  7. 2
      src/schemas.ts
  8. 106
      src/types/collections.ts

1
src/constants.ts

@ -4,3 +4,4 @@ export const MAX_NAME_LENGTH = 80
export const MIN_PASSWORD_LENGTH = 8 export const MIN_PASSWORD_LENGTH = 8
export const SHORT_TEXT_LENGTH = 100 export const SHORT_TEXT_LENGTH = 100
export const SUBSCRIBER_MAX_SIZE = 100 export const SUBSCRIBER_MAX_SIZE = 100
export const GROUP_LISTING_PARTITION_KEY = 'pk'

4
src/lib/authentication.ts

@ -1,6 +1,6 @@
import { v1 } from 'uuid' import { v1 } from 'uuid'
import { JWT } from '../lib/crypto' import { JWT } from '../lib/crypto'
import { UserToken } from '../types/collections'
import { UserToken, UserItemType } from '../types/collections'
export async function createAccessToken(userId: string): Promise<string> { export async function createAccessToken(userId: string): Promise<string> {
return await JWT.sign({ sub: userId }, { expiresIn: process.env.TOKEN_EXPIRATION }) 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 { return {
id: 'r' + v1().replace(/-/g, ''), id: 'r' + v1().replace(/-/g, ''),
pk: userId, pk: userId,
t: 'token',
t: UserItemType.Token,
userAgent, userAgent,
ip, ip,
expires: Date.now() + (1000 * 60 * 60 * 24 * 365), expires: Date.now() + (1000 * 60 * 60 * 24 * 365),

29
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 { badRequestError, badRequestFormError, unauthorizedError, serverError } from '../../lib/errors'
import { tokenFromHeader } from '../../lib/http' 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' import { PluginOptions } from '../../types'
function registerRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) { function registerRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) {
@ -100,13 +112,14 @@ function registerRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
SELECT g.id FROM Groups g WHERE SELECT g.id FROM Groups g WHERE
g.id = @invitation g.id = @invitation
g.pk = @group AND g.pk = @group AND
g.type = 'invitation' AND
g.t = @type AND
g.active = true AND g.active = true AND
g.expiration < GETCURRENTTIMESTAMP() AND g.expiration < GETCURRENTTIMESTAMP() AND
g.uses < g.limit g.uses < g.limit
`, { `, {
invitation, invitation,
group: group.id, group: group.id,
type: GroupItemType.Invitation,
}) })
const invitations = await queryItems<GroupInvitation>({ const invitations = await queryItems<GroupInvitation>({
@ -118,8 +131,8 @@ function registerRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
if (invitations.length === 0) return badRequestFormError(reply, 'invitation', 'Invalid invitation code') if (invitations.length === 0) return badRequestFormError(reply, 'invitation', 'Invalid invitation code')
} }
if (group.registration === 'closed' && !invitation) return badRequestFormError(reply, 'group', 'Group registration closed')
if (group.registration === 'approval') userPending = true
if (group.registration === GroupRegistrationType.Closed && !invitation) return badRequestFormError(reply, 'group', 'Group registration closed')
if (group.registration === GroupRegistrationType.Approval) userPending = true
groupPartial = { groupPartial = {
id: group.id, id: group.id,
@ -132,7 +145,7 @@ function registerRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
const user: User = { const user: User = {
id, id,
pk: id, pk: id,
t: 'user',
t: UserItemType.User,
group: groupPartial, group: groupPartial,
name, name,
about: '', about: '',
@ -147,7 +160,7 @@ function registerRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
subscribedCount: 0, subscribedCount: 0,
pending: userPending, pending: userPending,
requiresApproval: false, requiresApproval: false,
privacy: 'public',
privacy: UserPrivacyType.Public,
paid: false, paid: false,
active: true, active: true,
created: Date.now(), created: Date.now(),
@ -161,10 +174,10 @@ function registerRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
if (group) { if (group) {
await groupContainer.items.create<GroupMembership>({ await groupContainer.items.create<GroupMembership>({
pk: group.id, pk: group.id,
t: 'membership',
t: GroupItemType.Membership,
userId: user.id, userId: user.id,
pending: userPending, pending: userPending,
membership: 'member',
membership: GroupMembershipType.Member,
invitation, invitation,
created: Date.now(), created: Date.now(),
}) })

92
src/plugins/api/groups.ts

@ -8,13 +8,29 @@ import {
DefaultHeaders, DefaultHeaders,
} from 'fastify' } from 'fastify'
import merge from 'lodash/merge'
import { Server, IncomingMessage, ServerResponse } from 'http' 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 { errorSchema, groupListingSchema } from '../../schemas'
import { unauthorizedError, badRequestError, notFoundError, serverError } from '../../lib/errors' import { unauthorizedError, badRequestError, notFoundError, serverError } from '../../lib/errors'
import { containerFor, createQuerySpec, queryItems, getItem, normalize } from '../../lib/database' 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' import { PluginOptions } from '../../types'
function availabilityRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) { function availabilityRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) {
@ -127,12 +143,12 @@ function createRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
const group: Group = { const group: Group = {
id: id, id: id,
pk: id, pk: id,
t: 'group',
t: GroupItemType.Group,
userId: request.viewer.id, userId: request.viewer.id,
name, name,
about, about,
registration, registration,
status: 'pending',
status: GroupStatus.Pending,
active: true, active: true,
created: Date.now(), created: Date.now(),
} }
@ -140,10 +156,10 @@ function createRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
const membership: GroupMembership = { const membership: GroupMembership = {
id: request.viewer.id, id: request.viewer.id,
pk: id, pk: id,
t: 'membership',
t: GroupItemType.Membership,
userId: request.viewer.id, userId: request.viewer.id,
pending: false, pending: false,
membership: 'admin',
membership: GroupMembershipType.Admin,
created: Date.now(), created: Date.now(),
} }
@ -188,14 +204,54 @@ function getRoute(server: FastifyInstance<Server, IncomingMessage, ServerRespons
server.get<DefaultQuery, Params, DefaultHeaders, DefaultBody>('/api/group/:id', options, async (request, reply) => { server.get<DefaultQuery, Params, DefaultHeaders, DefaultBody>('/api/group/:id', options, async (request, reply) => {
if (!server.database) return serverError(reply) if (!server.database) return serverError(reply)
const groupContainer = containerFor(server.database.client, 'Groups')
const listing = await getItem<GroupListing>({ const listing = await getItem<GroupListing>({
container: containerFor(server.database.client, 'GroupDirectory'), container: containerFor(server.database.client, 'GroupDirectory'),
id: request.params.id, id: request.params.id,
partitionKey: 'pk',
logger: request.log, logger: request.log,
}) })
if (!listing) return notFoundError(reply)
return listing
const group = await getItem<Group>({
container: groupContainer,
id: request.params.id,
logger: request.log,
})
const combine = async (group: Group, listing: GroupListing) => {
if (request.viewer) {
const memberships = await queryItems<GroupMembership>({
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<Server, IncomingMessage, ServerRespo
await containerFor(server.database.client, 'Users').items.create<UserBlock>({ await containerFor(server.database.client, 'Users').items.create<UserBlock>({
blockedId: group.id, blockedId: group.id,
pk: request.viewer.id, pk: request.viewer.id,
t: 'block',
blockType: 'group',
t: UserItemType.Block,
blockType: BlockType.Group,
description: request.body.description, description: request.body.description,
created: Date.now(), created: Date.now(),
}) })
await groupContainer.items.create<GroupBlock>({ await groupContainer.items.create<GroupBlock>({
pk: group.id, pk: group.id,
t: 'block',
t: GroupItemType.Block,
blockedId: group.id, blockedId: group.id,
userId: request.viewer.id, userId: request.viewer.id,
created: Date.now(), created: Date.now(),
@ -288,9 +344,10 @@ function unblockRoute(server: FastifyInstance<Server, IncomingMessage, ServerRes
}) })
if (!group) return notFoundError(reply) if (!group) return notFoundError(reply)
const userBlockQuery = createQuerySpec(`SELECT u.id FROM Users u WHERE u.pk = @pk AND u.blockedId = @blocked AND u.type = 'block'`, {
const userBlockQuery = createQuerySpec(`SELECT u.id FROM Users u WHERE u.pk = @pk AND u.blockedId = @blocked AND u.t = @type`, {
pk: request.viewer.id, pk: request.viewer.id,
blocked: group.id, blocked: group.id,
type: UserItemType.Block,
}) })
const userBlocks = await queryItems<UserBlock>({ const userBlocks = await queryItems<UserBlock>({
@ -303,11 +360,12 @@ function unblockRoute(server: FastifyInstance<Server, IncomingMessage, ServerRes
} }
const groupBlockQuery = createQuerySpec( const groupBlockQuery = createQuerySpec(
`SELECT g.id FROM Groups g WHERE g.pk = @pk AND u.blockedId = @blocked AND u.userId = @viewer AND u.type = 'block'`,
`SELECT g.id FROM Groups g WHERE g.pk = @pk AND u.blockedId = @blocked AND u.userId = @viewer AND u.t = @type`,
{ {
pk: group.id, pk: group.id,
blocked: group.id, blocked: group.id,
viewer: request.viewer.id, viewer: request.viewer.id,
type: GroupItemType.Block,
} }
) )
@ -359,11 +417,11 @@ function activateRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
await groupItem.replace<Group>({ await groupItem.replace<Group>({
...group, ...group,
active: true, active: true,
status: 'paid',
status: GroupStatus.Paid,
}) })
const directoryContainer = containerFor(server.database.client, 'GroupDirectory') 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<GroupListing>() const { resource: listing, requestCharge: listingRequestCharge } = await listingItem.read<GroupListing>()
request.log.trace('Get: %d', listingRequestCharge) request.log.trace('Get: %d', listingRequestCharge)
@ -371,7 +429,7 @@ function activateRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
await directoryContainer.items.create<GroupListing>({ await directoryContainer.items.create<GroupListing>({
id: group.id, id: group.id,
name: group.name, name: group.name,
pk: 'pk',
pk: GROUP_LISTING_PARTITION_KEY,
registration: group.registration, registration: group.registration,
members: 1, members: 1,
posts: 0, posts: 0,
@ -436,7 +494,7 @@ function listRoute(server: FastifyInstance<Server, IncomingMessage, ServerRespon
const container = containerFor(server.database.client, 'GroupDirectory') const container = containerFor(server.database.client, 'GroupDirectory')
const { resources: groups, requestCharge, continuation: newContinuation } = await container.items.query<GroupListing>( const { resources: groups, requestCharge, continuation: newContinuation } = await container.items.query<GroupListing>(
`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, maxItemCount: 40,
continuation, continuation,

23
src/plugins/api/posts.ts

@ -27,6 +27,10 @@ import {
GroupBlock, GroupBlock,
PostRelationship, PostRelationship,
Status, Status,
PostItemType,
UserItemType,
UserPrivacyType,
GroupItemType,
} from '../../types/collections' } from '../../types/collections'
import { PluginOptions } from '../../types' import { PluginOptions } from '../../types'
@ -151,7 +155,7 @@ function doPostRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
const post: Post = { const post: Post = {
id: postId, id: postId,
pk: postId, pk: postId,
t: 'post',
t: PostItemType.Post,
userId: request.viewer.id, userId: request.viewer.id,
root: newPostRelationship ? newPostRelationship.pk : postId, root: newPostRelationship ? newPostRelationship.pk : postId,
parents: newPostRelationship ? newPostRelationship.parents : [], parents: newPostRelationship ? newPostRelationship.parents : [],
@ -167,7 +171,7 @@ function doPostRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
const userPost: UserPost = { const userPost: UserPost = {
postId, postId,
pk: request.viewer.id, pk: request.viewer.id,
t: 'post',
t: UserItemType.Post,
created: Date.now(), created: Date.now(),
} }
@ -176,7 +180,7 @@ function doPostRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
if (newPostRelationship) await ancestryContainer.items.create<PostRelationship>(newPostRelationship) if (newPostRelationship) await ancestryContainer.items.create<PostRelationship>(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<UserSubscription>({ const subscribers = await queryItems<UserSubscription>({
container: userContainer, container: userContainer,
query, query,
@ -188,7 +192,7 @@ function doPostRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
await userContainer.items.create<UserTimelinePost>({ await userContainer.items.create<UserTimelinePost>({
postId, postId,
pk: subscriber.id!, pk: subscriber.id!,
t: 'timeline',
t: UserItemType.Timeline,
created: Date.now(), created: Date.now(),
}) })
} }
@ -244,9 +248,9 @@ function postsByUserRoute(server: FastifyInstance<Server, IncomingMessage, Serve
if (!user.group) return notFoundError(reply) if (!user.group) return notFoundError(reply)
switch (user.privacy) { switch (user.privacy) {
case 'private':
case UserPrivacyType.Private:
return unauthorizedError(reply) return unauthorizedError(reply)
case 'subscribers': {
case UserPrivacyType.Subscribers: {
if (!request.viewer) return unauthorizedError(reply) if (!request.viewer) return unauthorizedError(reply)
const subscriptions = await getApprovedSubscriptions(server.database.client, user.id, request.viewer.id, request.log) const subscriptions = await getApprovedSubscriptions(server.database.client, user.id, request.viewer.id, request.log)
@ -254,7 +258,7 @@ function postsByUserRoute(server: FastifyInstance<Server, IncomingMessage, Serve
break break
} }
case 'group': {
case UserPrivacyType.Group: {
if (!request.viewer) return unauthorizedError(reply) if (!request.viewer) return unauthorizedError(reply)
const viewer = await getItem<User>({ const viewer = await getItem<User>({
@ -287,7 +291,7 @@ function postsByUserRoute(server: FastifyInstance<Server, IncomingMessage, Serve
if (blocks.length > 0) return unauthorizedError(reply) if (blocks.length > 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<UserPost>({ const userPosts = await queryItems<UserPost>({
container: userContainer, container: userContainer,
query: userPostsQuery, query: userPostsQuery,
@ -408,13 +412,14 @@ function postRoute(server: FastifyInstance<Server, IncomingMessage, ServerRespon
const blockQuery = createQuerySpec(` const blockQuery = createQuerySpec(`
SELECT g.userId FROM Groups g WHERE SELECT g.userId FROM Groups g WHERE
g.pk = @viewerGroup AND g.pk = @viewerGroup AND
g.t = 'block' AND
g.t = @type AND
(g.blockedId = @viewer OR g.blockedId = @viewerGroup) (g.blockedId = @viewer OR g.blockedId = @viewerGroup)
ARRAY_CONTAINS(@ids, g.userId) ARRAY_CONTAINS(@ids, g.userId)
`, { `, {
viewer: viewer.id, viewer: viewer.id,
viewerGroup: viewer.group.id, viewerGroup: viewer.group.id,
ids: userIds, ids: userIds,
type: GroupItemType.Block,
}) })
const blocks = await queryItems<GroupBlock>({ const blocks = await queryItems<GroupBlock>({

46
src/plugins/api/users.ts

@ -10,13 +10,24 @@ import {
import { Server, IncomingMessage, ServerResponse } from 'http' 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 { unauthorizedError, serverError, notFoundError, badRequestError } from '../../lib/errors'
import { getUserBlocks } from '../../lib/collections' import { getUserBlocks } from '../../lib/collections'
import { containerFor, createQuerySpec, queryItems, getItem, normalize } from '../../lib/database' 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' import { PluginOptions } from '../../types'
function availabilityRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) { function availabilityRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) {
@ -220,9 +231,10 @@ function subscribeRoute(server: FastifyInstance<Server, IncomingMessage, ServerR
if (!viewer) return serverError(reply) if (!viewer) return serverError(reply)
if (!viewer.group) return unauthorizedError(reply) if (!viewer.group) return unauthorizedError(reply)
const subscriptionQuery = createQuerySpec(`SELECT u.id FROM Users u WHERE u.subscriberId = @user AND u.pk = @viewer AND u.t = 'subscription'`, {
const subscriptionQuery = createQuerySpec(`SELECT u.id FROM Users u WHERE u.subscriberId = @user AND u.pk = @viewer AND u.t = @type`, {
user: user.id, user: user.id,
viewer: viewer.id, viewer: viewer.id,
type: UserItemType.Subscription,
}) })
const subscriptions = await queryItems<UserSubscription>({ container: userContainer, query: subscriptionQuery, logger: request.log }) const subscriptions = await queryItems<UserSubscription>({ container: userContainer, query: subscriptionQuery, logger: request.log })
@ -231,11 +243,11 @@ function subscribeRoute(server: FastifyInstance<Server, IncomingMessage, ServerR
let pending = false let pending = false
switch (user.privacy) { switch (user.privacy) {
case 'private':
case UserPrivacyType.Private:
return unauthorizedError(reply) return unauthorizedError(reply)
case 'group':
case UserPrivacyType.Group:
if (user.group !== viewer.group) return unauthorizedError(reply) if (user.group !== viewer.group) return unauthorizedError(reply)
case 'subscribers':
case UserPrivacyType.Subscribers:
pending = true pending = true
break break
} }
@ -243,12 +255,13 @@ function subscribeRoute(server: FastifyInstance<Server, IncomingMessage, ServerR
const blockQuery = createQuerySpec(` const blockQuery = createQuerySpec(`
SELECT g.id FROM Groups g WHERE SELECT g.id FROM Groups g WHERE
g.pk = @viewerGroup AND g.pk = @viewerGroup AND
g.t = 'block' AND
g.t = @type AND
g.userId = @user AND g.userId = @user AND
(g.blockedId = @viewer OR g.blockedId = @viewerGroup) (g.blockedId = @viewer OR g.blockedId = @viewerGroup)
`, { `, {
user: user.id, user: user.id,
viewerGroup: viewer.group.id, viewerGroup: viewer.group.id,
type: GroupItemType.Block,
}) })
const blocks = await queryItems<GroupBlock>({ const blocks = await queryItems<GroupBlock>({
@ -262,7 +275,7 @@ function subscribeRoute(server: FastifyInstance<Server, IncomingMessage, ServerR
await userContainer.items.create<UserSubscription>({ await userContainer.items.create<UserSubscription>({
subscriberId: user.id, subscriberId: user.id,
pk: request.viewer.id, pk: request.viewer.id,
t: 'subscription',
t: UserItemType.Subscription,
pending, pending,
created: Date.now(), created: Date.now(),
}) })
@ -298,9 +311,10 @@ function unsubscribeRoute(server: FastifyInstance<Server, IncomingMessage, Serve
if (!user) return notFoundError(reply) if (!user) return notFoundError(reply)
if (!viewer) return serverError(reply) if (!viewer) return serverError(reply)
const subscriptionQuery = createQuerySpec(`SELECT u.id FROM Users u WHERE u.subscriberId = @user AND u.pk = @viewer AND u.t = 'subscription'`, {
const subscriptionQuery = createQuerySpec(`SELECT u.id FROM Users u WHERE u.subscriberId = @user AND u.pk = @viewer AND u.t = @type`, {
user: user.id, user: user.id,
viewer: viewer.id, viewer: viewer.id,
type: UserItemType.Subscription,
}) })
const subscriptions = await queryItems<UserSubscription>({ container: userContainer, query: subscriptionQuery, logger: request.log }) const subscriptions = await queryItems<UserSubscription>({ container: userContainer, query: subscriptionQuery, logger: request.log })
@ -358,15 +372,15 @@ function blockRoute(server: FastifyInstance<Server, IncomingMessage, ServerRespo
await userContainer.items.create<UserBlock>({ await userContainer.items.create<UserBlock>({
blockedId: user.id, blockedId: user.id,
pk: request.viewer.id, pk: request.viewer.id,
t: 'block',
blockType: 'user',
t: UserItemType.Block,
blockType: BlockType.User,
description: request.body.description, description: request.body.description,
created: Date.now(), created: Date.now(),
}) })
await containerFor(server.database.client, 'Groups').items.create<GroupBlock>({ await containerFor(server.database.client, 'Groups').items.create<GroupBlock>({
pk: user.group.id, pk: user.group.id,
t: 'block',
t: GroupItemType.Block,
blockedId: user.id, blockedId: user.id,
userId: request.viewer.id, userId: request.viewer.id,
created: Date.now(), created: Date.now(),
@ -408,9 +422,10 @@ function unblockRoute(server: FastifyInstance<Server, IncomingMessage, ServerRes
if (!user) return notFoundError(reply) if (!user) return notFoundError(reply)
if (!user.group) return badRequestError(reply, 'Invalid operation') if (!user.group) return badRequestError(reply, 'Invalid operation')
const userBlockQuery = createQuerySpec(`SELECT u.id FROM Users u WHERE u.pk = @pk AND u.blockedId = @blocked AND u.type = 'block'`, {
const userBlockQuery = createQuerySpec(`SELECT u.id FROM Users u WHERE u.pk = @pk AND u.blockedId = @blocked AND u.t = @type`, {
pk: request.viewer.id, pk: request.viewer.id,
blocked: user.id, blocked: user.id,
type: UserItemType.Block,
}) })
const userBlocks = await queryItems<UserBlock>({ const userBlocks = await queryItems<UserBlock>({
@ -424,11 +439,12 @@ function unblockRoute(server: FastifyInstance<Server, IncomingMessage, ServerRes
} }
const groupBlockQuery = createQuerySpec( const groupBlockQuery = createQuerySpec(
`SELECT g.id FROM Groups g WHERE g.pk = @pk AND u.blockedId = @blocked AND u.userId = @viewer AND u.type = 'block'`,
`SELECT g.id FROM Groups g WHERE g.pk = @pk AND u.blockedId = @blocked AND u.userId = @viewer AND u.t = @type`,
{ {
pk: user.group.id, pk: user.group.id,
blocked: user.id, blocked: user.id,
viewer: request.viewer.id, viewer: request.viewer.id,
type: GroupItemType.Block,
} }
) )

2
src/schemas.ts

@ -67,6 +67,8 @@ export const groupListingSchema: JSONSchema = {
posts: { type: 'number' }, posts: { type: 'number' },
awards: { type: 'number' }, awards: { type: 'number' },
points: { type: 'number' }, points: { type: 'number' },
created: { type: 'number' },
membership: { type: 'string' },
latestAwards: { latestAwards: {
type: 'array', type: 'array',
items: awardSchema, items: awardSchema,

106
src/types/collections.ts

@ -12,20 +12,74 @@
// - Partition Key: pk (postId) // - Partition Key: pk (postId)
// Points: total reward value + likes // Points: total reward value + likes
import { GROUP_LISTING_PARTITION_KEY } from '../constants'
export type UserItemType = 'user' | 'token' | 'post' | 'follow' | 'timeline'
export type UserPrivacyType = 'public' | 'group' | 'subscribers' | 'private'
export type UserTransactionType = 'purchase' | 'award'
export type GroupStatus = 'pending' | 'paid'
export type GroupRegistrationType = 'open' | 'approval' | 'closed'
export type GroupItemType = 'group' | 'membership' | 'report' | 'block'
export type GroupMembershipType = 'admin' | 'moderator' | 'member'
export type ReportStatus = 'pending' | 'complete'
export type BlockType = 'user' | 'group'
export enum UserItemType {
User = 'user',
Token = 'token',
Post = 'post',
Follow = 'follow',
Timeline = 'timeline',
Subscription = 'subscription',
Block = 'block',
Transaction = 'transation',
}
export enum UserPrivacyType {
Public = 'public',
Group = 'group',
Subscribers = 'subscribers',
Private = 'private',
}
export enum UserTransactionType {
Purchase = 'purchase',
Award = 'award',
}
export enum GroupStatus {
Pending = 'pending',
Paid = 'paid',
}
export enum GroupRegistrationType {
Open = 'open',
Approval = 'approval',
Closed = 'closed',
}
export enum GroupItemType {
Group = 'group',
Membership = 'membership',
Report = 'report',
Block = 'block',
Invitation = 'invitation',
}
export enum GroupMembershipType {
Admin = 'admin',
Moderator = 'moderator',
Member = 'member',
}
export enum ReportStatus {
Pending = 'pending',
Complete = 'complete',
}
export enum BlockType {
User = 'user',
Group = 'group',
}
export enum PostItemType {
Post = 'post',
Award = 'award',
}
export interface GroupListing { export interface GroupListing {
id: string id: string
pk: 'pk'
pk: typeof GROUP_LISTING_PARTITION_KEY
name: string name: string
about?: string about?: string
registration: GroupRegistrationType registration: GroupRegistrationType
@ -40,7 +94,7 @@ export interface GroupListing {
export interface Group { export interface Group {
id: string id: string
pk: string // ID pk: string // ID
t: 'group'
t: GroupItemType.Group
userId: string userId: string
name: string // Prenormalized ID name: string // Prenormalized ID
about?: string about?: string
@ -63,7 +117,7 @@ export interface GroupPartial {
export interface GroupMembership { export interface GroupMembership {
id?: string id?: string
pk: string // Group ID pk: string // Group ID
t: 'membership'
t: GroupItemType.Membership
userId: string userId: string
pending: boolean pending: boolean
membership: GroupMembershipType membership: GroupMembershipType
@ -74,7 +128,7 @@ export interface GroupMembership {
export interface GroupInvitation { export interface GroupInvitation {
id: string id: string
pk: string // Group ID pk: string // Group ID
t: 'invitation'
t: GroupItemType.Invitation
userId: string userId: string
limit: number limit: number
expiration: number expiration: number
@ -86,7 +140,7 @@ export interface GroupInvitation {
export interface GroupReport { export interface GroupReport {
id: string id: string
pk: string // Group ID pk: string // Group ID
t: 'report'
t: GroupItemType.Report
postId: string postId: string
reportedById: string reportedById: string
description?: string description?: string
@ -97,7 +151,7 @@ export interface GroupReport {
export interface GroupBlock { export interface GroupBlock {
id?: string id?: string
pk: string // Group ID pk: string // Group ID
t: 'block'
t: GroupItemType.Block
blockedId: string // User or Group ID blockedId: string // User or Group ID
userId: string // blocker ID userId: string // blocker ID
created: number created: number
@ -106,7 +160,7 @@ export interface GroupBlock {
export interface User { export interface User {
id: string id: string
pk: string // ID pk: string // ID
t: 'user'
t: UserItemType.User
group?: GroupPartial group?: GroupPartial
name: string name: string
about?: string about?: string
@ -132,7 +186,7 @@ export interface User {
export interface UserToken { export interface UserToken {
id: string id: string
pk: string // userId pk: string // userId
t: 'token'
t: UserItemType.Token
userAgent: string userAgent: string
ip: string ip: string
expires: number expires: number
@ -143,7 +197,7 @@ export interface UserPost {
id?: string id?: string
postId: string postId: string
pk: string // userId pk: string // userId
t: 'post'
t: UserItemType.Post
created: number created: number
} }
@ -151,7 +205,7 @@ export interface UserSubscription {
id?: string id?: string
subscriberId: string subscriberId: string
pk: string pk: string
t: 'subscription'
t: UserItemType.Subscription
pending: boolean pending: boolean
created: number created: number
} }
@ -160,16 +214,16 @@ export interface UserBlock {
id?: string id?: string
blockedId: string blockedId: string
pk: string pk: string
t: 'block'
t: UserItemType.Block
blockType: BlockType blockType: BlockType
description?: string description?: string
created: number created: number
} }
export interface IUserTransaction {
export interface UserTransaction {
id: string id: string
pk: string pk: string
t: 'transaction'
t: UserItemType.Transaction
transactionType: UserTransactionType transactionType: UserTransactionType
fromUserId: string fromUserId: string
toUserId: string toUserId: string
@ -181,7 +235,7 @@ export interface UserTimelinePost {
id?: string id?: string
postId: string postId: string
pk: string // userId pk: string // userId
t: 'timeline'
t: UserItemType.Timeline
created: number created: number
} }
@ -200,7 +254,7 @@ export interface Status {
export interface Post { export interface Post {
id: string id: string
pk: string // postId pk: string // postId
t: 'post'
t: PostItemType.Post
userId: string userId: string
root: string root: string
parents: string[] // Post IDs parents: string[] // Post IDs
@ -214,10 +268,10 @@ export interface Post {
created: number created: number
} }
export interface IPostAward {
export interface PostAward {
id: string id: string
pk: string // postId pk: string // postId
t: 'award'
t: PostItemType.Award
userId: string userId: string
imageUrl: string imageUrl: string
text: string text: string

Loading…
Cancel
Save