1
0
Fork 0
mirror of https://github.com/alangrainger/immich-public-proxy.git synced 2024-12-28 03:41:58 +00:00

Add #10 - Single item as a gallery

This commit is contained in:
Alan Grainger 2024-11-04 20:23:34 +01:00
parent 0afc820f7b
commit 71feba9ce8
10 changed files with 87 additions and 54 deletions

View file

@ -1,3 +0,0 @@
IMMICH_URL=http://localhost:2283
PORT=3000
CACHE_AGE=2592000

View file

@ -48,17 +48,7 @@ Here is an example setup for [securing Immich behind mTLS](./docs/securing-immic
1. Download the [docker-compose.yml](https://github.com/alangrainger/immich-public-proxy/blob/main/docker-compose.yml) file. 1. Download the [docker-compose.yml](https://github.com/alangrainger/immich-public-proxy/blob/main/docker-compose.yml) file.
2. Create a `.env` file to configure the app: 2. Update the value for `IMMICH_URL` in your docker-compose file to point to your local URL for Immich. This should not be a public URL.
```
IMMICH_URL=http://localhost:2283
PORT=3000
CACHE_AGE=2592000
```
- `IMMICH_URL` is the URL to access Immich in your local network. This is not your public URL.
- `PORT` is the external port you want for the docker container.
- `CACHE_AGE` this is setting the Cache-Control header, to tell the visitor's browser to cache the assets. Set to 0 to disable caching. By default this is 30 days.
3. Start the docker container: 3. Start the docker container:
@ -102,7 +92,7 @@ If the shared link has expired or any of the assets have been put in the Immich
## Additional configuration ## Additional configuration
The gallery is created using [lightGallery](https://github.com/sachinchoolur/lightGallery). You can adjust various settings to customise how your gallery displays. There are some additional configuration options you can change, for example the way the gallery is set up.
1. Make a copy of [config.json](https://github.com/alangrainger/immich-public-proxy/blob/main/app/config.json) in the same folder as your `docker-compose.yml`. 1. Make a copy of [config.json](https://github.com/alangrainger/immich-public-proxy/blob/main/app/config.json) in the same folder as your `docker-compose.yml`.
@ -115,10 +105,13 @@ The gallery is created using [lightGallery](https://github.com/sachinchoolur/lig
3. Restart your container and your custom configuration should be active. 3. Restart your container and your custom configuration should be active.
### lightGallery
The gallery is created using [lightGallery](https://github.com/sachinchoolur/lightGallery).
You can find all of lightGallery's settings here: You can find all of lightGallery's settings here:
https://www.lightgalleryjs.com/docs/settings/ https://www.lightgalleryjs.com/docs/settings/
For example, to disable the download button for images, you would change `download` to `false`: For example, to disable the download button for images, you would edit the `lightGallery` section and change `download` to `false`:
```json ```json
{ {

View file

@ -1,4 +1,11 @@
{ {
"ipp": {
"responseHeaders": {
"Cache-Control": "public, max-age=2592000"
},
"singleImageGallery": false,
"singleItemAutoOpen": true
},
"lightGallery": { "lightGallery": {
"controls": true, "controls": true,
"download": true, "download": true,

View file

@ -1,6 +1,6 @@
{ {
"name": "immich-public-proxy", "name": "immich-public-proxy",
"version": "1.3.6", "version": "1.3.7",
"scripts": { "scripts": {
"dev": "ts-node src/index.ts", "dev": "ts-node src/index.ts",
"build": "npx tsc", "build": "npx tsc",

54
app/src/functions.ts Normal file
View file

@ -0,0 +1,54 @@
import dayjs from 'dayjs'
import { Request, Response } from 'express-serve-static-core'
import { ImageSize } from './types'
let config = {}
try {
const configJson = require('../config.json')
if (typeof configJson === 'object') config = configJson
} catch (e) { }
/**
* Get a configuration option using dotted notation.
*
* @param path
* @param [defaultOption] - Specify a default option to return if no configuation value is found
*
* @example
* getConfigOption('ipp.singleImageGallery')
*/
export const getConfigOption = (path: string, defaultOption?: unknown) => {
const value = path.split('.').reduce((obj: { [key: string]: unknown }, key) => (obj || {})[key], config)
if (value === undefined) {
return defaultOption
} else {
return value
}
}
/**
* Output a console.log message with timestamp
*/
export const log = (message: string) => console.log(dayjs().format() + ' ' + message)
/**
* Sanitise the data for an incoming query string `size` parameter
* e.g. https://example.com/share/abc...xyz?size=thumbnail
*/
export function getSize (req: Request) {
return req.query?.size === 'thumbnail' ? ImageSize.thumbnail : ImageSize.original
}
export function toString (value: unknown) {
return typeof value === 'string' ? value : ''
}
/**
* Add response headers from config.json
*/
export function addResponseHeaders (res: Response) {
Object.entries(getConfigOption('ipp.responseHeaders', {}) as { [key: string]: string })
.forEach(([header, value]) => {
res.set(header, value)
})
}

View file

@ -1,6 +1,6 @@
import { Asset, AssetType, ImageSize, IncomingShareRequest, SharedLink, SharedLinkResult } from './types' import { Asset, AssetType, ImageSize, IncomingShareRequest, SharedLink, SharedLinkResult } from './types'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { log } from './index' import { getConfigOption, log } from './functions'
import render from './render' import render from './render'
import { Response } from 'express-serve-static-core' import { Response } from 'express-serve-static-core'
import { encrypt } from './encrypt' import { encrypt } from './encrypt'
@ -70,12 +70,13 @@ class Immich {
// This is an individual item (not a gallery) // This is an individual item (not a gallery)
log('Serving link ' + request.key) log('Serving link ' + request.key)
const asset = link.assets[0] const asset = link.assets[0]
if (asset.type === AssetType.image) { if (asset.type === AssetType.image && !getConfigOption('ipp.singleImageGallery')) {
// For photos, output the image directly // For photos, output the image directly unless configured to show a gallery
await render.assetBuffer(res, link.assets[0], request.size) await render.assetBuffer(res, link.assets[0], request.size)
} else if (asset.type === AssetType.video) { } else {
// For videos, show the video as a web player // Show a gallery page
await render.gallery(res, link, 1) const openItem = getConfigOption('ipp.singleItemAutoOpen', true) ? 1 : 0
await render.gallery(res, link, openItem)
} }
} else { } else {
// Multiple images - render as a gallery // Multiple images - render as a gallery

View file

@ -2,19 +2,19 @@ import express from 'express'
import immich from './immich' import immich from './immich'
import render from './render' import render from './render'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { AssetType, ImageSize } from './types' import { AssetType } from './types'
import { Request } from 'express-serve-static-core'
import { decrypt } from './encrypt' import { decrypt } from './encrypt'
import { log, getSize, toString, addResponseHeaders } from './functions'
require('dotenv').config() require('dotenv').config()
const app = express() const app = express()
// Add the EJS view engine, to render the gallery page // Add the EJS view engine, to render the gallery page
app.set('view engine', 'ejs') app.set('view engine', 'ejs')
// Serve static assets from the /public folder
app.use(express.static('public'))
// For parsing the password unlock form // For parsing the password unlock form
app.use(express.json()) app.use(express.json())
// Serve static assets from the /public folder
app.use(express.static('public', { setHeaders: addResponseHeaders }))
// An incoming request for a shared link // An incoming request for a shared link
app.get('/share/:key', async (req, res) => { app.get('/share/:key', async (req, res) => {
@ -34,7 +34,7 @@ app.post('/unlock', async (req, res) => {
// Output the buffer data for a photo or video // Output the buffer data for a photo or video
app.get('/:type(photo|video)/:key/:id', async (req, res) => { app.get('/:type(photo|video)/:key/:id', async (req, res) => {
res.set('Cache-Control', 'public, max-age=' + process.env.CACHE_AGE) addResponseHeaders(res)
// Check for valid key and ID // Check for valid key and ID
if (immich.isKey(req.params.key) && immich.isId(req.params.id)) { if (immich.isKey(req.params.key) && immich.isId(req.params.id)) {
let password let password
@ -83,23 +83,6 @@ app.get('*', (req, res) => {
res.status(404).send() res.status(404).send()
}) })
/**
* Output a console.log message with timestamp
*/
export const log = (message: string) => console.log(dayjs().format() + ' ' + message)
/**
* 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
}
const toString = (value: unknown) => {
return typeof value === 'string' ? value : ''
}
app.listen(3000, () => { app.listen(3000, () => {
console.log(dayjs().format() + ' Server started') console.log(dayjs().format() + ' Server started')
}) })

View file

@ -1,16 +1,13 @@
import immich from './immich' import immich from './immich'
import { Response } from 'express-serve-static-core' import { Response } from 'express-serve-static-core'
import { Asset, AssetType, ImageSize, SharedLink } from './types' import { Asset, AssetType, ImageSize, SharedLink } from './types'
import { getConfigOption } from './functions'
class Render { class Render {
lgConfig = {} lgConfig
constructor () { constructor () {
try { this.lgConfig = getConfigOption('lightGallery', {})
// Import user-provided lightGallery config (if exists)
const config = require('../config.json')
if (typeof config === 'object' && config.lightGallery) this.lgConfig = config.lightGallery
} catch (e) { }
} }
async assetBuffer (res: Response, asset: Asset, size?: ImageSize) { async assetBuffer (res: Response, asset: Asset, size?: ImageSize) {
@ -61,7 +58,7 @@ class Render {
items, items,
openItem, openItem,
title: this.title(share), title: this.title(share),
lgConfig: this.lgConfig lgConfig: getConfigOption('lightGallery', {})
}) })
} }

View file

@ -4,8 +4,9 @@ services:
container_name: immich-public-proxy container_name: immich-public-proxy
restart: always restart: always
ports: ports:
- ${PORT}:3000 - "3000:3000"
env_file: .env environment:
- IMMICH_URL=http://localhost:2283
healthcheck: healthcheck:
test: wget -q http://localhost:3000/healthcheck || exit 1 test: wget -q http://localhost:3000/healthcheck || exit 1
start_period: 10s start_period: 10s