Dwayne Harris 5 years ago
parent
commit
01c65d2617
  1. 2
      src/lib/database.ts
  2. 10
      src/plugins/api/authentication.ts
  3. 18
      src/plugins/api/groups.ts
  4. 8
      src/plugins/api/index.ts
  5. 62
      src/plugins/api/posts.ts
  6. 31
      src/plugins/api/profile.ts

2
src/lib/database.ts

@ -1,7 +1,7 @@
import { CosmosClient, Container, SqlQuerySpec } from '@azure/cosmos'
interface IQueryParams {
[key: string]: string
[key: string]: string | string[] | number | boolean
}
export interface IDatabaseItem {

10
src/plugins/api/authentication.ts

@ -254,16 +254,16 @@ function selfRoute(server: FastifyInstance<Server, IncomingMessage, ServerRespon
}
server.get<DefaultQuery, DefaultParams, DefaultHeaders, DefaultBody>('/api/self', options, async (request, reply) => {
if (!request.user) return unauthorizedError(reply)
if (!request.viewer) return unauthorizedError(reply)
const container = await containerFor(server.database.client, 'Users')
const { resource: user } = await container.item(request.user.id, request.user.id).read<IUser>()
const { resource: viewer } = await container.item(request.viewer.id, request.viewer.id).read<IUser>()
if (!user) return unauthorizedError(reply)
if (!viewer) return unauthorizedError(reply)
return {
id: user.id,
name: user.name,
id: viewer.id,
name: viewer.name,
}
})
}

18
src/plugins/api/groups.ts

@ -48,15 +48,15 @@ function createRoute(server: fastify.FastifyInstance<Server, IncomingMessage, Se
}
server.post<DefaultQuery, DefaultParams, DefaultHeaders, Body>('/api/group', options, async (request, reply) => {
if (!request.user) return unauthorizedError(reply)
if (!request.viewer) return unauthorizedError(reply)
const userContainer = await containerFor(server.database.client, 'Users')
const userItem = userContainer.item(request.user.id, request.user.id)
const { resource: user } = await userItem.read<IUser>()
const viewerItem = userContainer.item(request.viewer.id, request.viewer.id)
const { resource: viewer } = await viewerItem.read<IUser>()
const groupContainer = await containerFor(server.database.client, 'Groups')
if (user.group !== '') return badRequestError(reply, 'Invalid operation')
if (viewer.group !== '') return badRequestError(reply, 'Invalid operation')
const { name, about, open, requiresApproval } = request.body
const normalizedName = normalize(name)
@ -64,7 +64,7 @@ function createRoute(server: fastify.FastifyInstance<Server, IncomingMessage, Se
id: normalizedName,
partitionKey: normalizedName,
type: 'group',
userId: request.user.id,
userId: request.viewer.id,
name,
about,
open,
@ -74,10 +74,10 @@ function createRoute(server: fastify.FastifyInstance<Server, IncomingMessage, Se
}
const membership: IGroupMembership = {
id: request.user.id,
id: request.viewer.id,
partitionKey: normalizedName,
type: 'membership',
userId: request.user.id,
userId: request.viewer.id,
pending: false,
membership: 'admin',
created: Date.now(),
@ -86,8 +86,8 @@ function createRoute(server: fastify.FastifyInstance<Server, IncomingMessage, Se
await groupContainer.items.create(group)
await groupContainer.items.create(membership)
await userItem.replace<IUser>({
...user,
await viewerItem.replace<IUser>({
...viewer,
group: group.id,
})

8
src/plugins/api/index.ts

@ -6,6 +6,7 @@ import { JWT } from '../../lib/crypto'
import { tokenFromHeader } from '../../lib/http'
import authentication from './authentication'
import groups from './groups'
import posts from './posts'
import profile from './profile'
@ -23,7 +24,7 @@ declare module "fastify" {
}
interface FastifyRequest{
user?: IUserInfo
viewer?: IUserInfo
}
}
@ -40,7 +41,7 @@ const plugin: Plugin<Server, IncomingMessage, ServerResponse, PluginOptions> = a
}
server.decorate('database', database)
server.decorateRequest('user', null)
server.decorateRequest('viewer', null)
server.addHook('preHandler', async req => {
const token = tokenFromHeader(req.headers.authorization)
@ -49,7 +50,7 @@ const plugin: Plugin<Server, IncomingMessage, ServerResponse, PluginOptions> = a
try {
const data: JWT.IJWTData = await JWT.verify(token)
req.user = {
req.viewer = {
id: data.sub
}
} catch (err) {
@ -59,6 +60,7 @@ const plugin: Plugin<Server, IncomingMessage, ServerResponse, PluginOptions> = a
})
server.register(authentication)
server.register(groups)
server.register(posts)
server.register(profile)
}

62
src/plugins/api/posts.ts

@ -52,6 +52,7 @@ const userSchema: JSONSchema = {
properties: {
id: { type: 'string' },
name: { type: 'string' },
group: { type: 'string' },
created: { type: 'number' },
},
}
@ -111,17 +112,17 @@ function doPostRoute(server: fastify.FastifyInstance<Server, IncomingMessage, Se
}
server.post<DefaultQuery, DefaultParams, DefaultHeaders, Body>('/api/post', options, async (request, reply) => {
if (!request.user) return unauthorizedError(reply)
if (!request.viewer) return unauthorizedError(reply)
let newPostRelationship: IPostRelationship = null
const postContainer = await containerFor(server.database.client, 'Posts')
const postRelationshipContainer = await containerFor(server.database.client, 'PostRelationships')
const userContainer = await containerFor(server.database.client, 'Users')
const { resource: user } = await userContainer.item(request.user.id, request.user.id).read<IUser>()
const { resource: viewer } = await userContainer.item(request.viewer.id, request.viewer.id).read<IUser>()
if (user.pending) return badRequestError(reply, 'User requires approval')
if (user.group === '') return badRequestError(reply, 'User must belong to a group')
if (viewer.pending) return badRequestError(reply, 'User requires approval')
if (viewer.group === '') return badRequestError(reply, 'User must belong to a group')
const postId = createPostId()
@ -144,7 +145,7 @@ function doPostRoute(server: fastify.FastifyInstance<Server, IncomingMessage, Se
const post: IPost = {
id: postId,
userId: request.user.id,
userId: request.viewer.id,
root: newPostRelationship ? newPostRelationship.partitionKey : postId,
parents: newPostRelationship ? newPostRelationship.parents : [],
text: trimContent(request.body.text, 1000),
@ -157,7 +158,7 @@ function doPostRoute(server: fastify.FastifyInstance<Server, IncomingMessage, Se
const userPost: IUserPost = {
postId,
partitionKey: request.user.id,
partitionKey: request.viewer.id,
type: 'post',
created: Date.now(),
}
@ -167,14 +168,14 @@ function doPostRoute(server: fastify.FastifyInstance<Server, IncomingMessage, Se
if (newPostRelationship) await postRelationshipContainer.items.create<IPostRelationship>(newPostRelationship)
const query = createQuerySpec(`SELECT u.id FROM Users u WHERE u.partitionKey = @partitionKey AND u.type = 'subscription'`, { partitionKey: request.user.id })
const query = createQuerySpec(`SELECT u.id FROM Users u WHERE u.partitionKey = @partitionKey AND u.type = 'subscription'`, { partitionKey: request.viewer.id })
const { resources: subscribers } = await userContainer.items.query<IUserSubscription>(query, {}).fetchAll()
if (subscribers.length < 500) {
for (let subscriber in subscribers) {
if (subscribers.length < 100) {
for (const subscriber of subscribers) {
await userContainer.items.create<IUserTimelinePost>({
postId,
partitionKey: subscribers[subscriber].id,
partitionKey: subscriber.id,
type: 'timeline',
created: Date.now(),
})
@ -224,26 +225,31 @@ function postsByUserRoute(server: fastify.FastifyInstance<Server, IncomingMessag
if (!user) return notFoundError(reply)
if (user.private) {
if (!request.user) return unauthorizedError(reply)
const query = createQuerySpec(`
SELECT u.id FROM Users u WHERE
u.id = @viewer
u.partitionKey = @user AND
u.type = 'subscription' AND
u.pending = false
`, {
viewer: request.user.id,
user: user.id,
})
const { resources: subscriptions } = await userContainer.items.query<IUserSubscription>(query, {}).fetchAll()
if (subscriptions.length === 0) return unauthorizedError(reply)
switch (user.privacy) {
case 'approve':
case 'private':
if (!request.viewer) return unauthorizedError(reply)
const query = createQuerySpec(`
SELECT u.id FROM Users u WHERE
u.id = @viewer
u.partitionKey = @user AND
u.type = 'subscription' AND
u.pending = false
`, {
viewer: request.viewer.id,
user: user.id,
})
const { resources: subscriptions } = await userContainer.items.query<IUserSubscription>(query, {}).fetchAll()
if (subscriptions.length === 0) return unauthorizedError(reply)
case 'group':
if (!request.viewer) return unauthorizedError(reply)
const { resource: viewer } = await userContainer.item(request.viewer.id, request.viewer.id).read<IUser>()
}
if (request.user) {
const { resource: viewer } = await userContainer.item(request.user.id, request.user.id).read<IUser>()
if (request.viewer) {
const { resource: viewer } = await userContainer.item(request.viewer.id, request.viewer.id).read<IUser>()
const query = createQuerySpec(`
SELECT u.id FROM Users u WHERE
(u.blockedId = @viewer OR u.blockedId = @viewerGroup)

31
src/plugins/api/profile.ts

@ -54,33 +54,33 @@ function updateRoute(server: fastify.FastifyInstance<Server, IncomingMessage, Se
}
server.put<DefaultQuery, DefaultParams, Headers, Body>('/api/self', options, async (request, reply) => {
if (!request.user) return unauthorizedError(reply)
if (!request.viewer) return unauthorizedError(reply)
const container = await containerFor(server.database.client, 'Users')
const userItem = container.item(request.user.id, request.user.id)
const { resource: user } = await userItem.read<IUser>()
const viewerItem = container.item(request.viewer.id, request.viewer.id)
const { resource: viewer } = await viewerItem.read<IUser>()
if (!user) return serverError(reply)
if (!viewer) return serverError(reply)
if (request.body.name) {
const name = request.body.name.trim()
if (name !== '') {
user.name = name
viewer.name = name
}
}
if (request.body.about) {
const about = request.body.about.trim()
if (about !== '') {
user.about = about
viewer.about = about
}
}
await userItem.replace<IUser>(user)
await viewerItem.replace<IUser>(viewer)
return {
id: user.id,
name: user.name,
id: viewer.id,
name: viewer.name,
}
})
}
@ -102,15 +102,15 @@ function subscribeRoute(server: fastify.FastifyInstance<Server, IncomingMessage,
}
server.post<DefaultQuery, Params, DefaultHeaders, DefaultBody>('/api/user/:id/subscribe', options, async (request, reply) => {
if (!request.user) return unauthorizedError(reply)
if (!request.viewer) return unauthorizedError(reply)
const userContainer = await containerFor(server.database.client, 'Users')
const groupContainer = await containerFor(server.database.client, 'Groups')
if (request.user.id === request.params.id) return badRequestError(reply, 'Invalid operation')
if (request.viewer.id === request.params.id) return badRequestError(reply, 'Invalid operation')
const { resource: user } = await userContainer.item(request.params.id, request.params.id).read<IUser>()
const { resource: viewer } = await userContainer.item(request.user.id, request.user.id).read<IUser>()
const { resource: viewer } = await userContainer.item(request.viewer.id, request.viewer.id).read<IUser>()
if (!user) return notFoundError(reply)
@ -131,6 +131,7 @@ function subscribeRoute(server: fastify.FastifyInstance<Server, IncomingMessage,
if (user.group !== viewer.group) return unauthorizedError(reply)
case 'approve':
pending = true
break
}
const blockQuery = createQuerySpec(`
@ -149,7 +150,7 @@ function subscribeRoute(server: fastify.FastifyInstance<Server, IncomingMessage,
await userContainer.items.create<IUserSubscription>({
subscriberId: user.id,
partitionKey: request.user.id,
partitionKey: request.viewer.id,
type: 'subscription',
pending,
created: Date.now(),
@ -176,12 +177,12 @@ function unsubscribeRoute(server: fastify.FastifyInstance<Server, IncomingMessag
}
server.post<DefaultQuery, Params, DefaultHeaders, DefaultBody>('/api/user/:id/unsubscribe', options, async (request, reply) => {
if (!request.user) return unauthorizedError(reply)
if (!request.viewer) return unauthorizedError(reply)
const userContainer = await containerFor(server.database.client, 'Users')
const { resource: user } = await userContainer.item(request.params.id, request.params.id).read<IUser>()
const { resource: viewer } = await userContainer.item(request.user.id, request.user.id).read<IUser>()
const { resource: viewer } = await userContainer.item(request.viewer.id, request.viewer.id).read<IUser>()
if (!user) return notFoundError(reply)

Loading…
Cancel
Save