2024-10-28 04:56:11 -04:00
|
|
|
import express from 'express'
|
2024-10-28 15:47:14 -04:00
|
|
|
import immich from './immich'
|
2024-10-29 05:29:14 -04:00
|
|
|
import render from './render'
|
2024-10-28 04:56:11 -04:00
|
|
|
import dayjs from 'dayjs'
|
2024-10-28 15:47:14 -04:00
|
|
|
import { AssetType, ImageSize } from './types'
|
2024-10-29 05:29:14 -04:00
|
|
|
import { Request } from 'express-serve-static-core'
|
2024-10-28 15:47:14 -04:00
|
|
|
|
|
|
|
require('dotenv').config()
|
2024-10-28 04:08:16 -04:00
|
|
|
|
|
|
|
const app = express()
|
2024-10-30 10:43:07 -04:00
|
|
|
// Add the EJS view engine, to render the gallery page
|
2024-10-28 15:47:14 -04:00
|
|
|
app.set('view engine', 'ejs')
|
2024-10-30 10:43:07 -04:00
|
|
|
// Serve static assets from the /public folder
|
2024-10-28 15:47:14 -04:00
|
|
|
app.use(express.static('public'))
|
2024-10-28 04:08:16 -04:00
|
|
|
|
2024-10-30 10:40:42 -04:00
|
|
|
// An incoming request for a shared link
|
2024-10-28 04:08:16 -04:00
|
|
|
app.get('/share/:key', async (req, res) => {
|
2024-10-29 11:15:34 -04:00
|
|
|
res.set('Cache-Control', 'public, max-age=' + process.env.CACHE_AGE)
|
2024-10-29 10:07:54 -04:00
|
|
|
if (!immich.isKey(req.params.key)) {
|
2024-10-30 09:28:29 -04:00
|
|
|
log('Invalid share key ' + req.params.key)
|
2024-10-28 04:08:16 -04:00
|
|
|
res.status(404).send()
|
|
|
|
} else {
|
2024-10-29 10:07:54 -04:00
|
|
|
const sharedLink = await immich.getShareByKey(req.params.key)
|
2024-10-30 09:28:29 -04:00
|
|
|
if (!sharedLink) {
|
|
|
|
log('Unknown share key ' + req.params.key)
|
|
|
|
res.status(404).send()
|
|
|
|
} else if (!sharedLink.assets.length) {
|
|
|
|
log('No assets for key ' + req.params.key)
|
2024-10-28 15:47:14 -04:00
|
|
|
res.status(404).send()
|
2024-10-29 10:07:54 -04:00
|
|
|
} else if (sharedLink.assets.length === 1) {
|
2024-10-28 04:08:16 -04:00
|
|
|
// This is an individual item (not a gallery)
|
2024-10-30 09:38:08 -04:00
|
|
|
log('Serving link ' + req.params.key)
|
2024-10-29 10:07:54 -04:00
|
|
|
const asset = sharedLink.assets[0]
|
2024-10-29 05:29:14 -04:00
|
|
|
if (asset.type === AssetType.image) {
|
2024-10-29 06:48:16 -04:00
|
|
|
// For photos, output the image directly
|
2024-10-29 10:07:54 -04:00
|
|
|
await render.assetBuffer(res, sharedLink.assets[0], getSize(req))
|
2024-10-29 05:29:14 -04:00
|
|
|
} else if (asset.type === AssetType.video) {
|
2024-10-29 06:48:16 -04:00
|
|
|
// For videos, show the video as a web player
|
2024-10-29 10:07:54 -04:00
|
|
|
await render.gallery(res, sharedLink, 1)
|
2024-10-29 05:29:14 -04:00
|
|
|
}
|
2024-10-28 04:08:16 -04:00
|
|
|
} else {
|
2024-10-28 15:47:14 -04:00
|
|
|
// Multiple images - render as a gallery
|
2024-10-30 09:38:08 -04:00
|
|
|
log('Serving link ' + req.params.key)
|
2024-10-29 10:07:54 -04:00
|
|
|
await render.gallery(res, sharedLink)
|
2024-10-28 04:08:16 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2024-10-30 10:40:42 -04:00
|
|
|
// Output the buffer data for a photo or video
|
2024-10-29 10:07:54 -04:00
|
|
|
app.get('/:type(photo|video)/:key/:id', async (req, res) => {
|
2024-10-29 11:15:34 -04:00
|
|
|
res.set('Cache-Control', 'public, max-age=' + process.env.CACHE_AGE)
|
2024-10-29 10:07:54 -04:00
|
|
|
// Check for valid key and ID
|
|
|
|
if (immich.isKey(req.params.key) && immich.isId(req.params.id)) {
|
|
|
|
// Check if the key is a valid share link
|
|
|
|
const sharedLink = await immich.getShareByKey(req.params.key)
|
|
|
|
if (sharedLink?.assets.length) {
|
|
|
|
// Check that the requested asset exists in this share
|
|
|
|
const asset = sharedLink.assets.find(x => x.id === req.params.id)
|
|
|
|
if (asset) {
|
|
|
|
asset.type = req.params.type === 'video' ? AssetType.video : AssetType.image
|
|
|
|
render.assetBuffer(res, asset, getSize(req)).then()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2024-10-29 05:29:14 -04:00
|
|
|
}
|
2024-10-30 09:28:29 -04:00
|
|
|
log('No asset found for ' + req.path)
|
2024-10-29 10:07:54 -04:00
|
|
|
res.status(404).send()
|
2024-10-29 06:48:16 -04:00
|
|
|
})
|
|
|
|
|
2024-10-31 08:21:57 -04:00
|
|
|
// Healthcheck
|
|
|
|
app.get('/healthcheck', (_req, res) => {
|
|
|
|
res.send('ok')
|
|
|
|
})
|
|
|
|
|
2024-10-30 10:40:42 -04:00
|
|
|
// Send a 404 for all other routes
|
2024-10-30 09:28:29 -04:00
|
|
|
app.get('*', (req, res) => {
|
|
|
|
log('Invalid route ' + req.path)
|
2024-10-29 06:48:16 -04:00
|
|
|
res.status(404).send()
|
2024-10-28 15:47:14 -04:00
|
|
|
})
|
|
|
|
|
2024-10-30 10:40:42 -04:00
|
|
|
/**
|
|
|
|
* Sanitise the data for an incoming query string `size` parameter
|
|
|
|
* e.g. https://example.com/share/abc...xyz?size=thumbnail
|
|
|
|
*/
|
|
|
|
const getSize = (req: Request) => {
|
|
|
|
return req?.query?.size === 'thumbnail' ? ImageSize.thumbnail : ImageSize.original
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Output a console.log message with timestamp
|
|
|
|
*/
|
|
|
|
export const log = (message: string) => console.log(dayjs().format() + ' ' + message)
|
|
|
|
|
2024-11-01 03:55:54 -04:00
|
|
|
// Handle process termination requests (e.g. Ctrl+C)
|
|
|
|
process.on('SIGTERM', () => { process.exit(0) })
|
|
|
|
process.on('SIGINT', () => { process.exit(0) })
|
|
|
|
|
2024-10-28 04:08:16 -04:00
|
|
|
app.listen(3000, () => {
|
|
|
|
console.log(dayjs().format() + ' Server started')
|
|
|
|
})
|