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 SHORT_TEXT_LENGTH = 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 { JWT } from '../lib/crypto'
import { UserToken } from '../types/collections'
import { UserToken, UserItemType } from '../types/collections'
export async function createAccessToken(userId: string): Promise<string> {
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),

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 { 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<Server, IncomingMessage, ServerResponse>) {
@ -100,13 +112,14 @@ function registerRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
SELECT g.id FROM Groups g WHERE
g.id = @invitation
g.pk = @group AND
g.type = 'invitation' AND
g.t = @type AND
g.active = true AND
g.expiration < GETCURRENTTIMESTAMP() AND
g.uses < g.limit
`, {
invitation,
group: group.id,
type: GroupItemType.Invitation,
})
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 (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 = {
id: group.id,
@ -132,7 +145,7 @@ function registerRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
const user: User = {
id,
pk: id,
t: 'user',
t: UserItemType.User,
group: groupPartial,
name,
about: '',
@ -147,7 +160,7 @@ function registerRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
subscribedCount: 0,
pending: userPending,
requiresApproval: false,
privacy: 'public',
privacy: UserPrivacyType.Public,
paid: false,
active: true,
created: Date.now(),
@ -161,10 +174,10 @@ function registerRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
if (group) {
await groupContainer.items.create<GroupMembership>({
pk: group.id,
t: 'membership',
t: GroupItemType.Membership,
userId: user.id,
pending: userPending,
membership: 'member',
membership: GroupMembershipType.Member,
invitation,
created: Date.now(),
})

92
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<Server, IncomingMessage, ServerResponse>) {
@ -127,12 +143,12 @@ function createRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
const group: Group = {
id: id,
pk: id,
t: 'group',
t: GroupItemType.Group,
userId: request.viewer.id,
name,
about,
registration,
status: 'pending',
status: GroupStatus.Pending,
active: true,
created: Date.now(),
}
@ -140,10 +156,10 @@ function createRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
const membership: GroupMembership = {
id: request.viewer.id,
pk: id,
t: 'membership',
t: GroupItemType.Membership,
userId: request.viewer.id,
pending: false,
membership: 'admin',
membership: GroupMembershipType.Admin,
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) => {
if (!server.database) return serverError(reply)
const groupContainer = containerFor(server.database.client, 'Groups')
const listing = await getItem<GroupListing>({
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<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>({
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<GroupBlock>({
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<Server, IncomingMessage, ServerRes
})
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,
blocked: group.id,
type: UserItemType.Block,
})
const userBlocks = await queryItems<UserBlock>({
@ -303,11 +360,12 @@ function unblockRoute(server: FastifyInstance<Server, IncomingMessage, ServerRes
}
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,
blocked: group.id,
viewer: request.viewer.id,
type: GroupItemType.Block,
}
)
@ -359,11 +417,11 @@ function activateRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
await groupItem.replace<Group>({
...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<GroupListing>()
request.log.trace('Get: %d', listingRequestCharge)
@ -371,7 +429,7 @@ function activateRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
await directoryContainer.items.create<GroupListing>({
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<Server, IncomingMessage, ServerRespon
const container = containerFor(server.database.client, 'GroupDirectory')
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,
continuation,

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

46
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<Server, IncomingMessage, ServerResponse>) {
@ -220,9 +231,10 @@ 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.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,
viewer: viewer.id,
type: UserItemType.Subscription,
})
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
switch (user.privacy) {
case 'private':
case UserPrivacyType.Private:
return unauthorizedError(reply)
case 'group':
case UserPrivacyType.Group:
if (user.group !== viewer.group) return unauthorizedError(reply)
case 'subscribers':
case UserPrivacyType.Subscribers:
pending = true
break
}
@ -243,12 +255,13 @@ function subscribeRoute(server: FastifyInstance<Server, IncomingMessage, ServerR
const blockQuery = createQuerySpec(`
SELECT g.id FROM Groups g WHERE
g.pk = @viewerGroup AND
g.t = 'block' AND
g.t = @type AND
g.userId = @user AND
(g.blockedId = @viewer OR g.blockedId = @viewerGroup)
`, {
user: user.id,
viewerGroup: viewer.group.id,
type: GroupItemType.Block,
})
const blocks = await queryItems<GroupBlock>({
@ -262,7 +275,7 @@ function subscribeRoute(server: FastifyInstance<Server, IncomingMessage, ServerR
await userContainer.items.create<UserSubscription>({
subscriberId: user.id,
pk: request.viewer.id,
t: 'subscription',
t: UserItemType.Subscription,
pending,
created: Date.now(),
})
@ -298,9 +311,10 @@ 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.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,
viewer: viewer.id,
type: UserItemType.Subscription,
})
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>({
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<GroupBlock>({
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<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.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,
blocked: user.id,
type: UserItemType.Block,
})
const userBlocks = await queryItems<UserBlock>({
@ -424,11 +439,12 @@ function unblockRoute(server: FastifyInstance<Server, IncomingMessage, ServerRes
}
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,
blocked: user.id,
viewer: request.viewer.id,
type: GroupItemType.Block,
}
)

2
src/schemas.ts

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

106
src/types/collections.ts

@ -12,20 +12,74 @@
// - Partition Key: pk (postId)
// 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 {
id: string
pk: 'pk'
pk: typeof GROUP_LISTING_PARTITION_KEY
name: string
about?: string
registration: GroupRegistrationType
@ -40,7 +94,7 @@ export interface GroupListing {
export interface Group {
id: string
pk: string // ID
t: 'group'
t: GroupItemType.Group
userId: string
name: string // Prenormalized ID
about?: string
@ -63,7 +117,7 @@ export interface GroupPartial {
export interface GroupMembership {
id?: string
pk: string // Group ID
t: 'membership'
t: GroupItemType.Membership
userId: string
pending: boolean
membership: GroupMembershipType
@ -74,7 +128,7 @@ export interface GroupMembership {
export interface GroupInvitation {
id: string
pk: string // Group ID
t: 'invitation'
t: GroupItemType.Invitation
userId: string
limit: number
expiration: number
@ -86,7 +140,7 @@ export interface GroupInvitation {
export interface GroupReport {
id: string
pk: string // Group ID
t: 'report'
t: GroupItemType.Report
postId: string
reportedById: string
description?: string
@ -97,7 +151,7 @@ export interface GroupReport {
export interface GroupBlock {
id?: string
pk: string // Group ID
t: 'block'
t: GroupItemType.Block
blockedId: string // User or Group ID
userId: string // blocker ID
created: number
@ -106,7 +160,7 @@ export interface GroupBlock {
export interface User {
id: string
pk: string // ID
t: 'user'
t: UserItemType.User
group?: GroupPartial
name: string
about?: string
@ -132,7 +186,7 @@ export interface User {
export interface UserToken {
id: string
pk: string // userId
t: 'token'
t: UserItemType.Token
userAgent: string
ip: string
expires: number
@ -143,7 +197,7 @@ export interface UserPost {
id?: string
postId: string
pk: string // userId
t: 'post'
t: UserItemType.Post
created: number
}
@ -151,7 +205,7 @@ export interface UserSubscription {
id?: string
subscriberId: string
pk: string
t: 'subscription'
t: UserItemType.Subscription
pending: boolean
created: number
}
@ -160,16 +214,16 @@ export interface UserBlock {
id?: string
blockedId: string
pk: string
t: 'block'
t: UserItemType.Block
blockType: BlockType
description?: string
created: number
}
export interface IUserTransaction {
export interface UserTransaction {
id: string
pk: string
t: 'transaction'
t: UserItemType.Transaction
transactionType: UserTransactionType
fromUserId: string
toUserId: string
@ -181,7 +235,7 @@ export interface UserTimelinePost {
id?: string
postId: string
pk: string // userId
t: 'timeline'
t: UserItemType.Timeline
created: number
}
@ -200,7 +254,7 @@ export interface Status {
export interface Post {
id: string
pk: string // postId
t: 'post'
t: PostItemType.Post
userId: string
root: string
parents: string[] // Post IDs
@ -214,10 +268,10 @@ export interface Post {
created: number
}
export interface IPostAward {
export interface PostAward {
id: string
pk: string // postId
t: 'award'
t: PostItemType.Award
userId: string
imageUrl: string
text: string

Loading…
Cancel
Save