import path from 'path' import sharp from 'sharp-lite' import fs from 'fs/promises' import { HttpError } from '../error.mjs' import * as security from './security.mjs' import * as formidable from './formidable.mjs' export default class MediaRoutes { constructor(opts = {}) { Object.assign(this, { security: opts.security || security, formidable: opts.formidable || formidable, sharp: opts.sharp || sharp, fs: opts.fs || fs, }) this.siteCache = new Map() this.collator = new Intl.Collator('is-IS', { numeric: false, sensitivity: 'accent' }) } init() { return fs.readdir('./public').then(folders => { return Promise.all(folders.map(folder => { return fs.readdir('./public/' + folder) .then(files => { return Promise.all(files.map(file => { return fs.stat(`./public/${folder}/${file}`) .then(function(stat) { return { name: file, size: stat.size } }) })) }) .then(files => { this.siteCache.set(folder, files) }) .catch(function() {}) })) }) } filesCacheGet(site) { let files = this.siteCache.get(site) || [] return files.sort((a, b) => { return this.collator.compare(a.name, b.name) }) } filesCacheAdd(site, filename, size) { let arr = this.siteCache.get(site) if (!arr) { this.siteCache.set(site, arr = []) } arr.push({ name: filename, size: size }) } async listFiles(ctx) { let site = await this.security.verifyToken(ctx) ctx.body = this.filesCacheGet(site) } async listPublicFiles(ctx) { this.security.throwIfNotPublic(ctx.params.site) ctx.body = this.filesCacheGet(ctx.params.site) } async upload(ctx) { let site = await this.security.verifyToken(ctx) ctx.state.site = site let result = await this.formidable.uploadFile(ctx, ctx.state.site) ctx.log.info(`Uploaded ${result.filename}`) let stat = await this.fs.stat(`./public/${ctx.state.site}/${result.filename}`) this.filesCacheAdd(ctx.state.site, result.filename, stat.size) ctx.body = { filename: result.filename, path: `/${ctx.state.site}/${result.filename}` } } async resize(ctx) { await this.upload(ctx) this.security.verifyBody(ctx) let out = { original: ctx.body, } let keys = Object.keys(ctx.req.body) await Promise.all(keys.map(key => { return Promise.resolve() .then(async () => { let item = ctx.req.body[key] let sharp = this.sharp(`./public/${ctx.state.site}/${ctx.body.filename}`) .rotate() if (item.resize) { sharp = sharp.resize(item.resize) } sharp = sharp[item.format](item[item.format]) let target = ctx.body.filename if (path.extname(target).length > 0) { target = target.slice(0, -path.extname(target).length) } target += `_${key}.${item.format.replace('jpeg', 'jpg')}` if (item.out === 'base64') { let buffer = await sharp.toBuffer() out[key] = { base64: `data:image/${item.format};base64,` + buffer.toString('base64'), } return } await sharp.toFile(`./public/${ctx.state.site}/${target}`) let stat = await this.fs.stat(`./public/${ctx.state.site}/${target}`) this.filesCacheAdd(ctx.state.site, target, stat.size) out[key] = { filename: target, path: `/${ctx.state.site}/${target}`, } }).then( function() {}, function(err) { throw new HttpError(`Error processing ${key}: ${err.message}`, 422) } ) })) ctx.body = out } }