Dwayne Harris 5 years ago
parent
commit
4e4cfdcd9d
  1. 2
      package.json
  2. 22
      src/awards.ts
  3. 4
      src/lib/authentication.ts
  4. 11
      src/lib/collections.ts
  5. 9
      src/lib/database.ts
  6. 9
      src/plugins/api/authentication.ts
  7. 160
      src/plugins/api/groups.ts
  8. 37
      src/plugins/api/posts.ts
  9. 28
      src/plugins/api/users.ts
  10. 30
      src/schemas.ts
  11. 122
      src/types/collections.ts
  12. 16
      src/types/index.ts

2
package.json

@ -8,7 +8,7 @@
"build": "tsc",
"watch-server": "nodemon dist/server.js",
"watch-typescript": "tsc -w",
"watch": "npm-run-all --parallel watch-typescript watch-server"
"watch": "run-p watch-typescript watch-server"
},
"devDependencies": {
"@types/dotenv": "^6.1.1",

22
src/awards.ts

@ -0,0 +1,22 @@
import { IAwardDefinition } from './types'
const awards: IAwardDefinition[] = [
{
id: 'like',
name: 'Like',
imageUrl: '',
text: '',
cost: 0,
reward: 0,
},
{
id: 'bronze',
name: 'Bronze',
imageUrl: '',
text: 'Bronze Award!',
cost: 2000,
reward: 200,
},
]
export default awards

4
src/lib/authentication.ts

@ -9,8 +9,8 @@ export async function createAccessToken(userId: string): Promise<string> {
export function createRefreshToken(userId: string, userAgent: string, ip: string): IUserToken {
return {
id: 'r' + v1().replace(/-/g, ''),
partitionKey: userId,
type: 'token',
pk: userId,
t: 'token',
userAgent,
ip,
expires: Date.now() + (1000 * 60 * 60 * 24 * 365),

11
src/lib/collections.ts

@ -1,8 +1,9 @@
import { CosmosClient } from '@azure/cosmos'
import { Logger } from 'fastify'
import uniq from 'lodash/uniq'
import { IDatabaseItem } from '../types'
import { containerFor, createQuerySpec, queryItems, IDatabaseItem } from './database'
import { containerFor, createQuerySpec, queryItems } from './database'
import { IUser, IUserSubscription, IUserBlock } from '../types/collections'
export async function getUsers(client: CosmosClient, ids: string[], logger?: Logger): Promise<IUser[]> {
@ -20,8 +21,8 @@ export async function getApprovedSubscriptions(client: CosmosClient, from: strin
return await queryItems<IUserSubscription>(containerFor(client, 'Users'), createQuerySpec(`
SELECT u.id FROM Users u WHERE
u.subscriberId = @to
u.partitionKey = @from AND
u.type = 'subscription' AND
u.pk = @from AND
u.t = 'subscription' AND
u.pending = false
`, { from, to }), logger)
}
@ -29,8 +30,8 @@ export async function getApprovedSubscriptions(client: CosmosClient, from: strin
export async function getUserBlocks(client: CosmosClient, from: string, to: string[], logger?: Logger): Promise<IUserBlock[]> {
return await queryItems<IUserBlock>(containerFor(client, 'Users'), createQuerySpec(`
SELECT u.id FROM Users u WHERE
u.partitionKey = @from
AND u.type = 'block'
u.pk = @from
AND u.t = 'block'
AND ARRAY_CONTAINS(@to, u.blockedId)
`, { from, to }), logger)
}

9
src/lib/database.ts

@ -1,13 +1,6 @@
import { CosmosClient, Container, SqlQuerySpec } from '@azure/cosmos'
import { Logger } from 'fastify'
interface IQueryParams {
[key: string]: string | string[] | number | boolean
}
export interface IDatabaseItem {
id: string
}
import { IQueryParams } from '../types'
export function containerFor(client: CosmosClient, containerId: string): Container {
return client.database('Flexor').container(containerId)

9
src/plugins/api/authentication.ts

@ -94,22 +94,25 @@ function registerRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
const user: IUser = {
id,
partitionKey: id,
type: 'user',
pk: id,
t: 'user',
group: groupPartial,
name,
about: '',
email,
emailVerified: false,
passwordHash: await hashPassword(password),
installations: [],
awards: 0,
points: 0,
balance: 0,
subscriberCount: 0,
subscribedCount: 0,
pending: userPending,
requiresApproval: false,
privacy: 'public',
paid: false,
about: '',
active: true,
created: Date.now(),
}

160
src/plugins/api/groups.ts

@ -3,6 +3,7 @@ import {
Plugin,
DefaultQuery,
DefaultParams,
DefaultBody,
RouteShorthandOptions,
DefaultHeaders,
} from 'fastify'
@ -10,10 +11,10 @@ import {
import { Server, IncomingMessage, ServerResponse } from 'http'
import { MIN_ID_LENGTH, MAX_NAME_LENGTH } from '../../constants'
import { errorSchema } from '../../schemas'
import { errorSchema, groupListingSchema } from '../../schemas'
import { unauthorizedError, badRequestError, notFoundError, serverError } from '../../lib/errors'
import { containerFor, createQuerySpec, queryItems, getItem, normalize } from '../../lib/database'
import { IUser, IGroup, IGroupMembership, IUserBlock, IGroupBlock } from '../../types/collections'
import { IUser, IGroup, IGroupListing, IGroupMembership, IUserBlock, IGroupBlock } from '../../types/collections'
interface PluginOptions {}
@ -73,21 +74,22 @@ function createRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
const group: IGroup = {
id: id,
partitionKey: id,
type: 'group',
pk: id,
t: 'group',
userId: request.viewer.id,
name,
about,
open,
requiresApproval,
members: 1,
status: 'pending',
active: true,
created: Date.now(),
}
const membership: IGroupMembership = {
id: request.viewer.id,
partitionKey: id,
type: 'membership',
pk: id,
t: 'membership',
userId: request.viewer.id,
pending: false,
membership: 'admin',
@ -149,16 +151,16 @@ function blockRoute(server: FastifyInstance<Server, IncomingMessage, ServerRespo
await containerFor(server.database.client, 'Users').items.create<IUserBlock>({
blockedId: group.id,
partitionKey: request.viewer.id,
type: 'block',
pk: request.viewer.id,
t: 'block',
blockType: 'group',
description: request.body.description,
created: Date.now(),
})
await groupContainer.items.create<IGroupBlock>({
partitionKey: group.id,
type: 'block',
pk: group.id,
t: 'block',
blockedId: group.id,
userId: request.viewer.id,
created: Date.now(),
@ -184,7 +186,7 @@ function unblockRoute(server: FastifyInstance<Server, IncomingMessage, ServerRes
},
}
server.post<DefaultQuery, Params, DefaultHeaders, Body>('/api/group/:id/unblock', options, async (request, reply) => {
server.post<DefaultQuery, Params, DefaultHeaders, DefaultBody>('/api/group/:id/unblock', options, async (request, reply) => {
if (!server.database) return serverError(reply)
if (!request.viewer) return unauthorizedError(reply)
@ -194,8 +196,8 @@ function unblockRoute(server: FastifyInstance<Server, IncomingMessage, ServerRes
const group = await getItem<IGroup>(groupContainer, request.params.id, request.log)
if (!group) return notFoundError(reply)
const userBlockQuery = createQuerySpec(`SELECT u.id FROM Users u WHERE u.partitionKey = @partitionKey AND u.blockedId = @blocked AND u.type = 'block'`, {
partitionKey: request.viewer.id,
const userBlockQuery = createQuerySpec(`SELECT u.id FROM Users u WHERE u.pk = @pk AND u.blockedId = @blocked AND u.type = 'block'`, {
pk: request.viewer.id,
blocked: group.id,
})
@ -205,9 +207,9 @@ function unblockRoute(server: FastifyInstance<Server, IncomingMessage, ServerRes
}
const groupBlockQuery = createQuerySpec(
`SELECT g.id FROM Groups g WHERE g.partitionKey = @partitionKey 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.type = 'block'`,
{
partitionKey: group.id,
pk: group.id,
blocked: group.id,
viewer: request.viewer.id,
}
@ -222,10 +224,136 @@ function unblockRoute(server: FastifyInstance<Server, IncomingMessage, ServerRes
})
}
function activateRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) {
interface Params {
id: string
}
const options: RouteShorthandOptions = {
schema: {
params: {
type: 'object',
properties: {
id: { type: 'string' },
},
},
},
}
server.post<DefaultQuery, Params, DefaultHeaders, DefaultBody>('/api/group/:id/activate', options, async (request, reply) => {
if (!server.database) return serverError(reply)
const container = containerFor(server.database.client, 'Groups')
const groupItem = container.item(request.params.id, request.params.id)
const { resource: group, requestCharge: groupRequestCharge } = await groupItem.read<IGroup>()
request.log.trace('Get: %d', groupRequestCharge)
if (!group) return notFoundError(reply)
if (group.active && group.status === 'paid') {
return badRequestError(reply, 'Already activated')
}
await groupItem.replace<IGroup>({
...group,
active: true,
status: 'paid',
})
const directoryContainer = containerFor(server.database.client, 'GroupDirectory')
const listingItem = directoryContainer.item(request.params.id, 'pk')
const { resource: listing, requestCharge: listingRequestCharge } = await listingItem.read<IGroupListing>()
request.log.trace('Get: %d', listingRequestCharge)
if (!listing) {
await directoryContainer.items.create<IGroupListing>({
id: group.id,
name: group.name,
pk: 'pk',
open: group.open,
requiresApproval: group.requiresApproval,
members: 0,
posts: 0,
awards: 0,
points: 0,
latestAwards: [],
created: Date.now(),
})
}
reply.code(204)
})
}
function listRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) {
interface Query {
sort?: string
requiresApproval?: boolean
continuation?: string
}
const options: RouteShorthandOptions = {
schema: {
querystring: {
type: 'object',
properties: {
sort: {
type: 'string',
enum: ['name', 'members', 'points'],
},
continuation: { type: 'string' },
},
},
response: {
200: {
type: 'object',
properties: {
groups: {
type: 'array',
items: groupListingSchema,
},
continuation: { type: 'string' },
}
},
400: errorSchema,
}
},
}
server.get<Query, DefaultParams, DefaultHeaders, DefaultBody>('/api/groups', options, async (request, reply) => {
if (!server.database) return serverError(reply)
const { sort = 'members', requiresApproval, continuation } = request.query
let requiresApprovalString = ''
if (requiresApproval !== undefined) {
requiresApprovalString = `AND d.requiresApproval = ${requiresApproval.toString()}`
}
const container = containerFor(server.database.client, 'GroupDirectory')
const { resources: groups, requestCharge, continuation: newContinuation } = await container.items.query<IGroupListing>(
`SELECT * FROM GroupDirectory d WHERE d.pk = 'pk' AND d.open = true ${requiresApprovalString} ORDER BY ${sort}`,
{
maxItemCount: 40,
continuation,
}
).fetchAll()
request.log.trace('Query: %d', requestCharge)
return {
groups,
continuation: newContinuation,
}
})
}
const plugin: Plugin<Server, IncomingMessage, ServerResponse, PluginOptions> = async server => {
createRoute(server)
blockRoute(server)
unblockRoute(server)
activateRoute(server)
listRoute(server)
}
export default plugin

37
src/plugins/api/posts.ts

@ -105,7 +105,7 @@ function doPostRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
let newPostRelationship: IPostRelationship | undefined
const postContainer = containerFor(server.database.client, 'Posts')
const postRelationshipContainer = containerFor(server.database.client, 'PostRelationships')
const ancestryContainer = containerFor(server.database.client, 'Ancestry')
const userContainer = containerFor(server.database.client, 'Users')
const viewer = await getItem<IUser>(userContainer, request.viewer.id, request.log)
@ -119,12 +119,12 @@ function doPostRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
const parent = await getItem<IPost>(postContainer, request.body.parent, request.log)
if (!parent) return badRequestError(reply, 'Invalid parent', 'parent')
const parentRelationship = await getItem<IPostRelationship>(postRelationshipContainer, request.body.parent, parent.root, request.log)
const parentRelationship = await getItem<IPostRelationship>(ancestryContainer, request.body.parent, parent.root, request.log)
const parents = parentRelationship ? parentRelationship.parents : []
newPostRelationship = {
id: postId,
partitionKey: parent.root,
pk: parent.root,
parents: [
...parents,
parent.id,
@ -134,38 +134,41 @@ function doPostRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
const post: IPost = {
id: postId,
pk: postId,
t: 'post',
userId: request.viewer.id,
root: newPostRelationship ? newPostRelationship.partitionKey : postId,
root: newPostRelationship ? newPostRelationship.pk : postId,
parents: newPostRelationship ? newPostRelationship.parents : [],
text: trimContent(request.body.text, 1000),
cover: trimContent(request.body.cover),
visible: request.body.visible,
attachments: [],
awards: [],
awards: 0,
latestAwards: [],
created: Date.now(),
}
const userPost: IUserPost = {
postId,
partitionKey: request.viewer.id,
type: 'post',
pk: request.viewer.id,
t: 'post',
created: Date.now(),
}
await postContainer.items.create<IPost>(post)
await userContainer.items.create<IUserPost>(userPost)
if (newPostRelationship) await postRelationshipContainer.items.create<IPostRelationship>(newPostRelationship)
if (newPostRelationship) await ancestryContainer.items.create<IPostRelationship>(newPostRelationship)
const query = createQuerySpec(`SELECT u.id FROM Users u WHERE u.partitionKey = @partitionKey AND u.type = 'subscription'`, { partitionKey: request.viewer.id })
const query = createQuerySpec(`SELECT u.id FROM Users u WHERE u.pk = @pk AND u.type = 'subscription'`, { pk: request.viewer.id })
const subscribers = await queryItems<IUserSubscription>(userContainer, query, request.log)
if (subscribers.length < SUBSCRIBER_MAX_SIZE) {
for (const subscriber of subscribers) {
await userContainer.items.create<IUserTimelinePost>({
postId,
partitionKey: subscriber.id!,
type: 'timeline',
pk: subscriber.id!,
t: 'timeline',
created: Date.now(),
})
}
@ -250,7 +253,7 @@ function postsByUserRoute(server: FastifyInstance<Server, IncomingMessage, Serve
if (blocks.length > 0) return unauthorizedError(reply)
}
const userPostsQuery = createQuerySpec(`SELECT p.id FROM Users p WHERE p.partitionKey = @user AND p.type = 'post'`, { user: id })
const userPostsQuery = createQuerySpec(`SELECT p.id FROM Users p WHERE p.pk = @user AND p.type = 'post'`, { user: id })
const userPosts = await queryItems<IUserPost>(userContainer, userPostsQuery)
const posts = await queryItems<IPost>(containerFor(server.database.client, 'Posts'), createQuerySpec('SELECT * FROM Posts p WHERE ARRAY_CONTAINS(@posts, p.id)', {
@ -308,12 +311,12 @@ function postRoute(server: FastifyInstance<Server, IncomingMessage, ServerRespon
const post = await getItem<IPost>(postContainer, request.params.id, request.log)
if (!post) return notFoundError(reply)
const query = createQuerySpec('SELECT * FROM PostRelationships r WHERE r.partitionKey = @partitionKey AND ARRAY_CONTAINS(r.parents, @id)', {
partitionKey: post.root,
const query = createQuerySpec('SELECT * FROM Ancestry a WHERE a.pk = @pk AND ARRAY_CONTAINS(a.parents, @id)', {
pk: post.root,
id: post.id,
})
const descendantRelationships = await queryItems<IPostRelationship>(containerFor(server.database.client, 'PostRelationships'), query, request.log)
const descendantRelationships = await queryItems<IPostRelationship>(containerFor(server.database.client, 'Ancestry'), query, request.log)
const descendants = await queryItems<IPost>(postContainer, createQuerySpec('SELECT * FROM Posts p WHERE ARRAY_CONTAINS(@descendants, p.id)', {
descendants: descendantRelationships.map(r => r.id),
@ -340,8 +343,8 @@ function postRoute(server: FastifyInstance<Server, IncomingMessage, ServerRespon
const blockQuery = createQuerySpec(`
SELECT g.userId FROM Groups g WHERE
g.partitionKey = @viewerGroup AND
g.type = 'block' AND
g.pk = @viewerGroup AND
g.t = 'block' AND
(g.blockedId = @viewer OR g.blockedId = @viewerGroup)
ARRAY_CONTAINS(@ids, g.userId)
`, {

28
src/plugins/api/users.ts

@ -173,7 +173,7 @@ function subscribeRoute(server: FastifyInstance<Server, IncomingMessage, ServerR
if (!viewer) return serverError(reply)
if (!viewer.group) return unauthorizedError(reply)
const subscriptionQuery = createQuerySpec(`SELECT u.id FROM Users u WHERE u.subscriberId = @user AND u.partitionKey = @viewer AND u.type = 'subscription'`, {
const subscriptionQuery = createQuerySpec(`SELECT u.id FROM Users u WHERE u.subscriberId = @user AND u.pk = @viewer AND u.t = 'subscription'`, {
user: user.id,
viewer: viewer.id,
})
@ -195,8 +195,8 @@ function subscribeRoute(server: FastifyInstance<Server, IncomingMessage, ServerR
const blockQuery = createQuerySpec(`
SELECT g.id FROM Groups g WHERE
g.partitionKey = @viewerGroup AND
g.type = 'block' AND
g.pk = @viewerGroup AND
g.t = 'block' AND
g.userId = @user AND
(g.blockedId = @viewer OR g.blockedId = @viewerGroup)
`, {
@ -209,8 +209,8 @@ function subscribeRoute(server: FastifyInstance<Server, IncomingMessage, ServerR
await userContainer.items.create<IUserSubscription>({
subscriberId: user.id,
partitionKey: request.viewer.id,
type: 'subscription',
pk: request.viewer.id,
t: 'subscription',
pending,
created: Date.now(),
})
@ -246,7 +246,7 @@ function unsubscribeRoute(server: FastifyInstance<Server, IncomingMessage, Serve
if (!user) return notFoundError(reply)
if (!viewer) return serverError(reply)
const subscriptionQuery = createQuerySpec(`SELECT u.id FROM Users u WHERE u.subscriberId = @user AND u.partitionKey = @viewer AND u.type = 'subscription'`, {
const subscriptionQuery = createQuerySpec(`SELECT u.id FROM Users u WHERE u.subscriberId = @user AND u.pk = @viewer AND u.t = 'subscription'`, {
user: user.id,
viewer: viewer.id,
})
@ -301,16 +301,16 @@ function blockRoute(server: FastifyInstance<Server, IncomingMessage, ServerRespo
await userContainer.items.create<IUserBlock>({
blockedId: user.id,
partitionKey: request.viewer.id,
type: 'block',
pk: request.viewer.id,
t: 'block',
blockType: 'user',
description: request.body.description,
created: Date.now(),
})
await containerFor(server.database.client, 'Groups').items.create<IGroupBlock>({
partitionKey: user.group.id,
type: 'block',
pk: user.group.id,
t: 'block',
blockedId: user.id,
userId: request.viewer.id,
created: Date.now(),
@ -348,8 +348,8 @@ function unblockRoute(server: FastifyInstance<Server, IncomingMessage, ServerRes
if (!user) return notFoundError(reply)
if (!user.group) return badRequestError(reply, 'Invalid operation')
const userBlockQuery = createQuerySpec(`SELECT u.id FROM Users u WHERE u.partitionKey = @partitionKey AND u.blockedId = @blocked AND u.type = 'block'`, {
partitionKey: request.viewer.id,
const userBlockQuery = createQuerySpec(`SELECT u.id FROM Users u WHERE u.pk = @pk AND u.blockedId = @blocked AND u.type = 'block'`, {
pk: request.viewer.id,
blocked: user.id,
})
@ -359,9 +359,9 @@ function unblockRoute(server: FastifyInstance<Server, IncomingMessage, ServerRes
}
const groupBlockQuery = createQuerySpec(
`SELECT g.id FROM Groups g WHERE g.partitionKey = @partitionKey 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.type = 'block'`,
{
partitionKey: user.group.id,
pk: user.group.id,
blocked: user.id,
viewer: request.viewer.id,
}

30
src/schemas.ts

@ -42,6 +42,36 @@ export const userSchema: JSONSchema = {
},
}
export const awardSchema: JSONSchema = {
type: 'object',
properties: {
userId: { type: 'string' },
imageUrl: { type: 'string' },
text: { type: 'string' },
userText: { type: 'string' },
created: { type: 'number' },
},
}
export const groupListingSchema: JSONSchema = {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
imageUrl: { type: 'string' },
coverImageUrl: { type: 'string' },
requiresApproval: { type: 'boolean' },
members: { type: 'number' },
posts: { type: 'number' },
awards: { type: 'number' },
points: { type: 'number' },
latestAwards: {
type: 'array',
items: awardSchema,
},
},
}
export const errorSchema: JSONSchema = {
type: 'object',
properties: {

122
src/types/collections.ts

@ -1,25 +1,45 @@
// Containers
//
// Users
// - Partition Key: partitionKey (userId)
// - Partition Key: pk (userId)
// Groups
// - Partition Key: partitionKey (groupId)
// - Partition Key: pk (groupId)
// GroupDirectory
// - Partition Key: pk
// Posts
// - Partition Key: id
// PostRelationships
// - Partition Key: partitionKey (postId)
// - Partition Key: pk (postId)
// Ancestry
// - Partition Key: pk (postId)
// Points: total reward value + likes
export type IUserItemType = 'user' | 'token' | 'post' | 'follow' | 'timeline'
export type IUserPrivacyType = 'public' | 'group' | 'subscribers' | 'private'
export type IUserTransactionType = 'purchase' | 'award'
export type IGroupStatus = 'pending' | 'paid'
export type IGroupItemType = 'group' | 'membership' | 'report' | 'block'
export type IGroupMembershipType = 'admin' | 'moderator' | 'member'
export type IReportStatus = 'pending' | 'complete'
export type IBlockType = 'user' | 'group'
export interface IGroupListing {
id: string
pk: 'pk'
name: string
open: boolean
requiresApproval: boolean
members: number
posts: number
awards: number
points: number
latestAwards: IPostAwardPartial[]
created: number
}
export interface IGroup {
id: string
partitionKey: string // ID
type: 'group'
pk: string // ID
t: 'group'
userId: string
name: string // Prenormalized ID
about?: string
@ -27,7 +47,8 @@ export interface IGroup {
coverImageUrl?: string
open: boolean
requiresApproval: boolean
members: number
status: IGroupStatus
active: boolean
created: number
}
@ -40,8 +61,8 @@ export interface IGroupPartial {
export interface IGroupMembership {
id: string
partitionKey: string // Group ID
type: 'membership'
pk: string // Group ID
t: 'membership'
userId: string
pending: boolean
membership: IGroupMembershipType
@ -50,8 +71,8 @@ export interface IGroupMembership {
export interface IGroupReport {
id: string
partitionKey: string // Group ID
type: 'report'
pk: string // Group ID
t: 'report'
postId: string
reportedById: string
description?: string
@ -61,8 +82,8 @@ export interface IGroupReport {
export interface IGroupBlock {
id?: string
partitionKey: string // Group ID
type: 'block'
pk: string // Group ID
t: 'block'
blockedId: string // User or Group ID
userId: string // blocker ID
created: number
@ -70,8 +91,8 @@ export interface IGroupBlock {
export interface IUser {
id: string
partitionKey: string // ID
type: 'user'
pk: string // ID
t: 'user'
group?: IGroupPartial
name: string
about?: string
@ -81,20 +102,23 @@ export interface IUser {
emailVerified: boolean
passwordHash: string
installations: IInstallation[]
awards: number // Total Awards
points: number
balance: number // Currency (Flex)
subscriberCount: number
subscribedCount: number
pending: boolean
requiresApproval: boolean
privacy: IUserPrivacyType
paid: boolean
active: boolean
created: number // Timestamp
}
export interface IUserToken {
id: string
partitionKey: string // userId
type: 'token'
pk: string // userId
t: 'token'
userAgent: string
ip: string
expires: number
@ -104,16 +128,16 @@ export interface IUserToken {
export interface IUserPost {
id?: string
postId: string
partitionKey: string // userId
type: 'post'
pk: string // userId
t: 'post'
created: number
}
export interface IUserSubscription {
id?: string
subscriberId: string
partitionKey: string
type: 'subscription'
pk: string
t: 'subscription'
pending: boolean
created: number
}
@ -121,28 +145,32 @@ export interface IUserSubscription {
export interface IUserBlock {
id?: string
blockedId: string
partitionKey: string
type: 'block'
pk: string
t: 'block'
blockType: IBlockType
description?: string
created: number
}
export interface IUserTransaction {
id: string
pk: string
t: 'transaction'
transactionType: IUserTransactionType
fromUserId: string
toUserId: string
amount: number
created: number
}
export interface IUserTimelinePost {
id?: string
postId: string
partitionKey: string // userId
type: 'timeline'
pk: string // userId
t: 'timeline'
created: number
}
interface IAward {
imageUrl: string
text: string
received: number
points: number
}
export interface IPostAttachment {
imageUrl: string
caption?: string
@ -157,6 +185,8 @@ export interface IStatus {
export interface IPost {
id: string
pk: string // postId
t: 'post'
userId: string
root: string
parents: string[] // Post IDs
@ -165,14 +195,36 @@ export interface IPost {
attachments: IPostAttachment[]
status?: IStatus
visible: boolean
awards: IAward[]
awards: number
latestAwards: IPostAwardPartial[]
created: number
}
export interface IPostAward {
id: string
pk: string // postId
t: 'award'
userId: string
imageUrl: string
text: string
userText: string
cost: number
reward: number
created: number
}
export interface IPostAwardPartial {
userId: string
imageUrl: string
text: string
userText: string
created: number
}
export interface IPostRelationship {
id: string
parents: string[] // Post IDs
partitionKey: string // root post ID
pk: string // root post ID
}
export interface IInstallation {

16
src/types/index.ts

@ -0,0 +1,16 @@
export interface IQueryParams {
[key: string]: string | string[] | number | boolean
}
export interface IDatabaseItem {
id: string
}
export interface IAwardDefinition {
id: string
name: string
imageUrl: string
text: string
cost: number
reward: number
}
Loading…
Cancel
Save