Dwayne Harris 5 years ago
parent
commit
2b8652c133
  1. 4
      src/constants.ts
  2. 36
      src/lib/media.ts
  3. 114
      src/plugins/api/apps.ts
  4. 51
      src/plugins/api/authentication.ts
  5. 58
      src/plugins/api/groups.ts
  6. 4
      src/plugins/api/index.ts
  7. 143
      src/plugins/api/media.ts
  8. 51
      src/plugins/api/uploads.ts
  9. 15
      src/plugins/api/users.ts
  10. 8
      src/schemas.ts
  11. 25
      src/types/collections.ts

4
src/constants.ts

@ -6,4 +6,6 @@ export const SHORT_TEXT_LENGTH = 100
export const SUBSCRIBER_MAX_SIZE = 100
export const GROUP_LISTING_PARTITION_KEY = 'pk'
export const APP_PARTITION_KEY = 'pk'
export const APP_PARTITION_KEY = 'apk'
export const INSTALLATION_PARTITION_KEY = 'ipk'
export const MEDIA_PARTITION_KEY = 'pk'

36
src/lib/media.ts

@ -0,0 +1,36 @@
import { Container } from '@azure/cosmos'
import { BlockBlobURL, SharedKeyCredential, Aborter, ContainerSASPermissions, generateBlobSASQueryParameters, AnonymousCredential } from '@azure/storage-blob'
import moment from 'moment'
import { MEDIA_PARTITION_KEY } from '../constants'
import { Media } from '../types/collections'
export function generateSAS(permissions: string, expirationMinutes: number) {
const sharedKeyCredential = new SharedKeyCredential(process.env.BLOB_STORAGE_ACCOUNT!, process.env.BLOB_STORAGE_ACCOUNT_KEY!)
return generateBlobSASQueryParameters({
containerName: process.env.BLOB_STORAGE_CONTAINER!,
permissions: ContainerSASPermissions.parse(permissions).toString(),
startTime: new Date(),
expiryTime: moment().add(expirationMinutes, 'm').toDate(),
}, sharedKeyCredential).toString()
}
export async function deleteMedia(name: string) {
const blockBlobURL = new BlockBlobURL(
`https://${process.env.BLOB_STORAGE_ACCOUNT!}.blob.core.windows.net/${process.env.BLOB_STORAGE_CONTAINER!}/${name}`,
BlockBlobURL.newPipeline(new SharedKeyCredential(process.env.BLOB_STORAGE_ACCOUNT!, process.env.BLOB_STORAGE_ACCOUNT_KEY!))
)
await blockBlobURL.delete(Aborter.none)
}
export async function attachMedia(container: Container, name: string) {
const mediaItem = container.item(name, MEDIA_PARTITION_KEY)
const { resource: media } = await mediaItem.read<Media>()
await mediaItem.replace<Media>({
...media,
attached: true,
})
}

114
src/plugins/api/apps.ts

@ -16,11 +16,12 @@ import { getUsers } from '../../lib/collections'
import { generateString } from '../../lib/crypto'
import { containerFor, getItem, normalize, queryItems, createQuerySpec } from '../../lib/database'
import { unauthorizedError, serverError, badRequestError, notFoundError } from '../../lib/errors'
import { attachMedia, deleteMedia } from '../../lib/media'
import { createInstallationId } from '../../lib/utils'
import { APP_PARTITION_KEY, MAX_NAME_LENGTH } from '../../constants'
import { APP_PARTITION_KEY, MAX_NAME_LENGTH, INSTALLATION_PARTITION_KEY } from '../../constants'
import { App, User } from '../../types/collections'
import { App, User, Installation } from '../../types/collections'
import { PluginOptions } from '../../types'
function availabilityRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) {
@ -322,6 +323,12 @@ function createRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
created: Date.now(),
})
const mediaContainer = containerFor(server.database.client, 'Media')
if (imageUrl) await attachMedia(mediaContainer, imageUrl)
if (coverImageUrl) await attachMedia(mediaContainer, coverImageUrl)
if (iconImageUrl) await attachMedia(mediaContainer, iconImageUrl)
return {
id,
}
@ -388,6 +395,7 @@ function updateRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
if (!request.viewer) return unauthorizedError(reply)
const container = containerFor(server.database.client, 'Apps')
const mediaContainer = containerFor(server.database.client, 'Media')
const {
version,
@ -410,6 +418,14 @@ function updateRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
const { resource: app } = await appItem.read<App>()
if (!app) return notFoundError(reply)
if (app.imageUrl && !imageUrl) await deleteMedia(app.imageUrl)
if (app.coverImageUrl && !coverImageUrl) await deleteMedia(app.coverImageUrl)
if (app.iconImageUrl && !iconImageUrl) await deleteMedia(app.iconImageUrl)
if (!app.imageUrl && imageUrl) await attachMedia(mediaContainer, imageUrl)
if (!app.coverImageUrl && coverImageUrl) await attachMedia(mediaContainer, coverImageUrl)
if (!app.iconImageUrl && iconImageUrl) await attachMedia(mediaContainer, iconImageUrl)
await appItem.replace<App>({
...app,
version,
@ -449,7 +465,7 @@ function updateRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
updated: Date.now(),
})
reply.code(201)
reply.code(204)
})
}
@ -485,20 +501,8 @@ function getRoute(server: FastifyInstance<Server, IncomingMessage, ServerRespons
if (!app) return notFoundError(reply)
const userContainer = containerFor(server.database.client, 'Users')
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) {
attributes = [...attributes, 'publicKey', 'privateKey', 'revisions', 'composerUrl', 'rendererUrl']
}
@ -509,7 +513,6 @@ function getRoute(server: FastifyInstance<Server, IncomingMessage, ServerRespons
container: userContainer,
id: app.userId,
}),
installed,
}
})
}
@ -548,7 +551,20 @@ function installRoute(server: FastifyInstance<Server, IncomingMessage, ServerRes
const { resource: viewer } = await viewerItem.read<User>()
if (viewer.installations.find(i => i.appId === app.id)) {
const installations = await queryItems<Installation>({
container: appContainer,
query: createQuerySpec(
`SELECT a.id FROM Apps a WHERE a.pk = @pk AND a.userId = @userId AND a.appId = @appId`,
{
pk: INSTALLATION_PARTITION_KEY,
userId: request.viewer.id,
appId: app.id,
}
),
logger: request.log,
})
if (installations.length > 0) {
reply.code(204)
return
}
@ -558,16 +574,22 @@ function installRoute(server: FastifyInstance<Server, IncomingMessage, ServerRes
users: app.users + 1,
})
const installation: Installation = {
id: createInstallationId(),
pk: INSTALLATION_PARTITION_KEY,
userId: request.viewer.id,
appId: app.id,
settings: {},
created: Date.now(),
}
await appContainer.items.create<Installation>(installation)
await viewerItem.replace<User>({
...viewer,
installations: [
...viewer.installations,
{
id: createInstallationId(),
appId: app.id,
settings: {},
created: Date.now(),
},
installation.id,
]
})
@ -609,11 +631,27 @@ function uninstallRoute(server: FastifyInstance<Server, IncomingMessage, ServerR
const { resource: viewer } = await viewerItem.read<User>()
if (!viewer.installations.find(i => i.appId === app.id)) {
const installations = await queryItems<Installation>({
container: appContainer,
query: createQuerySpec(
`SELECT a.id FROM Apps a WHERE a.pk = @pk AND a.userId = @userId AND a.appId = @appId`,
{
pk: INSTALLATION_PARTITION_KEY,
userId: request.viewer.id,
appId: app.id,
}
),
logger: request.log,
})
if (installations.length === 0) {
reply.code(204)
return
}
const installation = installations[0]
const installationItem = appContainer.item(installation.id, INSTALLATION_PARTITION_KEY)
await appItem.replace<App>({
...app,
users: app.users - 1,
@ -621,9 +659,11 @@ function uninstallRoute(server: FastifyInstance<Server, IncomingMessage, ServerR
await viewerItem.replace<User>({
...viewer,
installations: viewer.installations.filter(i => i.appId !== app.id),
installations: viewer.installations.filter(i => i !== installation.id),
})
await installationItem.delete()
reply.code(204)
})
}
@ -640,6 +680,7 @@ function installationsRoute(server: FastifyInstance<Server, IncomingMessage, Ser
items: {
type: 'object',
properties: {
id: { type: 'string' },
app: appSchema,
settings: { type: 'object' },
created: { type: 'number' },
@ -664,19 +705,28 @@ function installationsRoute(server: FastifyInstance<Server, IncomingMessage, Ser
if (!viewer) return unauthorizedError(reply)
const apps = await queryItems<User>({
container: containerFor(server.database.client, 'Apps'),
const container = containerFor(server.database.client, 'Apps')
const installations = await queryItems<Installation>({
container,
query: createQuerySpec(
`SELECT * FROM Apps a WHERE ARRAY_CONTAINS(@ids, a.id)`,
{
ids: viewer.installations.map(installation => installation.appId),
}
`SELECT * FROM Apps a WHERE a.pk = @pk AND ARRAY_CONTAINS(@ids, a.id)`,
{ ids: viewer.installations, pk: INSTALLATION_PARTITION_KEY }
),
logger: request.log,
})
const apps = await queryItems<App>({
container,
query: createQuerySpec(
`SELECT * FROM Apps a WHERE a.pk = @pk AND ARRAY_CONTAINS(@ids, a.id)`,
{ ids: installations.map(i => i.appId), pk: APP_PARTITION_KEY }
),
logger: request.log,
})
return {
installations: viewer.installations.map(installation => {
installations: installations.map(installation => {
return {
...installation,
app: apps.find(app => app.id === installation.appId),

51
src/plugins/api/authentication.ts

@ -10,13 +10,14 @@ import {
import { Server, IncomingMessage, ServerResponse } from 'http'
import { MIN_ID_LENGTH, MAX_ID_LENGTH, MAX_NAME_LENGTH, MIN_PASSWORD_LENGTH } from '../../constants'
import { MIN_ID_LENGTH, MAX_ID_LENGTH, MAX_NAME_LENGTH, MIN_PASSWORD_LENGTH, INSTALLATION_PARTITION_KEY } from '../../constants'
import { tokenResponseSchema, selfSchema, errorSchema } from '../../schemas'
import { createAccessToken, createRefreshToken } from '../../lib/authentication'
import { hashPassword, verifyPassword, JWT } from '../../lib/crypto'
import { containerFor, getItem, queryItems, normalize, createQuerySpec } from '../../lib/database'
import { badRequestError, badRequestFormError, unauthorizedError, serverError } from '../../lib/errors'
import { tokenFromHeader } from '../../lib/http'
import { attachMedia } from '../../lib/media'
import { createInstallationId } from '../../lib/utils'
import {
@ -32,6 +33,7 @@ import {
GroupMembershipType,
GroupRegistrationType,
App,
Installation,
} from '../../types/collections'
import { PluginOptions } from '../../types'
@ -43,6 +45,9 @@ function registerRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
password: string
requiresApproval: boolean
privacy: string
about?: string
imageUrl?: string
coverImageUrl?: string
group?: string
invitation?: string
}
@ -75,6 +80,9 @@ function registerRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
type: 'string',
enum: ['public', 'group', 'subscribers', 'private'],
},
about: { type: 'string' },
imageUrl: { type: 'string' },
coverImageUrl: { type: 'string' },
group: { type: 'string' },
invitation: { type: 'string' },
},
@ -89,7 +97,7 @@ function registerRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
server.post<DefaultQuery, DefaultParams, DefaultHeaders, Body>('/api/register', options, async (request, reply) => {
if (!server.database) return serverError(reply)
const { name, email, password, requiresApproval, privacy, invitation: code } = request.body
const { name, email, password, requiresApproval, privacy, about, imageUrl, coverImageUrl, invitation: code } = request.body
const id = normalize(request.body.id)
const userContainer = containerFor(server.database.client, 'Users')
@ -137,33 +145,47 @@ function registerRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
groupPartial = {
id: group.id,
name: group.name,
imageUrl: group.imageUrl,
coverImageUrl: group.coverImageUrl,
iconImageUrl: group.iconImageUrl,
}
}
const appContainer = containerFor(server.database.client, 'Apps')
const apps = await queryItems<App>({
container: containerFor(server.database.client, 'Apps'),
container: appContainer,
query: 'SELECT * FROM Apps a WHERE a.active = true AND a.preinstall = true',
logger: request.log,
})
const installations: string[] = []
for (const app of apps) {
const installation: Installation = {
id: createInstallationId(),
pk: INSTALLATION_PARTITION_KEY,
userId: id,
appId: app.id,
settings: {},
created: Date.now(),
}
await appContainer.items.create<Installation>(installation)
installations.push(installation.id)
}
const user: User = {
id,
pk: id,
t: UserItemType.User,
group: groupPartial,
name,
about: '',
about,
email,
emailVerified: false,
passwordHash: await hashPassword(password),
installations: apps.map(app => ({
id: createInstallationId(),
appId: app.id,
settings: {},
created: Date.now(),
})),
imageUrl,
coverImageUrl,
installations,
awards: 0,
points: 0,
balance: 0,
@ -205,6 +227,11 @@ function registerRoute(server: FastifyInstance<Server, IncomingMessage, ServerRe
}
}
const mediaContainer = containerFor(server.database.client, 'Media')
if (imageUrl) await attachMedia(mediaContainer, imageUrl)
if (coverImageUrl) await attachMedia(mediaContainer, coverImageUrl)
return {
id,
access: await createAccessToken(id),

58
src/plugins/api/groups.ts

@ -17,6 +17,7 @@ import { unauthorizedError, badRequestError, notFoundError, serverError } from '
import { getUsers, getUserMembership } from '../../lib/collections'
import { containerFor, createQuerySpec, queryItems, getItem, normalize } from '../../lib/database'
import { createInvitationCode } from '../../lib/utils'
import { attachMedia, deleteMedia } from '../../lib/media'
import {
User,
@ -89,6 +90,9 @@ function createRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
name: string
about?: string
registration: GroupRegistrationType
imageUrl?: string
coverImageUrl?: string
iconImageUrl?: string
}
const options: RouteShorthandOptions = {
@ -107,6 +111,9 @@ function createRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
type: 'string',
enum: ['open', 'approval', 'closed'],
},
imageUrl: { type: 'string' },
coverImageUrl: { type: 'string' },
iconImageUrl: { type: 'string' },
},
},
response: {
@ -132,7 +139,7 @@ function createRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
if (viewer.group) return badRequestError(reply)
const { name, about, registration } = request.body
const { name, about, registration, imageUrl, coverImageUrl, iconImageUrl } = request.body
const id = normalize(name)
const existingGroup = await getItem<Group>({ container: groupContainer, id })
@ -146,6 +153,9 @@ function createRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
name,
about,
registration,
imageUrl,
coverImageUrl,
iconImageUrl,
status: GroupStatus.Pending,
active: true,
created: Date.now(),
@ -169,8 +179,7 @@ function createRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
group: {
id: group.id,
name: group.name,
imageUrl: group.imageUrl,
coverImageUrl: group.coverImageUrl,
iconImageUrl: group.iconImageUrl,
},
})
@ -182,6 +191,12 @@ function createRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
created: Date.now(),
})
const mediaContainer = containerFor(server.database.client, 'Media')
if (imageUrl) await attachMedia(mediaContainer, imageUrl)
if (coverImageUrl) await attachMedia(mediaContainer, coverImageUrl)
if (iconImageUrl) await attachMedia(mediaContainer, iconImageUrl)
return {
id: group.id,
}
@ -264,6 +279,9 @@ function updateRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
name?: string
about?: string
registration?: string
imageUrl?: string
coverImageUrl?: string
iconImageUrl?: string
}
const options: RouteShorthandOptions = {
@ -281,6 +299,9 @@ function updateRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
type: 'string',
enum: ['open', 'approval', 'closed'],
},
imageUrl: { type: 'string' },
coverImageUrl: { type: 'string' },
iconImageUrl: { type: 'string' },
},
},
response: {
@ -306,16 +327,41 @@ function updateRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
const { resource: groupListing } = await groupListingItem.read<GroupListing>()
const {
name,
about,
registration,
imageUrl,
coverImageUrl,
iconImageUrl,
} = request.body
const mediaContainer = containerFor(server.database.client, 'Media')
if (group.imageUrl && !imageUrl) await deleteMedia(group.imageUrl)
if (group.coverImageUrl && !coverImageUrl) await deleteMedia(group.coverImageUrl)
if (group.iconImageUrl && !iconImageUrl) await deleteMedia(group.iconImageUrl)
if (!group.imageUrl && imageUrl) await attachMedia(mediaContainer, imageUrl)
if (!group.coverImageUrl && coverImageUrl) await attachMedia(mediaContainer, coverImageUrl)
if (!group.iconImageUrl && iconImageUrl) await attachMedia(mediaContainer, iconImageUrl)
interface Updates {
name?: string
about?: string
registration?: GroupRegistrationType
imageUrl?: string
coverImageUrl?: string
iconImageUrl?: string
}
let updates: Updates = {}
if (request.body.name) updates.name = request.body.name
if (request.body.about) updates.about = request.body.about
if (request.body.registration) updates.registration = request.body.registration as GroupRegistrationType
if (name) updates.name = name
if (about) updates.about = about
if (registration) updates.registration = registration as GroupRegistrationType
if (imageUrl) updates.imageUrl = imageUrl
if (coverImageUrl) updates.coverImageUrl = coverImageUrl
if (iconImageUrl) updates.iconImageUrl = iconImageUrl
await groupItem.replace<Group>({
...group,

4
src/plugins/api/index.ts

@ -9,8 +9,8 @@ import { tokenFromHeader } from '../../lib/http'
import apps from './apps'
import authentication from './authentication'
import groups from './groups'
import media from './media'
import posts from './posts'
import uploads from './uploads'
import users from './users'
import { PluginOptions, HttpError } from '../../types'
@ -83,8 +83,8 @@ const plugin: Plugin<Server, IncomingMessage, ServerResponse, PluginOptions> = a
server.register(apps)
server.register(authentication)
server.register(groups)
server.register(media)
server.register(posts)
server.register(uploads)
server.register(users)
}

143
src/plugins/api/media.ts

@ -0,0 +1,143 @@
import {
FastifyInstance,
Plugin,
DefaultQuery,
DefaultParams,
DefaultHeaders,
DefaultBody,
RouteShorthandOptions,
} from 'fastify'
import { Server, IncomingMessage, ServerResponse } from 'http'
import { MEDIA_PARTITION_KEY } from '../../constants'
import { errorSchema } from '../../schemas'
import { containerFor, getItem } from '../../lib/database'
import { badRequestError, serverError } from '../../lib/errors'
import { deleteMedia, generateSAS } from '../../lib/media'
import { createId } from '../../lib/utils'
import { Media } from '../../types/collections'
import { PluginOptions } from '../../types'
function getSASRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) {
const options: RouteShorthandOptions = {
schema: {
response: {
200: {
type: 'object',
properties: {
sas: { type: 'string' },
id: { type: 'string' },
},
},
},
},
}
server.get<DefaultQuery, DefaultParams, DefaultHeaders, DefaultBody>('/api/sas', options, async () => {
return {
sas: generateSAS('arcw', 5),
id: createId(),
}
})
}
function addRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) {
interface Body {
name: string
size: number
type: string
originalName: string
}
const options: RouteShorthandOptions = {
schema: {
body: {
type: 'object',
required: ['name', 'size', 'type', 'originalName'],
properties: {
name: { type: 'string' },
size: { type: 'number' },
type: { type: 'string' },
originalName: { type: 'string' },
},
},
response: {
400: errorSchema,
},
},
}
server.post<DefaultQuery, DefaultParams, DefaultHeaders, Body>('/api/media', options, async (request, reply) => {
if (!server.database) return serverError(reply)
const { name, size, type, originalName } = request.body
const container = containerFor(server.database.client, 'Media')
const item = await getItem<Media>({
container,
id: name,
partitionKey: MEDIA_PARTITION_KEY,
})
reply.code(204)
if (item) return
await container.items.create<Media>({
id: name,
pk: MEDIA_PARTITION_KEY,
size,
type,
originalName,
attached: false,
created: Date.now(),
})
})
}
function deleteRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) {
interface Body {
name: string
}
const options: RouteShorthandOptions = {
schema: {
body: {
type: 'object',
required: ['name'],
properties: {
name: { type: 'string' },
},
},
response: {
400: errorSchema,
},
},
}
server.post<DefaultQuery, DefaultParams, DefaultHeaders, Body>('/api/media/delete', options, async (request, reply) => {
if (!server.database) return serverError(reply)
const mediaItem = containerFor(server.database.client, 'Media').item(request.body.name, MEDIA_PARTITION_KEY)
const { resource: media } = await mediaItem.read<Media>()
if (!media) return badRequestError(reply)
reply.code(204)
if (media.attached) return
await mediaItem.delete()
await deleteMedia(request.body.name)
})
}
const plugin: Plugin<Server, IncomingMessage, ServerResponse, PluginOptions> = async server => {
getSASRoute(server)
addRoute(server)
deleteRoute(server)
}
export default plugin

51
src/plugins/api/uploads.ts

@ -1,51 +0,0 @@
import {
FastifyInstance,
Plugin,
DefaultQuery,
DefaultParams,
DefaultHeaders,
DefaultBody,
RouteShorthandOptions,
} from 'fastify'
import { Server, IncomingMessage, ServerResponse } from 'http'
import moment from 'moment'
import { SharedKeyCredential, ContainerSASPermissions, generateBlobSASQueryParameters } from '@azure/storage-blob'
import { createId } from '../../lib/utils'
import { PluginOptions } from '../../types'
function getSASRoute(server: FastifyInstance<Server, IncomingMessage, ServerResponse>) {
const options: RouteShorthandOptions = {
schema: {
response: {
200: {
type: 'object',
properties: {
sas: { type: 'string' },
id: { type: 'string' },
},
},
},
},
}
server.get<DefaultQuery, DefaultParams, DefaultHeaders, DefaultBody>('/api/sas', options, async () => {
const sharedKeyCredential = new SharedKeyCredential(process.env.BLOB_STORAGE_ACCOUNT!, process.env.BLOB_STORAGE_ACCOUNT_KEY!)
return {
sas: generateBlobSASQueryParameters({
containerName: process.env.BLOB_STORAGE_CONTAINER!,
permissions: ContainerSASPermissions.parse('arcw').toString(),
startTime: new Date(),
expiryTime: moment().add(5, 'm').toDate(),
}, sharedKeyCredential).toString(),
id: createId(),
}
})
}
const plugin: Plugin<Server, IncomingMessage, ServerResponse, PluginOptions> = async server => {
getSASRoute(server)
}
export default plugin

15
src/plugins/api/users.ts

@ -13,6 +13,7 @@ import { Server, IncomingMessage, ServerResponse } from 'http'
import { unauthorizedError, serverError, notFoundError, badRequestError } from '../../lib/errors'
import { getUserBlocks } from '../../lib/collections'
import { containerFor, createQuerySpec, queryItems, getItem, normalize } from '../../lib/database'
import { deleteMedia, attachMedia } from '../../lib/media'
import { MAX_NAME_LENGTH } from '../../constants'
import { userSchema, selfSchema, errorSchema } from '../../schemas'
@ -140,14 +141,20 @@ function updateRoute(server: FastifyInstance<Server, IncomingMessage, ServerResp
viewer.privacy = request.body.privacy
}
const mediaContainer = containerFor(server.database.client, 'Media')
if (viewer.imageUrl && !request.body.imageUrl) await deleteMedia(viewer.imageUrl)
if (viewer.coverImageUrl && !request.body.coverImageUrl) await deleteMedia(viewer.coverImageUrl)
if (!viewer.imageUrl && request.body.imageUrl) await attachMedia(mediaContainer, request.body.imageUrl)
if (!viewer.coverImageUrl && request.body.coverImageUrl) await attachMedia(mediaContainer, request.body.coverImageUrl)
if (request.body.imageUrl) {
const imageUrl = request.body.imageUrl.trim()
if (imageUrl !== '') viewer.imageUrl = imageUrl
viewer.imageUrl = request.body.imageUrl
}
if (request.body.coverImageUrl) {
const coverImageUrl = request.body.coverImageUrl.trim()
if (coverImageUrl !== '') viewer.coverImageUrl = coverImageUrl
viewer.coverImageUrl = request.body.coverImageUrl
}
await viewerItem.replace<User>(viewer)

8
src/schemas.ts

@ -35,8 +35,7 @@ export const userSchema: JSONSchema = {
properties: {
id: { type: 'string' },
name: { type: 'string' },
imageUrl: { type: 'string' },
coverImageUrl: { type: 'string' },
iconImageUrl: { type: 'string' },
},
},
subscription: { type: 'string' },
@ -63,7 +62,6 @@ export const appSchema: JSONSchema = {
users: { type: 'number' },
updated: { type: 'number' },
created: { type: 'number' },
installed: { type: 'boolean' },
publicKey: { type: 'string' },
privateKey: { type: 'string' },
@ -92,8 +90,7 @@ export const selfSchema: JSONSchema = {
properties: {
id: { type: 'string' },
name: { type: 'string' },
imageUrl: { type: 'string' },
coverImageUrl: { type: 'string' },
iconImageUrl: { type: 'string' },
},
},
requiresApproval: { type: 'boolean' },
@ -122,6 +119,7 @@ export const groupListingSchema: JSONSchema = {
about: { type: 'string' },
imageUrl: { type: 'string' },
coverImageUrl: { type: 'string' },
iconImageUrl: { type: 'string' },
requiresApproval: { type: 'boolean' },
members: { type: 'number' },
posts: { type: 'number' },

25
src/types/collections.ts

@ -12,7 +12,12 @@
// - Partition Key: pk (postId)
// Points: total reward value + likes
import { GROUP_LISTING_PARTITION_KEY, APP_PARTITION_KEY } from '../constants'
import {
GROUP_LISTING_PARTITION_KEY,
APP_PARTITION_KEY,
INSTALLATION_PARTITION_KEY,
MEDIA_PARTITION_KEY,
} from '../constants'
export enum UserItemType {
User = 'user',
@ -102,6 +107,7 @@ export interface Group {
codeOfConduct?: string
imageUrl?: string
coverImageUrl?: string
iconImageUrl?: string
registration: GroupRegistrationType
status: GroupStatus
active: boolean
@ -111,8 +117,7 @@ export interface Group {
export interface GroupPartial {
id: string
name: string
imageUrl?: string
coverImageUrl?: string
iconImageUrl?: string
}
export interface GroupMembership {
@ -179,7 +184,7 @@ export interface User {
email: string
emailVerified: boolean
passwordHash: string
installations: Installation[]
installations: string[]
awards: number // Total Awards
points: number
balance: number // Currency (Flex)
@ -307,6 +312,8 @@ export interface PostRelationship {
export interface Installation {
id: string
pk: typeof INSTALLATION_PARTITION_KEY
userId: string
appId: string
settings: object
created: number
@ -358,3 +365,13 @@ export interface App {
updated: number
created: number
}
export interface Media {
id: string
pk: typeof MEDIA_PARTITION_KEY
size: number
type: string
originalName: string
attached: boolean
created: number
}
Loading…
Cancel
Save