Dwayne Harris
5 years ago
11 changed files with 392 additions and 117 deletions
-
4src/constants.ts
-
36src/lib/media.ts
-
114src/plugins/api/apps.ts
-
51src/plugins/api/authentication.ts
-
58src/plugins/api/groups.ts
-
4src/plugins/api/index.ts
-
143src/plugins/api/media.ts
-
51src/plugins/api/uploads.ts
-
15src/plugins/api/users.ts
-
8src/schemas.ts
-
25src/types/collections.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, |
||||
|
}) |
||||
|
} |
@ -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 |
@ -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 |
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue