Compare commits
17 Commits
ml-models-v1
...
v2.6.4
| Author | SHA1 | Date | |
|---|---|---|---|
| e2a698e892 | |||
| d1d71dba25 | |||
| bb81fa5f4b | |||
| 03e19893b3 | |||
| 279314e4b7 | |||
| 730912a3f4 | |||
| ff9fb64e75 | |||
| 9cbbe74051 | |||
| 2e1c71c1ab | |||
| f6ca713a6e | |||
| 197cd8e1e0 | |||
| 681b440381 | |||
| 3daeac9e53 | |||
| 7febba2d9c | |||
| 0a3a53763c | |||
| 85a60a2dc7 | |||
| e74e73a3a0 |
@@ -1,3 +1,3 @@
|
||||
{
|
||||
".": "2.6.1"
|
||||
".": "2.6.4"
|
||||
}
|
||||
|
||||
@@ -5,6 +5,30 @@ All notable changes to PicPeak will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [2.6.4](https://github.com/the-luap/picpeak/compare/v2.6.3...v2.6.4) (2026-04-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* sync backend package-lock.json for security deps ([bb81fa5](https://github.com/the-luap/picpeak/commit/bb81fa5f4b5f1bd927a02470ce80a13c4f53443f))
|
||||
* sync backend package-lock.json with security dep updates ([03e1989](https://github.com/the-luap/picpeak/commit/03e19893b3532a27aa59e7b834d53c6a2b52b7cd))
|
||||
|
||||
## [2.6.3](https://github.com/the-luap/picpeak/compare/v2.6.2...v2.6.3) (2026-04-07)
|
||||
|
||||
|
||||
### Documentation
|
||||
|
||||
* add External Media Library section to deployment guide ([#270](https://github.com/the-luap/picpeak/issues/270)) ([2e1c71c](https://github.com/the-luap/picpeak/commit/2e1c71c1ab073e488ac93e35337a2d3955dfef3d))
|
||||
* add External Media Library section to deployment guide ([#270](https://github.com/the-luap/picpeak/issues/270)) ([f6ca713](https://github.com/the-luap/picpeak/commit/f6ca713a6edc8ba371db790daba05ecb85ea4872))
|
||||
|
||||
## [2.6.2](https://github.com/the-luap/picpeak/compare/v2.6.1...v2.6.2) (2026-03-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **security:** invalidate tokens on password change, enforce session timeout, fix role update ([85a60a2](https://github.com/the-luap/picpeak/commit/85a60a2dc7526aa6b673e2a04e9fdfba7de7117f))
|
||||
* **security:** token invalidation on password change, session timeout enforcement ([0a3a537](https://github.com/the-luap/picpeak/commit/0a3a53763c9f3caef9fdceccf9fdbfdefe9bd8bf))
|
||||
|
||||
## [2.6.1](https://github.com/the-luap/picpeak/compare/v2.6.0...v2.6.1) (2026-03-11)
|
||||
|
||||
|
||||
|
||||
+90
-1
@@ -11,7 +11,7 @@ This guide covers multiple deployment options for PicPeak, from simple local set
|
||||
- [First Login](#-first-login)
|
||||
- [Release Channels](#-release-channels)
|
||||
- [Reverse Proxy Setup](#-reverse-proxy-setup)
|
||||
- [External Media Library](#external-media-library)
|
||||
- [External Media Library](#-external-media-library)
|
||||
- [Maintenance](#-maintenance)
|
||||
- [Troubleshooting](#-troubleshooting)
|
||||
|
||||
@@ -552,6 +552,95 @@ sudo certbot certonly --webroot -w /var/www/certbot -d your-domain.com
|
||||
# Or use your reverse proxy's built-in ACME support
|
||||
```
|
||||
|
||||
## 📂 External Media Library
|
||||
|
||||
The External Media Library allows events to reference photos stored directly on your host filesystem instead of uploading them through the admin UI. This is useful for photographers who already have organized photo libraries and want to share them without re-uploading.
|
||||
|
||||
### How It Works
|
||||
|
||||
- **Managed mode** (default): Photos are uploaded through the admin UI and stored inside PicPeak's storage directory.
|
||||
- **Reference mode**: Photos remain on your host filesystem. PicPeak reads them directly and generates thumbnails on demand.
|
||||
|
||||
Each event can use either mode. Reference mode events point to a folder under the configured external media root.
|
||||
|
||||
### Configuration
|
||||
|
||||
Add the following to your `.env` file:
|
||||
|
||||
```bash
|
||||
# Path where your photo library is stored on the host
|
||||
EXTERNAL_MEDIA_ROOT=/path/to/your/photos
|
||||
```
|
||||
|
||||
Then mount this path into the backend container in your `docker-compose.yml` or `docker-compose.production.yml`:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
backend:
|
||||
environment:
|
||||
- EXTERNAL_MEDIA_ROOT=/external-media
|
||||
volumes:
|
||||
- /path/to/your/photos:/external-media:ro # read-only is recommended
|
||||
```
|
||||
|
||||
> **Permissions**: Ensure the container user (`PUID`/`PGID`) has read access to the mounted directory. If thumbnails fail to generate, this is usually a permissions issue.
|
||||
|
||||
### Folder Structure
|
||||
|
||||
Organize your photos with subdirectories for each event. Within each event folder, use `individual/` and `collages/` subdirectories to classify photos:
|
||||
|
||||
```
|
||||
/path/to/your/photos/
|
||||
├── wedding-smith-2026/
|
||||
│ ├── individual/
|
||||
│ │ ├── IMG_0001.jpg
|
||||
│ │ ├── IMG_0002.jpg
|
||||
│ │ └── ...
|
||||
│ └── collages/
|
||||
│ ├── group-photo.jpg
|
||||
│ └── ...
|
||||
├── corporate-event/
|
||||
│ ├── individual/
|
||||
│ │ └── ...
|
||||
│ └── collages/
|
||||
│ └── ...
|
||||
```
|
||||
|
||||
Supported file formats: `.jpg`, `.jpeg`, `.png`, `.webp`
|
||||
|
||||
### Usage
|
||||
|
||||
1. **Create an event** in the admin panel as usual (name, date, email, etc.).
|
||||
|
||||
2. **Switch source mode** to "Reference external folder" in the event details under Source Mode.
|
||||
|
||||
3. **Browse and select** the external folder using the folder picker that appears. Navigate to the event's directory.
|
||||
|
||||
4. **Import photos** by clicking "Import from External Folder" in the Photos tab. PicPeak will:
|
||||
- Recursively scan the selected folder
|
||||
- Classify photos by subfolder name (`individual/` or `collages/`)
|
||||
- Deduplicate by filename (keeps the largest file if duplicates exist)
|
||||
- Extract image dimensions for gallery layout
|
||||
- Register the photos in the database
|
||||
|
||||
5. **Thumbnails** are generated on demand when a guest first views the gallery. There is no upfront processing delay.
|
||||
|
||||
### Limitations
|
||||
|
||||
- **Images only** — video files are not supported for external media.
|
||||
- **Read-only** — PicPeak does not modify or delete files in the external media directory.
|
||||
- **No automatic sync** — If you add new photos to the external folder, you need to re-import from the admin UI.
|
||||
- **Backup caveat** — External media originals are excluded from PicPeak's built-in backup system. Only thumbnails and database records are backed up. You are responsible for backing up the source files separately.
|
||||
|
||||
### Troubleshooting
|
||||
|
||||
| Problem | Solution |
|
||||
|---------|----------|
|
||||
| Folder picker shows empty directory | Check that the volume is mounted correctly and `EXTERNAL_MEDIA_ROOT` matches the container path |
|
||||
| "Permission denied" errors | Ensure `PUID`/`PGID` in `.env` match the owner of the external media files on the host |
|
||||
| Thumbnails not generating | Verify the backend container can read the files: `docker exec picpeak-backend ls /external-media/your-folder/` |
|
||||
| Import finds 0 photos | Only `.jpg`, `.jpeg`, `.png`, `.webp` files are supported. Check file extensions. |
|
||||
|
||||
## 🔧 Maintenance
|
||||
|
||||
### Viewing Logs
|
||||
|
||||
Generated
+86
-51
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "picpeak-backend",
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "picpeak-backend",
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.3",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.850.0",
|
||||
"@aws-sdk/lib-storage": "^3.850.0",
|
||||
@@ -14,7 +14,7 @@
|
||||
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
||||
"adm-zip": "^0.5.16",
|
||||
"archiver": "^5.3.1",
|
||||
"axios": "^1.12.2",
|
||||
"axios": "1.14.0",
|
||||
"bcrypt": "6.0.0",
|
||||
"chokidar": "4.0.3",
|
||||
"cookie-parser": "^1.4.7",
|
||||
@@ -26,7 +26,7 @@
|
||||
"express-validator": "^7.0.1",
|
||||
"fluent-ffmpeg": "^2.1.3",
|
||||
"form-data": "^4.0.4",
|
||||
"handlebars": "^4.7.8",
|
||||
"handlebars": "^4.7.9",
|
||||
"helmet": "^7.0.0",
|
||||
"i18next": "25.3.2",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
@@ -39,7 +39,7 @@
|
||||
"mime-types": "^3.0.1",
|
||||
"multer": "^2.0.2",
|
||||
"node-cron": "^3.0.2",
|
||||
"nodemailer": "^7.0.10",
|
||||
"nodemailer": "^7.0.13",
|
||||
"pg": "^8.16.3",
|
||||
"react-i18next": "^15.6.0",
|
||||
"sanitize-html": "^2.17.0",
|
||||
@@ -4071,14 +4071,14 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.13.6",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
|
||||
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz",
|
||||
"integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.11",
|
||||
"form-data": "^4.0.5",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
"proxy-from-env": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/babel-jest": {
|
||||
@@ -4290,9 +4290,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
|
||||
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
|
||||
"integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^4.0.2"
|
||||
@@ -5832,21 +5832,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-xml-builder": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.0.0.tgz",
|
||||
"integrity": "sha512-fpZuDogrAgnyt9oDDz+5DBz0zgPdPZz6D4IR7iESxRXElrlGTRkHJ9eEt+SACRJwT0FNFrt71DFQIUFBJfX/uQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-xml-parser": {
|
||||
"version": "5.4.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.4.1.tgz",
|
||||
"integrity": "sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==",
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz",
|
||||
"integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -5855,8 +5843,24 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-xml-builder": "^1.0.0",
|
||||
"strnum": "^2.1.2"
|
||||
"path-expression-matcher": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/fast-xml-parser": {
|
||||
"version": "5.5.10",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.10.tgz",
|
||||
"integrity": "sha512-go2J2xODMc32hT+4Xr/bBGXMaIoiCwrwp2mMtAvKyvEFW6S/v5Gn2pBmE4nvbwNjGhpcAiOwEv7R6/GZ6XRa9w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fast-xml-builder": "^1.1.4",
|
||||
"path-expression-matcher": "^1.2.1",
|
||||
"strnum": "^2.2.2"
|
||||
},
|
||||
"bin": {
|
||||
"fxparser": "src/cli/cli.js"
|
||||
@@ -6418,9 +6422,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/handlebars": {
|
||||
"version": "4.7.8",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
|
||||
"integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
|
||||
"version": "4.7.9",
|
||||
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz",
|
||||
"integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.5",
|
||||
@@ -6664,6 +6668,19 @@
|
||||
"cross-fetch": "4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/iconv-lite": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
|
||||
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
@@ -7977,9 +7994,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||
"version": "4.18.1",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
|
||||
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.defaults": {
|
||||
@@ -8760,9 +8777,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nodemailer": {
|
||||
"version": "7.0.12",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.12.tgz",
|
||||
"integrity": "sha512-H+rnK5bX2Pi/6ms3sN4/jRQvYSMltV6vqup/0SFOrxYYY/qoNvhXPlYq3e+Pm9RFJRwrMGbMIwi81M4dxpomhA==",
|
||||
"version": "7.0.13",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.13.tgz",
|
||||
"integrity": "sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==",
|
||||
"license": "MIT-0",
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
@@ -9145,6 +9162,21 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/path-expression-matcher": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.4.0.tgz",
|
||||
"integrity": "sha512-s4DQMxIdhj3jLFWd9LxHOplj4p9yQ4ffMGowFf3cpEgrrJjEhN0V5nxw4Ye1EViAGDoL4/1AeO6qHpqYPOzE4Q==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/NaturalIntelligence"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
@@ -9186,9 +9218,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz",
|
||||
"integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pg": {
|
||||
@@ -9579,10 +9611,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
|
||||
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/pstree.remy": {
|
||||
"version": "1.1.8",
|
||||
@@ -10659,9 +10694,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/strnum": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz",
|
||||
"integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==",
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.3.tgz",
|
||||
"integrity": "sha512-oKx6RUCuHfT3oyVjtnrmn19H1SiCqgJSg+54XqURKp5aCMbrXrhLjRN9TjuwMjiYstZ0MzDrHqkGZ5dFTKd+zg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -10760,9 +10795,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "7.5.9",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.9.tgz",
|
||||
"integrity": "sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==",
|
||||
"version": "7.5.13",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz",
|
||||
"integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"@isaacs/fs-minipass": "^4.0.0",
|
||||
|
||||
+10
-8
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "picpeak-backend",
|
||||
"version": "2.6.1",
|
||||
"version": "2.6.4",
|
||||
"description": "Backend for PicPeak event photo sharing platform",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
@@ -19,7 +19,7 @@
|
||||
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
||||
"adm-zip": "^0.5.16",
|
||||
"archiver": "^5.3.1",
|
||||
"axios": "^1.12.2",
|
||||
"axios": "1.14.0",
|
||||
"bcrypt": "6.0.0",
|
||||
"chokidar": "4.0.3",
|
||||
"cookie-parser": "^1.4.7",
|
||||
@@ -31,7 +31,7 @@
|
||||
"express-validator": "^7.0.1",
|
||||
"fluent-ffmpeg": "^2.1.3",
|
||||
"form-data": "^4.0.4",
|
||||
"handlebars": "^4.7.8",
|
||||
"handlebars": "^4.7.9",
|
||||
"helmet": "^7.0.0",
|
||||
"i18next": "25.3.2",
|
||||
"i18next-browser-languagedetector": "^8.2.0",
|
||||
@@ -44,7 +44,7 @@
|
||||
"mime-types": "^3.0.1",
|
||||
"multer": "^2.0.2",
|
||||
"node-cron": "^3.0.2",
|
||||
"nodemailer": "^7.0.10",
|
||||
"nodemailer": "^7.0.13",
|
||||
"pg": "^8.16.3",
|
||||
"react-i18next": "^15.6.0",
|
||||
"sanitize-html": "^2.17.0",
|
||||
@@ -67,10 +67,12 @@
|
||||
},
|
||||
"glob": "^11.1.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"fast-xml-parser": ">=5.3.8",
|
||||
"fast-xml-parser": ">=5.5.10",
|
||||
"qs": ">=6.14.2",
|
||||
"tar": ">=7.5.8",
|
||||
"brace-expansion": ">=5.0.0",
|
||||
"minimatch": ">=9.0.7"
|
||||
"tar": ">=7.5.13",
|
||||
"brace-expansion": ">=5.0.5",
|
||||
"minimatch": ">=9.0.7",
|
||||
"path-to-regexp": "0.1.13",
|
||||
"lodash": ">=4.18.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,18 +85,28 @@ async function sessionTimeoutMiddleware(req, res, next) {
|
||||
const now = Date.now();
|
||||
const lastActivity = sessions.get(token);
|
||||
const timeout = await getSessionTimeout();
|
||||
|
||||
// If session exists, check if it's expired
|
||||
|
||||
if (lastActivity) {
|
||||
// Existing session — check if idle too long
|
||||
if (now - lastActivity > timeout) {
|
||||
sessions.delete(token);
|
||||
return res.status(401).json({
|
||||
error: 'Session expired',
|
||||
code: 'SESSION_TIMEOUT'
|
||||
return res.status(401).json({
|
||||
error: 'Session expired',
|
||||
code: 'SESSION_TIMEOUT'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// First request with this token — check if token was issued longer ago than the timeout
|
||||
// This prevents old/stolen tokens from bypassing session timeout after server restart
|
||||
const tokenIssuedAt = (decoded.iat || 0) * 1000; // iat is in seconds
|
||||
if (now - tokenIssuedAt > timeout) {
|
||||
return res.status(401).json({
|
||||
error: 'Session expired',
|
||||
code: 'SESSION_TIMEOUT'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Update last activity
|
||||
sessions.set(token, now);
|
||||
|
||||
|
||||
@@ -122,13 +122,15 @@ router.post('/change-password', [
|
||||
// Hash new password with more rounds
|
||||
const newPasswordHash = await bcrypt.hash(newPassword, 12);
|
||||
|
||||
// Update password and clear must_change_password flag
|
||||
// Update password, set password_changed_at to invalidate existing tokens, and clear must_change_password flag
|
||||
const now = new Date();
|
||||
await db('admin_users')
|
||||
.where('id', userId)
|
||||
.update({
|
||||
password_hash: newPasswordHash,
|
||||
password_changed_at: now,
|
||||
must_change_password: false,
|
||||
updated_at: new Date()
|
||||
updated_at: now
|
||||
});
|
||||
|
||||
// Log activity
|
||||
|
||||
Generated
+14
-11
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "picpeak-frontend",
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "picpeak-frontend",
|
||||
"version": "2.5.0",
|
||||
"version": "2.6.2",
|
||||
"dependencies": {
|
||||
"@tanstack/react-query": "^5.0.0",
|
||||
"@tiptap/extension-character-count": "^2.26.1",
|
||||
@@ -20,7 +20,7 @@
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@types/react-google-recaptcha": "^2.1.9",
|
||||
"axios": "^1.12.2",
|
||||
"axios": "1.14.0",
|
||||
"clsx": "^2.0.0",
|
||||
"date-fns": "4.1.0",
|
||||
"dompurify": "^3.2.6",
|
||||
@@ -3020,14 +3020,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.13.6",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
|
||||
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz",
|
||||
"integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.11",
|
||||
"form-data": "^4.0.5",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
"proxy-from-env": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
@@ -5751,10 +5751,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"license": "MIT"
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
|
||||
"integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "picpeak-frontend",
|
||||
"private": true,
|
||||
"version": "2.6.1",
|
||||
"version": "2.6.4",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@@ -24,7 +24,7 @@
|
||||
"@types/dompurify": "^3.0.5",
|
||||
"@types/lodash": "^4.17.20",
|
||||
"@types/react-google-recaptcha": "^2.1.9",
|
||||
"axios": "^1.12.2",
|
||||
"axios": "1.14.0",
|
||||
"clsx": "^2.0.0",
|
||||
"date-fns": "4.1.0",
|
||||
"dompurify": "^3.2.6",
|
||||
|
||||
@@ -149,7 +149,11 @@ export const userManagementService = {
|
||||
* Update an admin user
|
||||
*/
|
||||
async updateUser(id: number, data: UpdateUserData): Promise<AdminUser> {
|
||||
const response = await api.put<UpdateUserResponse>(`/admin/users/${id}`, data);
|
||||
// Convert camelCase to snake_case for backend API
|
||||
const payload: Record<string, unknown> = {};
|
||||
if (data.roleId !== undefined) payload.role_id = data.roleId;
|
||||
if (data.isActive !== undefined) payload.is_active = data.isActive;
|
||||
const response = await api.put<UpdateUserResponse>(`/admin/users/${id}`, payload);
|
||||
return transformUser(response.data.user);
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user