|
|
@ -3,20 +3,59 @@ import fastify, { |
|
|
|
DefaultQuery, |
|
|
|
DefaultParams, |
|
|
|
DefaultHeaders, |
|
|
|
DefaultBody, |
|
|
|
RouteShorthandOptions, |
|
|
|
JSONSchema, |
|
|
|
} from 'fastify' |
|
|
|
|
|
|
|
import { Server, IncomingMessage, ServerResponse } from 'http' |
|
|
|
import { unauthorizedError, badRequestError } from '../../lib/errors' |
|
|
|
import { containerFor, createQuerySpec } from '../../lib/database' |
|
|
|
import { unauthorizedError, badRequestError, notFoundError } from '../../lib/errors' |
|
|
|
import { trimContent, createPostId } from '../../lib/util' |
|
|
|
import { IPost, IPostAttachment, IUserPost, IUserFollow, IStatus, IUserTimelinePost, IPostRelationship } from '../../types/collections' |
|
|
|
import { getUsers } from '../../lib/collections' |
|
|
|
|
|
|
|
import { |
|
|
|
containerFor, |
|
|
|
createQuerySpec, |
|
|
|
normalize, |
|
|
|
} from '../../lib/database' |
|
|
|
|
|
|
|
import { |
|
|
|
IPost, |
|
|
|
IPostAttachment, |
|
|
|
IUser, |
|
|
|
IUserPost, |
|
|
|
IUserFollow, |
|
|
|
IUserTimelinePost, |
|
|
|
IPostRelationship, |
|
|
|
IStatus, |
|
|
|
} from '../../types/collections' |
|
|
|
|
|
|
|
interface PluginOptions { |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
function postRoute(server: fastify.FastifyInstance<Server, IncomingMessage, ServerResponse>) { |
|
|
|
const postSchema: JSONSchema = { |
|
|
|
type: 'object', |
|
|
|
properties: { |
|
|
|
id: { type: 'string' }, |
|
|
|
userId: { type: 'string' }, |
|
|
|
text: { type: 'string' }, |
|
|
|
cover: { type: 'string' }, |
|
|
|
visible: { type: 'boolean' }, |
|
|
|
created: { type: 'number' }, |
|
|
|
}, |
|
|
|
} |
|
|
|
|
|
|
|
const userSchema: JSONSchema = { |
|
|
|
type: 'object', |
|
|
|
properties: { |
|
|
|
id: { type: 'string' }, |
|
|
|
name: { type: 'string' }, |
|
|
|
created: { type: 'number' }, |
|
|
|
}, |
|
|
|
} |
|
|
|
|
|
|
|
function doPostRoute(server: fastify.FastifyInstance<Server, IncomingMessage, ServerResponse>) { |
|
|
|
interface Body { |
|
|
|
text?: string |
|
|
|
cover?: string |
|
|
@ -82,13 +121,10 @@ function postRoute(server: fastify.FastifyInstance<Server, IncomingMessage, Serv |
|
|
|
const postId = createPostId() |
|
|
|
|
|
|
|
if (request.body.parent) { |
|
|
|
const parentItem = postContainer.item(request.body.parent, request.body.parent) |
|
|
|
const { resource: parent } = await parentItem.read<IPost>() |
|
|
|
|
|
|
|
const { resource: parent } = await postContainer.item(request.body.parent, request.body.parent).read<IPost>() |
|
|
|
if (!parent) return badRequestError(reply, 'Invalid parent', 'parent') |
|
|
|
|
|
|
|
const parentRelationshipItem = postRelationshipContainer.item(request.body.parent, parent.root) |
|
|
|
const { resource: parentRelationship } = await parentRelationshipItem.read<IPostRelationship>() |
|
|
|
const { resource: parentRelationship } = await postRelationshipContainer.item(request.body.parent, parent.root).read<IPostRelationship>() |
|
|
|
const parents = parentRelationship ? parentRelationship.parents : [] |
|
|
|
|
|
|
|
newPostRelationship = { |
|
|
@ -105,6 +141,7 @@ function postRoute(server: fastify.FastifyInstance<Server, IncomingMessage, Serv |
|
|
|
id: postId, |
|
|
|
userId: request.user.id, |
|
|
|
root: newPostRelationship ? newPostRelationship.partitionKey : postId, |
|
|
|
parents: newPostRelationship ? newPostRelationship.parents : [], |
|
|
|
text: trimContent(request.body.text, 1000), |
|
|
|
cover: trimContent(request.body.cover), |
|
|
|
visible: request.body.visible, |
|
|
@ -145,7 +182,145 @@ function postRoute(server: fastify.FastifyInstance<Server, IncomingMessage, Serv |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
function postsByUserRoute(server: fastify.FastifyInstance<Server, IncomingMessage, ServerResponse>) { |
|
|
|
interface Params { |
|
|
|
id: string |
|
|
|
} |
|
|
|
|
|
|
|
const options: RouteShorthandOptions = { |
|
|
|
schema: { |
|
|
|
params: { |
|
|
|
type: 'object', |
|
|
|
properties: { |
|
|
|
id: { type: 'string' }, |
|
|
|
}, |
|
|
|
}, |
|
|
|
response: { |
|
|
|
200: { |
|
|
|
type: 'object', |
|
|
|
properties: { |
|
|
|
user: userSchema, |
|
|
|
posts: { |
|
|
|
type: 'array', |
|
|
|
items: postSchema, |
|
|
|
}, |
|
|
|
}, |
|
|
|
}, |
|
|
|
}, |
|
|
|
}, |
|
|
|
} |
|
|
|
|
|
|
|
server.get<DefaultQuery, Params, DefaultHeaders, DefaultBody>('/api/user/:id/posts', options, async (request, reply) => { |
|
|
|
const id = normalize(request.params.id) |
|
|
|
const userContainer = await containerFor(server.database.client, 'Users') |
|
|
|
const postContainer = await containerFor(server.database.client, 'Posts') |
|
|
|
|
|
|
|
const { resource: user } = await userContainer.item(id, id).read<IUser>() |
|
|
|
|
|
|
|
if (!user) return notFoundError(reply) |
|
|
|
|
|
|
|
const userPostsQuery = createQuerySpec(`SELECT p.id FROM Users p WHERE p.partitionKey = @userId AND p.type = 'post'`, { userId: id }) |
|
|
|
const { resources: userPosts } = await userContainer.items.query<IUserPost>(userPostsQuery, {}).fetchAll() |
|
|
|
|
|
|
|
const { resources: posts } = await postContainer.items.query<IPost>({ |
|
|
|
query: 'SELECT * FROM Posts p WHERE ARRAY_CONTAINS(@posts, p.id)', |
|
|
|
parameters: [{ |
|
|
|
name: '@posts', |
|
|
|
value: userPosts.map(p => p.id), |
|
|
|
}] |
|
|
|
}, {}).fetchAll() |
|
|
|
|
|
|
|
return { |
|
|
|
user, |
|
|
|
posts, |
|
|
|
} |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
function postRoute(server: fastify.FastifyInstance<Server, IncomingMessage, ServerResponse>) { |
|
|
|
interface Params { |
|
|
|
id: string |
|
|
|
} |
|
|
|
|
|
|
|
const options: RouteShorthandOptions = { |
|
|
|
schema: { |
|
|
|
params: { |
|
|
|
type: 'object', |
|
|
|
properties: { |
|
|
|
id: { type: 'string' }, |
|
|
|
}, |
|
|
|
}, |
|
|
|
response: { |
|
|
|
200: { |
|
|
|
type: 'object', |
|
|
|
properties: { |
|
|
|
post: postSchema, |
|
|
|
descendants: { |
|
|
|
type: 'array', |
|
|
|
items: postSchema, |
|
|
|
}, |
|
|
|
ancestors: { |
|
|
|
type: 'array', |
|
|
|
items: postSchema, |
|
|
|
}, |
|
|
|
users: { |
|
|
|
type: 'array', |
|
|
|
items: userSchema, |
|
|
|
} |
|
|
|
}, |
|
|
|
}, |
|
|
|
}, |
|
|
|
}, |
|
|
|
} |
|
|
|
|
|
|
|
server.get<DefaultQuery, Params, DefaultHeaders, DefaultBody>('/api/post/:id', options, async (request, reply) => { |
|
|
|
const postContainer = await containerFor(server.database.client, 'Posts') |
|
|
|
const { resource: post } = await postContainer.item(request.params.id, request.params.id).read<IPost>() |
|
|
|
|
|
|
|
const postRelationshipContainer = await containerFor(server.database.client, 'PostRelationships') |
|
|
|
const query = createQuerySpec('SELECT * FROM PostRelationships r WHERE r.partitionKey = @partitionKey AND ARRAY_CONTAINS(r.parents, @id)', { |
|
|
|
partitionKey: post.root, |
|
|
|
id: post.id, |
|
|
|
}) |
|
|
|
|
|
|
|
const { resources: descendantRelationships } = await postRelationshipContainer.items.query<IPostRelationship>(query, {}).fetchAll() |
|
|
|
|
|
|
|
const { resources: descendants } = await postContainer.items.query<IPost>({ |
|
|
|
query: 'SELECT * FROM Posts p WHERE ARRAY_CONTAINS(@descendants, p.id)', |
|
|
|
parameters: [{ |
|
|
|
name: '@descendants', |
|
|
|
value: descendantRelationships.map(r => r.id), |
|
|
|
}] |
|
|
|
}, {}).fetchAll() |
|
|
|
|
|
|
|
const { resources: ancestors } = await postContainer.items.query<IPost>({ |
|
|
|
query: 'SELECT * FROM Posts p WHERE ARRAY_CONTAINS(@parents, p.id)', |
|
|
|
parameters: [{ |
|
|
|
name: '@parents', |
|
|
|
value: post.parents, |
|
|
|
}] |
|
|
|
}, {}).fetchAll() |
|
|
|
|
|
|
|
const getUserId = (post: IPost) => post.userId |
|
|
|
|
|
|
|
const users = await getUsers(server.database.client, [ |
|
|
|
...descendants.map(getUserId), |
|
|
|
...ancestors.map(getUserId), |
|
|
|
getUserId(post), |
|
|
|
]) |
|
|
|
|
|
|
|
return { |
|
|
|
post, |
|
|
|
descendants, |
|
|
|
ancestors, |
|
|
|
users, |
|
|
|
} |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
const plugin: Plugin<Server, IncomingMessage, ServerResponse, PluginOptions> = async server => { |
|
|
|
doPostRoute(server) |
|
|
|
postsByUserRoute(server) |
|
|
|
postRoute(server) |
|
|
|
} |
|
|
|
|
|
|
|