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:
parent
0afc820f7b
commit
71feba9ce8
10 changed files with 87 additions and 54 deletions
|
@ -1,3 +0,0 @@
|
|||
IMMICH_URL=http://localhost:2283
|
||||
PORT=3000
|
||||
CACHE_AGE=2592000
|
19
README.md
19
README.md
|
@ -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.
|
||||
|
||||
2. Create a `.env` file to configure the app:
|
||||
|
||||
```
|
||||
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.
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
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`.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
### lightGallery
|
||||
|
||||
The gallery is created using [lightGallery](https://github.com/sachinchoolur/lightGallery).
|
||||
You can find all of lightGallery's settings here:
|
||||
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
|
||||
{
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
{
|
||||
"ipp": {
|
||||
"responseHeaders": {
|
||||
"Cache-Control": "public, max-age=2592000"
|
||||
},
|
||||
"singleImageGallery": false,
|
||||
"singleItemAutoOpen": true
|
||||
},
|
||||
"lightGallery": {
|
||||
"controls": true,
|
||||
"download": true,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "immich-public-proxy",
|
||||
"version": "1.3.6",
|
||||
"version": "1.3.7",
|
||||
"scripts": {
|
||||
"dev": "ts-node src/index.ts",
|
||||
"build": "npx tsc",
|
||||
|
|
54
app/src/functions.ts
Normal file
54
app/src/functions.ts
Normal 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)
|
||||
})
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import { Asset, AssetType, ImageSize, IncomingShareRequest, SharedLink, SharedLinkResult } from './types'
|
||||
import dayjs from 'dayjs'
|
||||
import { log } from './index'
|
||||
import { getConfigOption, log } from './functions'
|
||||
import render from './render'
|
||||
import { Response } from 'express-serve-static-core'
|
||||
import { encrypt } from './encrypt'
|
||||
|
@ -70,12 +70,13 @@ class Immich {
|
|||
// This is an individual item (not a gallery)
|
||||
log('Serving link ' + request.key)
|
||||
const asset = link.assets[0]
|
||||
if (asset.type === AssetType.image) {
|
||||
// For photos, output the image directly
|
||||
if (asset.type === AssetType.image && !getConfigOption('ipp.singleImageGallery')) {
|
||||
// For photos, output the image directly unless configured to show a gallery
|
||||
await render.assetBuffer(res, link.assets[0], request.size)
|
||||
} else if (asset.type === AssetType.video) {
|
||||
// For videos, show the video as a web player
|
||||
await render.gallery(res, link, 1)
|
||||
} else {
|
||||
// Show a gallery page
|
||||
const openItem = getConfigOption('ipp.singleItemAutoOpen', true) ? 1 : 0
|
||||
await render.gallery(res, link, openItem)
|
||||
}
|
||||
} else {
|
||||
// Multiple images - render as a gallery
|
||||
|
|
|
@ -2,19 +2,19 @@ import express from 'express'
|
|||
import immich from './immich'
|
||||
import render from './render'
|
||||
import dayjs from 'dayjs'
|
||||
import { AssetType, ImageSize } from './types'
|
||||
import { Request } from 'express-serve-static-core'
|
||||
import { AssetType } from './types'
|
||||
import { decrypt } from './encrypt'
|
||||
import { log, getSize, toString, addResponseHeaders } from './functions'
|
||||
|
||||
require('dotenv').config()
|
||||
|
||||
const app = express()
|
||||
// Add the EJS view engine, to render the gallery page
|
||||
app.set('view engine', 'ejs')
|
||||
// Serve static assets from the /public folder
|
||||
app.use(express.static('public'))
|
||||
// For parsing the password unlock form
|
||||
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
|
||||
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
|
||||
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
|
||||
if (immich.isKey(req.params.key) && immich.isId(req.params.id)) {
|
||||
let password
|
||||
|
@ -83,23 +83,6 @@ app.get('*', (req, res) => {
|
|||
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, () => {
|
||||
console.log(dayjs().format() + ' Server started')
|
||||
})
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
import immich from './immich'
|
||||
import { Response } from 'express-serve-static-core'
|
||||
import { Asset, AssetType, ImageSize, SharedLink } from './types'
|
||||
import { getConfigOption } from './functions'
|
||||
|
||||
class Render {
|
||||
lgConfig = {}
|
||||
lgConfig
|
||||
|
||||
constructor () {
|
||||
try {
|
||||
// Import user-provided lightGallery config (if exists)
|
||||
const config = require('../config.json')
|
||||
if (typeof config === 'object' && config.lightGallery) this.lgConfig = config.lightGallery
|
||||
} catch (e) { }
|
||||
this.lgConfig = getConfigOption('lightGallery', {})
|
||||
}
|
||||
|
||||
async assetBuffer (res: Response, asset: Asset, size?: ImageSize) {
|
||||
|
@ -61,7 +58,7 @@ class Render {
|
|||
items,
|
||||
openItem,
|
||||
title: this.title(share),
|
||||
lgConfig: this.lgConfig
|
||||
lgConfig: getConfigOption('lightGallery', {})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,9 @@ services:
|
|||
container_name: immich-public-proxy
|
||||
restart: always
|
||||
ports:
|
||||
- ${PORT}:3000
|
||||
env_file: .env
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- IMMICH_URL=http://localhost:2283
|
||||
healthcheck:
|
||||
test: wget -q http://localhost:3000/healthcheck || exit 1
|
||||
start_period: 10s
|
||||
|
|
Loading…
Reference in a new issue