2022-01-05 14:47:51 +00:00
|
|
|
import path from 'path'
|
|
|
|
import sharp from 'sharp-lite'
|
|
|
|
import fs from 'fs/promises'
|
|
|
|
import { HttpError } from '../error.mjs'
|
2021-10-11 00:21:57 +00:00
|
|
|
import * as security from './security.mjs'
|
2021-10-11 00:56:22 +00:00
|
|
|
import * as formidable from './formidable.mjs'
|
2017-12-10 09:45:38 +00:00
|
|
|
|
2021-10-11 00:21:57 +00:00
|
|
|
export default class MediaRoutes {
|
|
|
|
constructor(opts = {}) {
|
|
|
|
Object.assign(this, {
|
|
|
|
security: opts.security || security,
|
2021-10-11 00:56:22 +00:00
|
|
|
formidable: opts.formidable || formidable,
|
2022-01-05 14:47:51 +00:00
|
|
|
sharp: opts.sharp || sharp,
|
|
|
|
fs: opts.fs || fs,
|
2021-10-11 00:21:57 +00:00
|
|
|
})
|
2022-01-05 14:47:51 +00:00
|
|
|
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) {
|
2022-01-06 09:01:10 +00:00
|
|
|
return { filename: file, size: stat.size }
|
2022-01-05 14:47:51 +00:00
|
|
|
})
|
|
|
|
}))
|
|
|
|
})
|
|
|
|
.then(files => {
|
|
|
|
this.siteCache.set(folder, files)
|
|
|
|
})
|
|
|
|
.catch(function() {})
|
|
|
|
}))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
filesCacheGet(site) {
|
|
|
|
let files = this.siteCache.get(site) || []
|
|
|
|
return files.sort((a, b) => {
|
2022-01-06 09:01:10 +00:00
|
|
|
return this.collator.compare(a.filename, b.filename)
|
2022-01-05 14:47:51 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
filesCacheAdd(site, filename, size) {
|
|
|
|
let arr = this.siteCache.get(site)
|
|
|
|
if (!arr) {
|
|
|
|
this.siteCache.set(site, arr = [])
|
|
|
|
}
|
2022-01-06 09:01:10 +00:00
|
|
|
let found = false
|
|
|
|
for (let file of arr) {
|
|
|
|
if (file.filename === filename) {
|
|
|
|
found = true
|
|
|
|
file.size = size
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!found) {
|
|
|
|
arr.push({ filename: filename, size: size })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
filesCacheRemove(site, filename) {
|
|
|
|
let arr = this.siteCache.get(site)
|
|
|
|
if (!arr) return
|
|
|
|
|
|
|
|
for (let i = 0; i < arr.length; i++) {
|
|
|
|
if (arr[i].filename === filename) {
|
|
|
|
arr.splice(i, 1)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
2022-01-05 14:47:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
2021-10-11 00:21:57 +00:00
|
|
|
}
|
|
|
|
|
2022-01-06 09:01:10 +00:00
|
|
|
async upload(ctx, noprefix = false) {
|
2021-10-11 00:21:57 +00:00
|
|
|
let site = await this.security.verifyToken(ctx)
|
2022-01-05 14:47:51 +00:00
|
|
|
ctx.state.site = site
|
2017-12-10 09:45:38 +00:00
|
|
|
|
2022-01-06 09:01:10 +00:00
|
|
|
let result = await this.formidable.uploadFile(ctx, ctx.state.site, noprefix)
|
2017-12-10 09:45:38 +00:00
|
|
|
|
2021-10-11 03:39:01 +00:00
|
|
|
ctx.log.info(`Uploaded ${result.filename}`)
|
|
|
|
|
2022-01-05 14:47:51 +00:00
|
|
|
let stat = await this.fs.stat(`./public/${ctx.state.site}/${result.filename}`)
|
|
|
|
this.filesCacheAdd(ctx.state.site, result.filename, stat.size)
|
|
|
|
|
2021-10-11 00:21:57 +00:00
|
|
|
ctx.body = {
|
|
|
|
filename: result.filename,
|
2022-01-05 14:47:51 +00:00
|
|
|
path: `/${ctx.state.site}/${result.filename}`
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-06 09:01:10 +00:00
|
|
|
uploadNoPrefix(ctx) {
|
|
|
|
return this.upload(ctx, true)
|
|
|
|
}
|
|
|
|
|
2022-01-05 14:47:51 +00:00
|
|
|
async resize(ctx) {
|
|
|
|
await this.upload(ctx)
|
|
|
|
|
|
|
|
this.security.verifyBody(ctx)
|
|
|
|
|
|
|
|
let keys = Object.keys(ctx.req.body)
|
|
|
|
|
2022-01-06 09:51:43 +00:00
|
|
|
let allowedOperations = [
|
|
|
|
'trim',
|
|
|
|
'flatten',
|
|
|
|
'resize',
|
|
|
|
'blur',
|
|
|
|
'extend',
|
|
|
|
]
|
|
|
|
|
2022-01-05 14:47:51 +00:00
|
|
|
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()
|
|
|
|
|
2022-01-06 09:51:43 +00:00
|
|
|
for (let operation of allowedOperations) {
|
|
|
|
if (item[operation] != null) {
|
|
|
|
sharp = sharp[operation](item[operation])
|
|
|
|
}
|
2022-01-05 14:47:51 +00:00
|
|
|
}
|
|
|
|
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()
|
2022-01-06 09:01:10 +00:00
|
|
|
ctx.body[key] = {
|
2022-01-05 14:47:51 +00:00
|
|
|
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)
|
|
|
|
|
2022-01-06 09:01:10 +00:00
|
|
|
ctx.body[key] = {
|
2022-01-05 14:47:51 +00:00
|
|
|
filename: target,
|
|
|
|
path: `/${ctx.state.site}/${target}`,
|
|
|
|
}
|
|
|
|
}).then(
|
|
|
|
function() {},
|
|
|
|
function(err) {
|
|
|
|
throw new HttpError(`Error processing ${key}: ${err.message}`, 422)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}))
|
2022-01-06 09:01:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async remove(ctx) {
|
|
|
|
let site = await this.security.verifyToken(ctx)
|
|
|
|
|
|
|
|
this.filesCacheRemove(site, ctx.params.filename)
|
|
|
|
|
|
|
|
await this.fs.unlink(`./public/${site}/${ctx.params.filename}`)
|
|
|
|
.catch(function(err) {
|
|
|
|
throw new HttpError(`Error removing ${site}/${ctx.params.filename}: ${err.message}`, 422)
|
|
|
|
})
|
2022-01-05 14:47:51 +00:00
|
|
|
|
2022-01-06 09:01:10 +00:00
|
|
|
ctx.status = 204
|
2017-12-10 09:45:38 +00:00
|
|
|
}
|
|
|
|
}
|