Allow thumbnails to be served in webp

main
Ashelyn Dawn 3 years ago
parent 4cfc17355f
commit c5efb315e0

3
.gitignore vendored

@ -1,4 +1,3 @@
.next/
node_modules/
.env
cache/
.env

@ -1,67 +1,29 @@
const router = require('express-promise-router')()
const db = require('../db')
const ensureAdmin = require('./middleware/ensureAdmin')
const fs = require('fs');
const path = require('path');
const cacheRoot = path.join(__dirname, '../cache/images')
fs.promises.mkdir(cacheRoot, {recursive: true})
.catch(() => console.error("Could not create image cache dir: " + cacheRoot));
const memCache = {};
async function writeImageFile(uuid, size, data) {
if(size !== 'thumb' && size !== 'large')
throw new Error(`Unknown image size ${size}`);
await fs.promises.writeFile(path.join(cacheRoot, `${uuid}-${size}`), data);
}
async function getImage(uuid, size) {
// If we've already stored a mime type, that means we've written the file to disk
if(memCache[`${uuid}-${size}`]?.mime_type) {
return {
file: fs.createReadStream(path.join(cacheRoot, `${uuid}-${size}`)),
mime_type: memCache[`${uuid}-${size}`].mime_type
}
}
// If we've stored a promise, that means we're currently retrieving it from the db
if(memCache[`${uuid}-${size}`]?.dbPromise) {
return await memCache[`${uuid}-${size}`]?.dbPromise;
}
// Otherwise, retrieve it from the DB, and then write it to disk
const record = memCache[`${uuid}-${size}`] = {};
record.dbPromise = db.item.getImage(uuid, size);
const image = await record.dbPromise;
await writeImageFile(uuid, size, image.file);
record.mime_type = image.mime_type;
delete record.dbPromise;
return image;
}
const sharp = require('sharp')
router.get('/:uuid/:size', async (req, res) => {
let extension
if(req.params.size.includes('.')) {
extension = req.params.size.split('.').slice(1).join('.');
req.params.size = req.params.size.split('.')[0];
}
const image = await getImage(req.params.uuid, req.params.size)
const image = await db.item.getImage(req.params.uuid, req.params.size)
const cacheSeconds = 60 * 60 * 24;
res.set('Cache-Control', cacheSeconds);
res.set('Content-Type', image.mime_type)
if(Buffer.isBuffer(image.file))
res.end(image.file)
else if (typeof image.file.pipe === 'function')
image.file.pipe(res)
else
throw new Error("Unable to send file to user");
if(extension === "webp") {
const webp = await sharp(image.file).webp().toBuffer();
res.set('Content-Type', "image/webp")
res.end(webp)
return;
}
res.set('Content-Type', image.mime_type)
res.end(image.file)
})
router.post('/:uuid/featured', ensureAdmin, async (req, res) => {

@ -23,7 +23,10 @@ export default function Card({item}) {
<h3><Link href={`/store/item/${item.urlslug}`}><a>{item.name}</a></Link></h3>
{featuredImage && (
<div className={styles['card-image']}>
<img src={`/api/images/${featuredImage.uuid}/thumb.png`} />
<picture>
<source srcset={`/api/images/${featuredImage.uuid}/thumb.webp`} type="image/webp"/>
<img src={`/api/images/${featuredImage.uuid}/thumb.png`} />
</picture>
</div>
)}
<div className={styles['card-text']}>{item.description}</div>

@ -39,16 +39,22 @@ export default function Item({item}){
{item.images && item.images.length > 0 && (
<div className={styles.imageContainer}>
<div className={styles.card}>
<img src={`/api/images/${item.images[selectedIndex].uuid}/thumb.png`} />
<picture>
<source srcset={`/api/images/${item.images[selectedIndex].uuid}/thumb.webp`} type="image/webp"/>
<img src={`/api/images/${item.images[selectedIndex].uuid}/thumb.png`} />
</picture>
</div>
{item.images && item.images.length > 1 &&
<div className={styles.imageSelector}>
{item.images.map((image, index) => (
<img key={image.uuid}
onClick={()=>setSelected(index)}
className={index === selectedIndex ? styles.selectedImage : undefined}
src={`/api/images/${image.uuid}/thumb.png`}
/>
<picture>
<source srcset={`/api/images/${image.uuid}/thumb.webp`} type="image/webp"/>
<img key={image.uuid}
onClick={()=>setSelected(index)}
className={index === selectedIndex ? styles.selectedImage : undefined}
src={`/api/images/${image.uuid}/thumb.png`}
/>
</picture>
))}
</div>
}

Loading…
Cancel
Save