diff --git a/keyserver/src/fetchers/upload-fetchers.js b/keyserver/src/fetchers/upload-fetchers.js index 82b52cc42..e589d64c0 100644 --- a/keyserver/src/fetchers/upload-fetchers.js +++ b/keyserver/src/fetchers/upload-fetchers.js @@ -1,122 +1,193 @@ // @flow +import _keyBy from 'lodash/fp/keyBy'; + import type { Media } from 'lib/types/media-types'; +import type { MediaMessageServerDBContent } from 'lib/types/messages/media.js'; import { ServerError } from 'lib/utils/errors'; import { dbQuery, SQL } from '../database/database'; import type { Viewer } from '../session/viewer'; import { getAndAssertCommAppURLFacts } from '../utils/urls'; type UploadInfo = { content: Buffer, mime: string, }; async function fetchUpload( viewer: Viewer, id: string, secret: string, ): Promise { const query = SQL` SELECT content, mime FROM uploads WHERE id = ${id} AND secret = ${secret} `; const [result] = await dbQuery(query); if (result.length === 0) { throw new ServerError('invalid_parameters'); } const [row] = result; const { content, mime } = row; return { content, mime }; } async function fetchUploadChunk( id: string, secret: string, pos: number, len: number, ): Promise { // We use pos + 1 because SQL is 1-indexed whereas js is 0-indexed const query = SQL` SELECT SUBSTRING(content, ${pos + 1}, ${len}) AS content, mime FROM uploads WHERE id = ${id} AND secret = ${secret} `; const [result] = await dbQuery(query); if (result.length === 0) { throw new ServerError('invalid_parameters'); } const [row] = result; const { content, mime } = row; return { content, mime, }; } // Returns total size in bytes. async function getUploadSize(id: string, secret: string): Promise { const query = SQL` SELECT LENGTH(content) AS length FROM uploads WHERE id = ${id} AND secret = ${secret} `; const [result] = await dbQuery(query); if (result.length === 0) { throw new ServerError('invalid_parameters'); } const [row] = result; const { length } = row; return length; } function getUploadURL(id: string, secret: string): string { const { baseDomain, basePath } = getAndAssertCommAppURLFacts(); return `${baseDomain}${basePath}upload/${id}/${secret}`; } function mediaFromRow(row: Object): Media { const uploadExtra = JSON.parse(row.uploadExtra); const { width, height, loop } = uploadExtra; const { uploadType: type, uploadSecret: secret } = row; const id = row.uploadID.toString(); const dimensions = { width, height }; const uri = getUploadURL(id, secret); if (type === 'photo') { return { id, type: 'photo', uri, dimensions }; } else if (loop) { // $FlowFixMe add thumbnailID, thumbnailURI once they're in DB return { id, type: 'video', uri, dimensions, loop }; } else { // $FlowFixMe add thumbnailID, thumbnailURI once they're in DB return { id, type: 'video', uri, dimensions }; } } async function fetchMedia( viewer: Viewer, mediaIDs: $ReadOnlyArray, ): Promise<$ReadOnlyArray> { const query = SQL` SELECT id AS uploadID, secret AS uploadSecret, type AS uploadType, extra AS uploadExtra FROM uploads WHERE id IN (${mediaIDs}) AND uploader = ${viewer.id} AND container IS NULL `; const [result] = await dbQuery(query); return result.map(mediaFromRow); } +async function fetchMediaFromMediaMessageContent( + viewer: Viewer, + mediaMessageContents: $ReadOnlyArray, +): Promise<$ReadOnlyArray> { + const uploadIDs = []; + for (const mediaContent of mediaMessageContents) { + uploadIDs.push(mediaContent.uploadID); + if (mediaContent.type === 'video') { + uploadIDs.push(mediaContent.thumbnailUploadID); + } + } + + const query = SQL` + SELECT id AS uploadID, secret AS uploadSecret, + type AS uploadType, extra AS uploadExtra + FROM uploads + WHERE id IN (${uploadIDs}) AND uploader = ${viewer.id} AND container IS NULL + `; + + const [uploads] = await dbQuery(query); + const uploadMap = _keyBy('uploadID')(uploads); + + const media: Media[] = []; + for (const mediaMessageContent of mediaMessageContents) { + const primaryUploadID = mediaMessageContent.uploadID; + const primaryUpload = uploadMap[primaryUploadID]; + + const primaryUploadSecret = primaryUpload.uploadSecret; + const primaryUploadURI = getUploadURL(primaryUploadID, primaryUploadSecret); + + const uploadExtra = JSON.parse(primaryUpload.uploadExtra); + const { width, height, loop } = uploadExtra; + const dimensions = { width, height }; + + if (mediaMessageContent.type === 'photo') { + media.push({ + type: 'photo', + id: primaryUploadID, + uri: primaryUploadURI, + dimensions, + }); + continue; + } + + const thumbnailUploadID = mediaMessageContent.thumbnailUploadID; + const thumbnailUpload = uploadMap[thumbnailUploadID]; + + const thumbnailUploadSecret = thumbnailUpload.uploadSecret; + const thumbnailUploadURI = getUploadURL( + thumbnailUploadID, + thumbnailUploadSecret, + ); + + const video = { + type: 'video', + id: primaryUploadID, + uri: primaryUploadURI, + dimensions, + thumbnailID: thumbnailUploadID, + thumbnailURI: thumbnailUploadURI, + }; + media.push(loop ? { ...video, loop } : video); + } + + return media; +} + export { fetchUpload, fetchUploadChunk, getUploadSize, getUploadURL, mediaFromRow, fetchMedia, + fetchMediaFromMediaMessageContent, };