@wepublish/api-media-karma
Version:
We.publish media service
130 lines (107 loc) • 3.78 kB
text/typescript
import {
MediaAdapter,
ImageWithFocalPoint,
UploadImage,
ImageTransformation,
ArrayBufferUpload
} from '@wepublish/api'
import {FileUpload} from 'graphql-upload'
import fetch from 'node-fetch'
import FormData from 'form-data'
import {URL} from 'url'
export class MediaServerError extends Error {
constructor(message: string) {
super(`Received error from media server. Message: ${message}`)
}
}
export class KarmaMediaAdapter implements MediaAdapter {
readonly url: URL
readonly token: string
readonly internalURL: URL
constructor(url: URL, token: string, internalURL: URL = url) {
this.url = url
this.token = token
this.internalURL = internalURL
}
async _uploadImage(form: FormData): Promise<UploadImage> {
// The form-data module reports a known length for the stream returned by createReadStream,
// which is wrong, override it and always set it to false.
// Related issue: https://github.com/form-data/form-data/issues/394
form.hasKnownLength = () => false
const response = await fetch(this.internalURL, {
method: 'POST',
headers: {authorization: `Bearer ${this.token}`},
body: form
})
const json = await response.json()
if (response.status !== 200) {
throw new MediaServerError(response.statusText)
}
const {id, filename, fileSize, extension, mimeType, format, width, height} = json
return {
id,
filename,
fileSize,
extension,
mimeType,
format,
width,
height
}
}
async uploadImage(fileUpload: Promise<FileUpload>): Promise<UploadImage> {
const form = new FormData()
const {filename: inputFilename, mimetype, createReadStream}: FileUpload = await fileUpload
form.append('file', createReadStream(), {filename: inputFilename, contentType: mimetype})
return this._uploadImage(form)
}
async uploadImageFromArrayBuffer(
arrayBufferUpload: Promise<ArrayBufferUpload>
): Promise<UploadImage> {
const form = new FormData()
const {
filename: inputFilename,
mimetype,
arrayBuffer
}: ArrayBufferUpload = await arrayBufferUpload
form.append('file', arrayBuffer, {filename: inputFilename, contentType: mimetype})
return this._uploadImage(form)
}
async deleteImage(id: string): Promise<boolean> {
const response = await fetch(`${this.internalURL}${id}`, {
method: 'DELETE',
headers: {authorization: `Bearer ${this.token}`}
})
if (response.status !== 204) {
throw new MediaServerError(response.statusText)
}
return true
}
async getImageURL(
{id, filename, extension, focalPoint}: ImageWithFocalPoint,
transformation?: ImageTransformation
): Promise<string> {
filename = filename || 'untitled'
if (transformation) {
const {width, height, rotation, output, quality} = transformation
const fullFilename = encodeURIComponent(`${filename}${output ? `.${output}` : extension}`)
const transformations = []
if (width) transformations.push(`w_${width}`)
if (height) transformations.push(`h_${height}`)
if (rotation) transformations.push(`r_${rotation}`)
if (output) transformations.push(`o_${output}`)
if (quality) transformations.push(`q_${quality}`)
if (focalPoint && (width || height)) {
transformations.push(`f_${focalPoint.x?.toFixed(3)}:${focalPoint.y?.toFixed(3)}`)
}
if (transformations.length > 0) {
return `${this.url}${id}/t/${transformations.join(',')}/${fullFilename}`
} else {
return `${this.url}${id}/${fullFilename}`
}
} else {
const fullFilename = encodeURIComponent(`${filename}${extension}`)
return `${this.url}${id}/${fullFilename}`
}
}
}