Changeset View
Changeset View
Standalone View
Standalone View
keyserver/src/uploads/uploads.js
// @flow | // @flow | ||||
import type { $Request, $Response, Middleware } from 'express'; | import type { $Request, $Response, Middleware } from 'express'; | ||||
import invariant from 'invariant'; | import invariant from 'invariant'; | ||||
import multer from 'multer'; | import multer from 'multer'; | ||||
import { Readable } from 'stream'; | import { Readable } from 'stream'; | ||||
import t from 'tcomb'; | import t, { type TInterface } from 'tcomb'; | ||||
import type { | import { | ||||
UploadMediaMetadataRequest, | type UploadMediaMetadataRequest, | ||||
UploadMultimediaResult, | type UploadMultimediaResult, | ||||
UploadDeletionRequest, | uploadMultimediaResultValidator, | ||||
Dimensions, | type UploadDeletionRequest, | ||||
type Dimensions, | |||||
} from 'lib/types/media-types.js'; | } from 'lib/types/media-types.js'; | ||||
import { ServerError } from 'lib/utils/errors.js'; | import { ServerError } from 'lib/utils/errors.js'; | ||||
import { tShape } from 'lib/utils/validation-utils.js'; | import { tShape, tID } from 'lib/utils/validation-utils.js'; | ||||
import { getMediaType, validateAndConvert } from './media-utils.js'; | import { getMediaType, validateAndConvert } from './media-utils.js'; | ||||
import type { UploadInput } from '../creators/upload-creator.js'; | import type { UploadInput } from '../creators/upload-creator.js'; | ||||
import createUploads from '../creators/upload-creator.js'; | import createUploads from '../creators/upload-creator.js'; | ||||
import { deleteUpload } from '../deleters/upload-deleters.js'; | import { deleteUpload } from '../deleters/upload-deleters.js'; | ||||
import { | import { | ||||
fetchUpload, | fetchUpload, | ||||
fetchUploadChunk, | fetchUploadChunk, | ||||
getUploadSize, | getUploadSize, | ||||
} from '../fetchers/upload-fetchers.js'; | } from '../fetchers/upload-fetchers.js'; | ||||
import type { MulterRequest } from '../responders/handlers.js'; | import type { MulterRequest } from '../responders/handlers.js'; | ||||
import type { Viewer } from '../session/viewer.js'; | import type { Viewer } from '../session/viewer.js'; | ||||
import { validateInput } from '../utils/validation-utils.js'; | import { validateInput, validateOutput } from '../utils/validation-utils.js'; | ||||
const upload = multer(); | const upload = multer(); | ||||
const multerProcessor: Middleware<> = upload.array('multimedia'); | const multerProcessor: Middleware<> = upload.array('multimedia'); | ||||
type MultimediaUploadResult = { | type MultimediaUploadResult = { | ||||
results: UploadMultimediaResult[], | results: UploadMultimediaResult[], | ||||
}; | }; | ||||
const MultimediaUploadResultValidator = tShape<MultimediaUploadResult>({ | |||||
results: t.list(uploadMultimediaResultValidator), | |||||
}); | |||||
async function multimediaUploadResponder( | async function multimediaUploadResponder( | ||||
viewer: Viewer, | viewer: Viewer, | ||||
req: MulterRequest, | req: MulterRequest, | ||||
): Promise<MultimediaUploadResult> { | ): Promise<MultimediaUploadResult> { | ||||
const { files, body } = req; | const { files, body } = req; | ||||
if (!files || !body || typeof body !== 'object') { | if (!files || !body || typeof body !== 'object') { | ||||
throw new ServerError('invalid_parameters'); | throw new ServerError('invalid_parameters'); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 47 Lines • ▼ Show 20 Lines | files.map(({ buffer, size, originalname }) => | ||||
}), | }), | ||||
), | ), | ||||
); | ); | ||||
const uploadInfos = validationResults.filter(Boolean); | const uploadInfos = validationResults.filter(Boolean); | ||||
if (uploadInfos.length === 0) { | if (uploadInfos.length === 0) { | ||||
throw new ServerError('invalid_parameters'); | throw new ServerError('invalid_parameters'); | ||||
} | } | ||||
const results = await createUploads(viewer, uploadInfos); | const results = await createUploads(viewer, uploadInfos); | ||||
return { results }; | return validateOutput( | ||||
viewer.platformDetails, | |||||
MultimediaUploadResultValidator, | |||||
{ | |||||
results, | |||||
}, | |||||
); | |||||
} | } | ||||
const uploadMediaMetadataInputValidator = tShape({ | const uploadMediaMetadataInputValidator = tShape<UploadMediaMetadataRequest>({ | ||||
filename: t.String, | filename: t.String, | ||||
width: t.Number, | width: t.Number, | ||||
height: t.Number, | height: t.Number, | ||||
blobHolder: t.String, | blobHolder: t.String, | ||||
encryptionKey: t.String, | encryptionKey: t.String, | ||||
mimeType: t.String, | mimeType: t.String, | ||||
loop: t.maybe(t.Boolean), | loop: t.maybe(t.Boolean), | ||||
thumbHash: t.maybe(t.String), | thumbHash: t.maybe(t.String), | ||||
}); | }); | ||||
async function uploadMediaMetadataResponder( | async function uploadMediaMetadataResponder( | ||||
viewer: Viewer, | viewer: Viewer, | ||||
input: any, | input: mixed, | ||||
): Promise<UploadMultimediaResult> { | ): Promise<UploadMultimediaResult> { | ||||
const request: UploadMediaMetadataRequest = input; | const request = await validateInput( | ||||
await validateInput(viewer, uploadMediaMetadataInputValidator, input); | viewer, | ||||
uploadMediaMetadataInputValidator, | |||||
input, | |||||
); | |||||
const mediaType = getMediaType(request.mimeType); | const mediaType = getMediaType(request.mimeType); | ||||
if (!mediaType) { | if (!mediaType) { | ||||
throw new ServerError('invalid_parameters'); | throw new ServerError('invalid_parameters'); | ||||
} | } | ||||
const { filename, blobHolder, encryptionKey, mimeType, width, height, loop } = | const { filename, blobHolder, encryptionKey, mimeType, width, height, loop } = | ||||
request; | request; | ||||
const uploadInfo: UploadInput = { | const uploadInfo: UploadInput = { | ||||
name: filename, | name: filename, | ||||
mime: mimeType, | mime: mimeType, | ||||
mediaType, | mediaType, | ||||
content: { storage: 'blob_service', blobHolder }, | content: { storage: 'blob_service', blobHolder }, | ||||
encryptionKey, | encryptionKey, | ||||
dimensions: { width, height }, | dimensions: { width, height }, | ||||
loop: loop ?? false, | loop: loop ?? false, | ||||
thumbHash: request.thumbHash, | thumbHash: request.thumbHash, | ||||
}; | }; | ||||
const [result] = await createUploads(viewer, [uploadInfo]); | const [result] = await createUploads(viewer, [uploadInfo]); | ||||
return result; | return validateOutput( | ||||
viewer.platformDetails, | |||||
uploadMultimediaResultValidator, | |||||
result, | |||||
); | |||||
} | } | ||||
async function uploadDownloadResponder( | async function uploadDownloadResponder( | ||||
viewer: Viewer, | viewer: Viewer, | ||||
req: $Request, | req: $Request, | ||||
res: $Response, | res: $Response, | ||||
): Promise<void> { | ): Promise<void> { | ||||
const { uploadID, secret } = req.params; | const { uploadID, secret } = req.params; | ||||
▲ Show 20 Lines • Show All 52 Lines • ▼ Show 20 Lines | if (!req.headers.range) { | ||||
res.writeHead(206, respHeaders); | res.writeHead(206, respHeaders); | ||||
const stream = new Readable(); | const stream = new Readable(); | ||||
stream.push(content); | stream.push(content); | ||||
stream.push(null); | stream.push(null); | ||||
stream.pipe(res); | stream.pipe(res); | ||||
} | } | ||||
} | } | ||||
const uploadDeletionRequestInputValidator: TInterface<UploadDeletionRequest> = | |||||
tShape<UploadDeletionRequest>({ | |||||
id: tID, | |||||
}); | |||||
async function uploadDeletionResponder( | async function uploadDeletionResponder( | ||||
viewer: Viewer, | viewer: Viewer, | ||||
request: UploadDeletionRequest, | input: mixed, | ||||
): Promise<void> { | ): Promise<void> { | ||||
const { id } = request; | const { id } = await validateInput( | ||||
viewer, | |||||
uploadDeletionRequestInputValidator, | |||||
input, | |||||
); | |||||
await deleteUpload(viewer, id); | await deleteUpload(viewer, id); | ||||
} | } | ||||
export { | export { | ||||
multerProcessor, | multerProcessor, | ||||
multimediaUploadResponder, | multimediaUploadResponder, | ||||
uploadDownloadResponder, | uploadDownloadResponder, | ||||
uploadDeletionResponder, | uploadDeletionResponder, | ||||
uploadMediaMetadataResponder, | uploadMediaMetadataResponder, | ||||
}; | }; |