Dwayne Harris 5 years ago
parent
commit
37c99b37f2
  1. 217
      src/plugins/api/apps.ts
  2. 23
      src/plugins/api/authentication.ts
  3. 14
      src/schemas.ts
  4. 1
      src/types/collections.ts

217
src/plugins/api/apps.ts

@ -14,8 +14,9 @@ import pick from 'lodash/pick'
import { appSchema, errorSchema } from '../../schemas'
import { getUsers } from '../../lib/collections'
import { generateString } from '../../lib/crypto'
import { containerFor, getItem, normalize } from '../../lib/database'
import { containerFor, getItem, normalize, queryItems, createQuerySpec } from '../../lib/database'
import { unauthorizedError, serverError, badRequestError, notFoundError } from '../../lib/errors'
import { createInstallationId } from '../../lib/utils'
import { APP_PARTITION_KEY, MAX_NAME_LENGTH } from '../../constants'
@ -120,6 +121,7 @@ function appsRoute(server: FastifyInstance<Server, IncomingMessage, ServerRespon
a.websiteUrl,
a.companyName
a.rating,
a.users,
a.created
FROM Apps a
WHERE a.pk = '${APP_PARTITION_KEY}' AND a.active = true ORDER BY a.${sort}`,
@ -193,6 +195,7 @@ function selfAppsRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
a.websiteUrl,
a.companyName
a.rating,
a.users,
a.created
FROM Apps a
WHERE a.pk = '${APP_PARTITION_KEY}' AND a.userId = ${request.viewer.id} ORDER BY a.${sort}`,
@ -309,6 +312,7 @@ function createRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
initCallbackUrl,
composeCallbackUrl,
rating: 0,
users: 0,
publicKey: generateString(20),
privateKey: generateString(40),
preinstall: false,
@ -479,24 +483,206 @@ function getRoute(server: FastifyInstance<Server, IncomingMessage, ServerRespons
})
if (!app) return notFoundError(reply)
const userContainer = containerFor(server.database.client, 'Users')
const user = await getItem<User>({
container: containerFor(server.database.client, 'Users'),
id: app.userId,
})
let installed = false
let attributes = ['id', 'version', 'name', 'imageUrl', 'coverImageUrl', 'iconImageUrl', 'about', 'websiteUrl', 'companyName', 'rating', 'users', 'updated', 'created']
if (request.viewer) {
const viewer = await getItem<User>({
container: userContainer,
id: request.viewer.id,
})
if (viewer && viewer.installations.find(i => i.appId === app.id)) {
installed = true
}
}
if (request.viewer && request.viewer.id === app.userId) {
const attributes = ['id', 'version', 'name', 'imageUrl', 'coverImageUrl', 'iconImageUrl', 'about', 'websiteUrl', 'companyName', 'rating', 'updated', 'created', 'publicKey', 'privateKey', 'revisions']
return {
...pick(app, attributes),
user,
attributes = [...attributes, 'publicKey', 'privateKey', 'revisions']
}
return {
...pick(app, attributes),
user: await getItem<User>({
container: userContainer,
id: app.userId,
}),
installed,
}
})
}
function installRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) {
interface Params {
id: string
}
const options: RouteShorthandOptions = {
schema: {
params: {
type: 'object',
properties: {
id: { type: 'string' },
},
},
response: {
400: errorSchema,
}
} else {
const attributes = ['id', 'version', 'name', 'imageUrl', 'coverImageUrl', 'iconImageUrl', 'about', 'websiteUrl', 'companyName', 'rating', 'updated', 'created']
return {
...pick(app, attributes),
user,
},
}
server.post<DefaultQuery, Params, DefaultHeaders, DefaultBody>('/api/app/:id/install', options, async (request, reply) => {
if (!server.database) return serverError(reply)
if (!request.viewer) return unauthorizedError(reply)
const appContainer = containerFor(server.database.client, 'Apps')
const userContainer = containerFor(server.database.client, 'Users')
const appItem = appContainer.item(request.params.id, APP_PARTITION_KEY)
const viewerItem = userContainer.item(request.viewer.id, request.viewer.id)
const { resource: app } = await appItem.read<App>()
if (!app) return notFoundError(reply)
const { resource: viewer } = await viewerItem.read<User>()
if (viewer.installations.find(i => i.appId === app.id)) {
reply.code(204)
return
}
await appItem.replace<App>({
...app,
users: app.users + 1,
})
await viewerItem.replace<User>({
...viewer,
installations: [
...viewer.installations,
{
id: createInstallationId(),
appId: app.id,
settings: {},
created: Date.now(),
},
]
})
reply.code(204)
})
}
function uninstallRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) {
interface Params {
id: string
}
const options: RouteShorthandOptions = {
schema: {
params: {
type: 'object',
properties: {
id: { type: 'string' },
},
},
response: {
400: errorSchema,
}
},
}
server.post<DefaultQuery, Params, DefaultHeaders, DefaultBody>('/api/app/:id/uninstall', options, async (request, reply) => {
if (!server.database) return serverError(reply)
if (!request.viewer) return unauthorizedError(reply)
const appContainer = containerFor(server.database.client, 'Apps')
const userContainer = containerFor(server.database.client, 'Users')
const appItem = appContainer.item(request.params.id, APP_PARTITION_KEY)
const viewerItem = userContainer.item(request.viewer.id, request.viewer.id)
const { resource: app } = await appItem.read<App>()
if (!app) return notFoundError(reply)
const { resource: viewer } = await viewerItem.read<User>()
if (!viewer.installations.find(i => i.appId === app.id)) {
reply.code(204)
return
}
await appItem.replace<App>({
...app,
users: app.users - 1,
})
await viewerItem.replace<User>({
...viewer,
installations: viewer.installations.filter(i => i.appId !== app.id),
})
reply.code(204)
})
}
function installationsRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) {
const options: RouteShorthandOptions = {
schema: {
response: {
200: {
type: 'object',
properties: {
installations: {
type: 'array',
items: {
type: 'object',
properties: {
app: appSchema,
settings: { type: 'object' },
created: { type: 'number' },
},
},
},
},
},
400: errorSchema,
},
},
}
server.get<DefaultQuery, DefaultParams, DefaultHeaders, DefaultBody>('/api/installations', options, async (request, reply) => {
if (!server.database) return serverError(reply)
if (!request.viewer) return unauthorizedError(reply)
const viewer = await getItem<User>({
container: containerFor(server.database.client, 'Users'),
id: request.viewer.id,
})
if (!viewer) return unauthorizedError(reply)
const apps = await queryItems<User>({
container: containerFor(server.database.client, 'Apps'),
query: createQuerySpec(
`SELECT * FROM Apps a WHERE ARRAY_CONTAINS(@ids, a.id)`,
{
ids: viewer.installations.map(installation => installation.appId),
}
),
logger: request.log,
})
return {
installations: viewer.installations.map(installation => {
return {
...installation,
app: apps.find(app => app.id === installation.appId),
appId: undefined,
}
}),
}
})
}
@ -508,6 +694,9 @@ const plugin: Plugin<Server, IncomingMessage, ServerResponse, PluginOptions> = a
createRoute(server)
updateRoute(server)
getRoute(server)
installRoute(server)
uninstallRoute(server)
installationsRoute(server)
}
export default plugin

