5f6bd4ae7e
* fix(server): Reduce number of bound parameters in Access queries According to https://github.com/typeorm/typeorm/issues/7565, the introduction of bulk queries for permission checks could quickly reach the limit of 65536 bound parameters allowed by the PostgreSQL connection. To avoid reaching that limit, this first change refactors the Access queries that are expanding the set of ids multiple times. For example, `asset.checkSharedLinkAccess` expands the ids 4 times, so providing just ~16400 ids is enough to break the query. Refactored queries: * activity.checkCreateAccess ```sql -- Before SELECT "AlbumEntity"."id" AS "AlbumEntity_id" FROM "albums" "AlbumEntity" LEFT JOIN "albums_shared_users_users" "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers" ON "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."albumsId"="AlbumEntity"."id" LEFT JOIN "users" "AlbumEntity__AlbumEntity_sharedUsers" ON "AlbumEntity__AlbumEntity_sharedUsers"."id"="AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."usersId" AND ("AlbumEntity__AlbumEntity_sharedUsers"."deletedAt" IS NULL) WHERE ( ( "AlbumEntity"."id" IN ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) AND "AlbumEntity"."isActivityEnabled" = $11 AND "AlbumEntity__AlbumEntity_sharedUsers"."id" = $12 ) OR ( "AlbumEntity"."id" IN ($13, $14, $15, $16, $17, $18, $19, $20, $21, $22) AND "AlbumEntity"."isActivityEnabled" = $23 AND "AlbumEntity"."ownerId" = $24 ) ) AND "AlbumEntity"."deletedAt" IS NULL -- After SELECT "album"."id" AS "album_id" FROM "albums" "album" LEFT JOIN "albums_shared_users_users" "album_sharedUsers" ON "album_sharedUsers"."albumsId"="album"."id" LEFT JOIN "users" "sharedUsers" ON "sharedUsers"."id"="album_sharedUsers"."usersId" AND "sharedUsers"."deletedAt" IS NULL WHERE "album"."id" IN ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) AND "album"."isActivityEnabled" = true AND ( "album"."ownerId" = $11 OR "sharedUsers"."id" = $12 ) AND "album"."deletedAt" IS NULL ``` * asset.checkAlbumAccess ```sql -- Before SELECT "asset"."id" AS "assetId", "asset"."livePhotoVideoId" AS "livePhotoVideoId" FROM "albums" "album" INNER JOIN "albums_assets_assets" "album_asset" ON "album_asset"."albumsId"="album"."id" INNER JOIN "assets" "asset" ON "asset"."id"="album_asset"."assetsId" AND "asset"."deletedAt" IS NULL LEFT JOIN "albums_shared_users_users" "album_sharedUsers" ON "album_sharedUsers"."albumsId"="album"."id" LEFT JOIN "users" "sharedUsers" ON "sharedUsers"."id"="album_sharedUsers"."usersId" AND "sharedUsers"."deletedAt" IS NULL WHERE ( "album"."ownerId" = $1 OR "sharedUsers"."id" = $2 ) AND ( "asset"."id" IN ($3, $4, $5, $6, $7, $8, $9, $10, $11, $12) OR "asset"."livePhotoVideoId" IN ($13, $14, $15, $16, $17, $18, $19, $20, $21, $22) ) AND "album"."deletedAt" IS NULL -- After WITH "assetIds" AS ( SELECT unnest(array[$1, $2, $3, $4, $5, $6, $7, $8, $9, $10])::uuid AS "id" FROM (SELECT 1 AS dummy_column) "dummy_table" ) SELECT "asset"."id" AS "assetId", "asset"."livePhotoVideoId" AS "livePhotoVideoId" FROM "albums" "album" INNER JOIN "albums_assets_assets" "album_asset" ON "album_asset"."albumsId"="album"."id" INNER JOIN "assets" "asset" ON "asset"."id"="album_asset"."assetsId" AND "asset"."deletedAt" IS NULL LEFT JOIN "albums_shared_users_users" "album_sharedUsers" ON "album_sharedUsers"."albumsId"="album"."id" LEFT JOIN "users" "sharedUsers" ON "sharedUsers"."id"="album_sharedUsers"."usersId" AND "sharedUsers"."deletedAt" IS NULL WHERE ( "album"."ownerId" = $11 OR "sharedUsers"."id" = $12 ) AND ( "asset"."id" IN (SELECT id FROM "assetIds") OR "asset"."livePhotoVideoId" IN (SELECT id FROM "assetIds") ) AND "album"."deletedAt" IS NULL ``` * asset.checkSharedLinkAccess ```sql -- Before SELECT "assets"."id" AS "assetId", "assets"."livePhotoVideoId" AS "assetLivePhotoVideoId", "albumAssets"."id" AS "albumAssetId", "albumAssets"."livePhotoVideoId" AS "albumAssetLivePhotoVideoId" FROM "shared_links" "sharedLink" LEFT JOIN "albums" "album" ON "album"."id"="sharedLink"."albumId" AND "album"."deletedAt" IS NULL LEFT JOIN "shared_link__asset" "assets_sharedLink" ON "assets_sharedLink"."sharedLinksId"="sharedLink"."id" LEFT JOIN "assets" "assets" ON "assets"."id"="assets_sharedLink"."assetsId" AND "assets"."deletedAt" IS NULL LEFT JOIN "albums_assets_assets" "album_albumAssets" ON "album_albumAssets"."albumsId"="album"."id" LEFT JOIN "assets" "albumAssets" ON "albumAssets"."id"="album_albumAssets"."assetsId" AND "albumAssets"."deletedAt" IS NULL WHERE "sharedLink"."id" = $1 AND ( "assets"."id" IN ($2, $3, $4, $5, $6, $7, $8, $9, $10, $11) OR "albumAssets"."id" IN ($12, $13, $14, $15, $16, $17, $18, $19, $20, $21) OR "assets"."livePhotoVideoId" IN ($22, $23, $24, $25, $26, $27, $28, $29, $30, $31) OR "albumAssets"."livePhotoVideoId" IN ($32, $33, $34, $35, $36, $37, $38, $39, $40, $41) ) -- After WITH "assetIds" AS ( SELECT unnest(array[$1, $2, $3, $4, $5, $6, $7, $8, $9, $10])::uuid AS "id" FROM (SELECT 1 AS dummy_column) "dummy_table" ) SELECT "assets"."id" AS "assetId", "assets"."livePhotoVideoId" AS "assetLivePhotoVideoId", "albumAssets"."id" AS "albumAssetId", "albumAssets"."livePhotoVideoId" AS "albumAssetLivePhotoVideoId" FROM "shared_links" "sharedLink" LEFT JOIN "albums" "album" ON "album"."id"="sharedLink"."albumId" AND "album"."deletedAt" IS NULL LEFT JOIN "shared_link__asset" "assets_sharedLink" ON "assets_sharedLink"."sharedLinksId"="sharedLink"."id" LEFT JOIN "assets" "assets" ON "assets"."id"="assets_sharedLink"."assetsId" AND "assets"."deletedAt" IS NULL LEFT JOIN "albums_assets_assets" "album_albumAssets" ON "album_albumAssets"."albumsId"="album"."id" LEFT JOIN "assets" "albumAssets" ON "albumAssets"."id"="album_albumAssets"."assetsId" AND "albumAssets"."deletedAt" IS NULL WHERE "sharedLink"."id" = $11 AND ( "assets"."id" IN (SELECT id FROM "assetIds") OR "albumAssets"."id" IN (SELECT id FROM "assetIds") OR "assets"."livePhotoVideoId" IN (SELECT id FROM "assetIds") OR "albumAssets"."livePhotoVideoId" IN (SELECT id FROM "assetIds") ) ``` * fix: Use array overlapping instead of CTEs |
||
---|---|---|
.github | ||
.vscode | ||
cli | ||
design | ||
docker | ||
docs | ||
fastlane | ||
machine-learning | ||
misc/release | ||
mobile | ||
server | ||
web | ||
.dockerignore | ||
.editorconfig | ||
.gitattributes | ||
.gitignore | ||
.gitmodules | ||
CODE_OF_CONDUCT.md | ||
install.sh | ||
LICENSE | ||
localizely.yml | ||
Makefile | ||
README_ca_ES.md | ||
README_de_DE.md | ||
README_es_ES.md | ||
README_fr_FR.md | ||
README_it_IT.md | ||
README_ja_JP.md | ||
README_ko_KR.md | ||
README_nl_NL.md | ||
README_tr_TR.md | ||
README_zh_CN.md | ||
README.md | ||
renovate.json | ||
SECURITY.md |
Immich - High performance self-hosted photo and video backup solution
Català Español Français Italiano 日本語 한국어 Deutsch Nederlands Türkçe 中文
Disclaimer
- ⚠️ The project is under very active development.
- ⚠️ Expect bugs and breaking changes.
- ⚠️ Do not use the app as the only way to store your photos and videos.
- ⚠️ Always follow 3-2-1 backup plan for your precious photos and videos!
Content
- Official Documentation
- Roadmap
- Demo
- Features
- Introduction
- Installation
- Contribution Guidelines
- Support The Project
Documentation
You can find the main documentation, including installation guides, at https://immich.app/.
Demo
You can access the web demo at https://demo.immich.app
For the mobile app, you can use https://demo.immich.app/api
for the Server Endpoint URL
The credential
email: demo@immich.app
password: demo
Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
Features
Features | Mobile | Web |
---|---|---|
Upload and view videos and photos | Yes | Yes |
Auto backup when the app is opened | Yes | N/A |
Selective album(s) for backup | Yes | N/A |
Download photos and videos to local device | Yes | Yes |
Multi-user support | Yes | Yes |
Album and Shared albums | Yes | Yes |
Scrubbable/draggable scrollbar | Yes | Yes |
Support raw formats | Yes | Yes |
Metadata view (EXIF, map) | Yes | Yes |
Search by metadata, objects, faces, and CLIP | Yes | Yes |
Administrative functions (user management) | No | Yes |
Background backup | Yes | N/A |
Virtual scroll | Yes | Yes |
OAuth support | Yes | Yes |
API Keys | N/A | Yes |
LivePhoto/MotionPhoto backup and playback | Yes | Yes |
User-defined storage structure | Yes | Yes |
Public Sharing | No | Yes |
Archive and Favorites | Yes | Yes |
Global Map | Yes | Yes |
Partner Sharing | Yes | Yes |
Facial recognition and clustering | Yes | Yes |
Memories (x years ago) | Yes | Yes |
Offline support | Yes | No |
Read-only gallery | Yes | Yes |
Stacked Photos | Yes | Yes |
Support the project
I've committed to this project, and I will not stop. I will keep updating the docs, adding new features, and fixing bugs. But I can't do it alone. So I need your help to give me additional motivation to keep going.
As our hosts in the selfhosted.show - In the episode 'The-organization-must-not-be-name is a Hostile Actor' said, this is a massive undertaking of what the team and I are doing. And I would love to someday be able to do this full-time, and I am asking for your help to make that happen.
If you feel like this is the right cause and the app is something you are seeing yourself using for a long time, please consider supporting the project with the option below.
Donation
- Monthly donation via GitHub Sponsors
- One-time donation via GitHub Sponsors
- Librepay
- buymeacoffee
- Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX
- ZCash: u1smm4wvqegcp46zss2jf5xptchgeczp4rx7a0wu3mermf2wxahm26yyz5w9mw3f2p4emwlljxjumg774kgs8rntt9yags0whnzane4n67z4c7gppq4yyvcj404ne3r769prwzd9j8ntvqp44fa6d67sf7rmcfjmds3gmeceff4u8e92rh38nd30cr96xw6vfhk6scu4ws90ldzupr3sz