* feat(web): onboarding
* feat: openapi
* feat: modulization
* feat: page advancing
* Animation
* Add storage templaete settings
* sql
* more style
* Theme
* information and styling
* hide/show table
* Styling
* Update user property
* fix test
* fix test:
* fix e2e
* test
* Update web/src/lib/components/onboarding-page/onboarding-hello.svelte
Co-authored-by: bo0tzz <git@bo0tzz.me>
* naming
* use System Metadata
* better return type
* onboarding using server metadata
* revert previous changes in user entity
* sql
* test web
* fix test server
* server/web test
* more test
* consolidate color theme change logic
* consolidate save button to storage template
* merge main
* fix web
---------
Co-authored-by: bo0tzz <git@bo0tzz.me>
* fix(deps): update dependency @nestjs/schedule to v4
* fix: change CronJob in addCronJob to match new type required by nestjs schedule module
---------
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Zack Pollard <zackpollard@ymail.com>
* Update libraries.md - External Library step by step set up guide added
When setting up immmich external libraries, I found some missing details around the relationship between external paths and import paths. By providing an example, I am hoping it helps new users.
* chore: fix formatting
---------
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
* Update hardware-transcoding.md
I accidentally closed#5924 for some odd reason. Copied the same code changes to this one.
* updated formatting
* Update hardware-transcoding.md
Added line 66 saying that `NVIDIA_DRIVER_CAMPABILITITES` is no longer required.
* feat(server): Enqueue jobs in bulk
The Job Repository now has a `queueAll` method, that enqueues messages
in bulk (using BullMQ's
[`addBulk`](https://docs.bullmq.io/guide/queues/adding-bulks)),
improving performance when many jobs must be enqueued within the same
operation.
Primary change is in `src/domain/job/job.service.ts`, and other services
have been refactored to use `queueAll` when useful.
As a simple local benchmark, triggering a full thumbnail generation
process over a library of ~1,200 assets and ~350 faces went from
**~600ms** to **~250ms**.
* fix: Review feedback
* fix(docs): update broken link
* fix(docs): change to path
* fix(docs): second broken link
---------
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
* Updated vite and ts config file with vtest options and a new alias to fix the dev command error
* Updated package script and update the packages
-- this removes jest dependencies
* Added new setup file needed in vitest in order to be able to use the jest-dom matchers in tests
* Updated deprecated utilities when using faker
* Updated test files and mocks to use vitest instead of jest
* Enabled web test check in GitHub actions
* remove babel dependencies as they are no longer needed with vitest
* move the jest config files to a folder in case we need to go back to jest
* chore: remove old files
---------
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
* fix: locks need to be acquired and released using the same session
* feat: only allow a single storage template operation at a single time
this has been added to avoid possible rare race conditions where two files are moved at once to the same path with the same name, causing our duplicate iterator to not detect them and therefore both files will have the same name and overwrite eachother
* fix: pgvecto.rs extension breaks typeorm schema:drop command
* fix: parse postgres bigints to javascript number types when selecting data
* feat: verify file size is the same as original asset after copying file for storage template job
* feat: allow disabling of storage template job, defaults to disabled for new instances
* fix: don't allow setting concurrency for storage template migration, can cause race conditions above 1
* feat: add checksum verification when file is copied for storage template job
* fix: extract metadata for assets that aren't visible on timeline
* 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