23
src/plugins/api/authentication.ts

@ -358,28 +358,7 @@ function selfRoute(server: FastifyInstance<Server, IncomingMessage, ServerRespon
})
if (!viewer) return unauthorizedError(reply)
const apps = await queryItems<User>({
container: containerFor(server.database.client, 'Apps'),
query: createQuerySpec(
`SELECT * FROM Apps a WHERE ARRAY_CONTAINS(@ids, a.id)`,
{
ids: viewer.installations.map(installation => installation.appId),
}
),
logger: request.log,
})
return {
...viewer,
installations: viewer.installations.map(installation => {
return {
...installation,
app: apps.find(app => app.id === installation.appId),
appId: undefined,
}
}),
}
return viewer
})
}

14
src/schemas.ts

@ -59,8 +59,11 @@ export const appSchema: JSONSchema = {
websiteUrl: { type: 'string' },
companyName: { type: 'string' },
version: { type: 'string' },
rating: { type: 'number' },
users: { type: 'number' },
updated: { type: 'number' },
created: { type: 'number' },
installed: { type: 'boolean' },
publicKey: { type: 'string' },
privateKey: { type: 'string' },
@ -91,17 +94,6 @@ export const selfSchema: JSONSchema = {
coverImageUrl: { type: 'string' },
},
},
installations: {
type: 'array',
items: {
type: 'object',
properties: {
app: appSchema,
settings: { type: 'object' },
created: { type: 'number' },
},
},
},
requiresApproval: { type: 'boolean' },
privacy: { type: 'string' },
membership: { type: 'string' },

1
src/types/collections.ts

@ -349,6 +349,7 @@ export interface App {
initCallbackUrl?: string
composeCallbackUrl?: string
rating: number
users: number
publicKey: string
privateKey: string
preinstall: boolean

Loading…
Cancel
Save