Compare commits

...

299 Commits

Author SHA1 Message Date
Zack Pollard
e503b92d79 wip 2024-05-21 12:42:12 +01:00
Zack Pollard
b50225327d wip 2024-05-21 10:27:32 +01:00
Zack Pollard
d9fe1bf51e wip 2024-05-21 10:27:06 +01:00
renovate[bot]
6119618ae2 chore(deps): update mambaorg/micromamba:bookworm-slim docker digest to d5b8281 (#9616)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-21 09:48:11 +01:00
renovate[bot]
4d044658a0 fix(deps): update dependency svelte-maplibre to v0.9.3 (#9626)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-21 09:35:16 +01:00
renovate[bot]
67fa598f44 chore(deps): update mobile (#9621)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-21 08:31:53 +00:00
waclaw66
9222b9d130 fix(mobile): invite user list (#9624)
* fix(mobile): invite user list

* make it dense as before
2024-05-21 09:27:17 +01:00
Zack Pollard
d6757fc2dd chore: update fvmrc flutter version to 3.22.0 (#9625) 2024-05-21 09:22:22 +01:00
Alex
4f838eabbe fix(server): semver in development (#9620) 2024-05-20 23:03:28 -04:00
renovate[bot]
143b9d6828 fix(deps): update typescript-projects (#9617)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-21 00:32:18 +00:00
Jason Rasmussen
1df7be8436 refactor(server): version logic (#9615)
* refactor(server): version

* test: better version and log checks
2024-05-20 20:31:36 -04:00
renovate[bot]
5f25f28c42 chore(deps): update redis:6.2-alpine docker digest to c0634a0 (#9577)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-20 20:19:43 -04:00
renovate[bot]
ae92422df7 chore(deps): update grafana/grafana docker tag to v11 (#9599)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-20 20:19:14 -04:00
Jason Rasmussen
84d824d6a7 refactor: library type (#9525) 2024-05-20 18:09:10 -04:00
Eric Barch
4353153fe6 fix(web): render failed upload buttons correctly on mobile (#9601)
fix(web): Failed upload buttons render correctly on mobile
2024-05-20 09:59:27 -05:00
Parsa
c37bf9d5d0 fix: docker compose pull rate limit (#9600)
* fix: docker compose pull rate limit

with "registry.hub.docker.com/" behind the image name, there was an issue where "docker compose up -d" would throw a rate-limiting error, even when logged in using a docker account.

it doesn't really matter where the image is downloaded from as long as it has the same sha256 hash in docker-compose.yml

* fix: use `docker.io/` for image reference in docker-compose.yml
2024-05-20 09:58:47 -05:00
Snowknight26
5c8c0b2f5b fix(web): prevent asset grid dates from being truncated (#9603) 2024-05-20 08:21:01 -04:00
Snowknight26
39129721fa fix(web): fix add to album modal text spacing (#9606)
* fix(web): fix add to album modal text spacing

* Fix formatting
2024-05-20 08:20:08 -04:00
Matthew Momjian
451416ec88 docs: community docs typos (#9604)
typos
2024-05-19 15:50:46 -04:00
Robert Schäfer
e39ee8a16f docs: Add pgadmin4 to docker-compose.yml (#9556)
* docs: Add pgadmin4 to docker-compose.yml

Motivation
----------
The current documentation encourages to install pgAdmin3 on the host
system. It's much simpler to add `pgAdmin4` to the `docker-compose.yml`:

1. No configuration needs to be modified, just added.
2. Better security because no additional ports need to be opened on the host.
3. Easier installation. E.g. on Archlinux there is no package pgAdmin3
   anymore.
4. `pgAdmin3` does not seem to be maintained.

How to test
-----------
1. Follow the documentation.
2. See if you can connect to the immich database

* docs: better use separate config file

I assume most users will not edit the `docker-compose.yml` and forget
about `pgAdmin` once they're done. So, `pgAdmin` might get exposed to
the internet with default credentials, which is not good.

Better to leave a hint to change the credentials and keep the
configuration separate, so users start `pgAdmin` knowingly and turn it
off once they're done.
2024-05-19 07:43:40 -05:00
renovate[bot]
1e56352b04 chore(deps): update registry.hub.docker.com/library/redis:6.2-alpine docker digest to c0634a0 (#9578)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-19 12:34:09 +02:00
Jason Rasmussen
470c95d614 chore: vs code formatter settings (#9562) 2024-05-18 13:50:53 -05:00
Alex
1ad04f0b17 chore(server): openapi generation (#9585) 2024-05-18 13:50:28 -05:00
Alex
60427f18ce chore(server): return duplicate assets as group (#9576)
* chore(server): return duplicate assets as group

* file name
2024-05-18 13:15:56 -05:00
Mert
9aed736911 fix(ml): openvino not working with kernel 6.7.5 or later (#9541)
* add envs

* move to Dockerfile
2024-05-18 00:00:20 +00:00
Robert Schäfer
6b369e84fc docs: use npm in README (#9566)
Motivation
----------
Looks like `npm` is being used, `package-lock.json` is checked in
whereas `yarn.lock` is gitignored.

So, let's use `npm` in the README.

How to test
-----------
1. Follow the README.
2. It behaves just like before.
2024-05-17 16:49:23 -04:00
Jason Rasmussen
136bb69bd0 refactor: sdk init (#9563) 2024-05-17 16:48:29 -04:00
Nicholas Flamy
975f2351ec fix(server): Disable duplicate detection when smart search disabled (#9565) 2024-05-17 16:37:26 -04:00
Jason Rasmussen
2e62c7b417 refactor: node_env => immich_env (#9561) 2024-05-17 13:30:05 -04:00
Alex
2689178a35 chore(server): openapi generation for Duplicate controller (#9560) 2024-05-17 12:05:23 -05:00
Jason Rasmussen
d61418886f refactor!: port env (#9559)
refactor: port env
2024-05-17 12:59:05 -04:00
Jason Rasmussen
c03981ac1d refactor(server): new version check (#9555) 2024-05-17 12:22:39 -04:00
Jason Rasmussen
4807fc40a6 refactor!: LOG_LEVEL => IMMICH_LOG_LEVEL (#9557)
refactor: LOG_LEVEL => IMMICH_LOG_LEVEL
2024-05-17 11:44:22 -04:00
Alex
101bc290f9 chore(web): light theme text color improvement (#9553)
* chore(web): light theme text color improvement

* openapi

* openapi
2024-05-17 15:05:59 +00:00
Alex
df42352f84 chore(server): openapi generation (#9554) 2024-05-17 14:58:55 +00:00
Zack Pollard
c8aa6a62c2 fix: when using old script args, just set the workers include var (#9552)
* fix: when using old script args, just set the workers include var and move on

* fix: set process.title when using new bootstrap worker startup method
2024-05-17 15:10:57 +01:00
Zack Pollard
85aca2bb54 feat: microservices be gone (#9551)
* feat: microservices be gone and api is a worker now too

* chore: remove very old startup scripts, surely nobody is using these anymore, right?

right?....
2024-05-17 14:44:30 +01:00
Mert
ff52300624 refactor(server): duplicate controller and service (#9542)
* duplicate controller and service

* change endpoint name

* fix search tests

* remove unused import

* add to index
2024-05-16 19:39:33 -04:00
Jason Rasmussen
936a46b4ed fix(server): use jasonnnnnnnnnb (#9539) 2024-05-16 17:24:54 -04:00
Mert
d8eca168ca feat(server): fully accelerated nvenc (#9452)
* use arrayContaining

* libplacebo for nvenc

update dockerfile

* tweaks

* update nvenc options

* tweak settings

* refactor

* toggle for hardware decoding, software / hardware decoding for nvenc and rkmpp

* fix software tone-mapping not being applied

* separate configs for hw/sw

* update api

* add hw decode toggle

* fix mutating config

* remove `version` flag

* fix config type

* remove submodule

* handle temporal AQ

* remove duplicate tests

* use `tonemap_opencl`

* wording

* update docs
2024-05-16 13:30:26 -04:00
Mert
64636c0618 feat(server): near-duplicate detection (#8228)
* duplicate detection job, entity, config

* queueing

* job panel, update api

* use embedding in db instead of fetching

* disable concurrency

* only queue visible assets

* handle multiple duplicateIds

* update concurrent queue check

* add provider

* add web placeholder, server endpoint, migration, various fixes

* update sql

* select embedding by default

* rename variable

* simplify

* remove separate entity, handle re-running with different threshold, set default back to 0.02

* fix tests

* add tests

* add index to entity

* formatting

* update asset mock

* fix `upsertJobStatus` signature

* update sql

* formatting

* default to 0.03

* optimize clustering

* use asset's `duplicateId` if present

* update sql

* update tests

* expose admin setting

* refactor

* formatting

* skip if ml is disabled

* debug trash e2e

* remove from web

* remove from sidebar

* test if ml is disabled

* update sql

* separate duplicate detection from clip in config, disable by default for now

* fix doc

* lower minimum `maxDistance`

* update api

* Add and Use Duplicate Detection Feature Flag (#9364)

* Add Duplicate Detection Flag

* Use Duplicate Detection Flag

* Attempt Fixes for Failing Checks

* lower minimum `maxDistance`

* fix tests

---------

Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>

* chore: fixes and additions after rebase

* chore: update api (remove new Role enum)

* fix: left join smart search so getAll works without machine learning

* test: trash e2e go back to checking length of assets is zero

* chore: regen api after rebase

* test: fix tests after rebase

* redundant join

---------

Co-authored-by: Nicholas Flamy <30300649+NicholasFlamy@users.noreply.github.com>
Co-authored-by: Zack Pollard <zackpollard@ymail.com>
Co-authored-by: Zack Pollard <zack@futo.org>
2024-05-16 18:08:37 +01:00
Alex
673e97e71d chore(mobile): upgrade flutter to 3.22 (#9518)
* chore(mobile): upgrade flutter sdk

* gha

* update kotlin

* refactor

* ios build

* remove patch files

* not touching openapi pubpsec file
2024-05-16 10:58:02 -05:00
Jason Rasmussen
984aa8fb41 refactor(server): system config (#9517) 2024-05-15 18:58:23 -04:00
renovate[bot]
7f0f016f2e chore(deps): update dependency eslint-plugin-unicorn to v53 (#9502)
* chore(deps): update dependency eslint-plugin-unicorn to v53

* use structured clone to match new eslint rules

* use raw string instead of escaping slash

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2024-05-15 18:06:25 -04:00
Jason Rasmussen
0589575154 chore: bump open-api (#9522) 2024-05-15 16:52:52 -05:00
Jason Rasmussen
73bf8f343a chore(server): remove unused property (#9521) 2024-05-15 15:17:48 -04:00
Michel Heusschen
581b467b4b fix(server): smtp certificate validation (#9506) 2024-05-15 07:21:35 -04:00
renovate[bot]
efb844c6cd fix(deps): update dependency @zoom-image/svelte to v0.2.12 (#9487)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-14 19:33:53 -04:00
Alex The Bot
88d4338348 Version v1.105.1 2024-05-14 21:31:24 +00:00
Jason Rasmussen
ce7bbe88f9 fix(server): skip originals when deleting a library (#9496) 2024-05-14 16:29:57 -05:00
kleinMaggus
d62e90424e feat(web,mobile) Allow videos to be looped in the detail viewer (#8615)
* First version of video looping for the web

* Use prop for slideshow state

* refactor asset settings and add autoloop video setting

* rename variables and adjust description

* loop videos based on user settings in gallery viewer

* make asset viewer setting a stateless widget

* do not update video playback value if looping is enabled

* add some translations

* adjust description

* add missing id

* WIP

* chore: clean up

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-05-14 14:31:47 -05:00
Jason Rasmussen
0f129cae4a refactor(server): feature flags (#9492) 2024-05-14 15:31:36 -04:00
renovate[bot]
5583635947 chore(deps): update python:3.11-bookworm docker digest to 96de1ea (#9490) 2024-05-14 19:13:56 +00:00
renovate[bot]
b7715305b3 chore(deps): update dependency fastlane to v2.220.0 (#9491)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-14 14:12:27 -05:00
shenlong
6c008176c9 deps(mobile): update dependency auto_route to v8 (#9456)
deps: update dependency auto_route to v8

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-05-14 14:07:31 -05:00
renovate[bot]
72b1d582ba chore(deps): update dependency flutter_lints to v4 (#9488)
* chore(deps): update dependency flutter_lints to v4

* fix lints

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-05-14 18:52:07 +00:00
Jason Rasmussen
7b1112f3e3 refactor(server): system config (#9484) 2024-05-14 14:43:49 -04:00
renovate[bot]
42d0fc85ca chore(deps): update mobile (#9453)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-14 13:39:48 -05:00
Alex Tran
d32b621750 Merge branch 'main' of github.com:immich-app/immich 2024-05-14 13:38:15 -05:00
Alex Tran
d551003311 chore: post release tasks 2024-05-14 13:38:12 -05:00
dependabot[bot]
dd64379a4e chore(deps-dev): bump flask-cors from 4.0.0 to 4.0.1 in /machine-learning (#9485) 2024-05-14 14:06:40 -04:00
dependabot[bot]
c1cdda0ac4 chore(deps-dev): bump jinja2 from 3.1.3 to 3.1.4 in /machine-learning (#9483) 2024-05-14 17:08:29 +00:00
Alex The Bot
596ab39293 Version v1.105.0 2024-05-14 17:07:25 +00:00
renovate[bot]
31e57d27a7 fix(deps): update dependency @zoom-image/svelte to v0.2.11 (#9482)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-14 18:04:44 +01:00
Alex
f28b4e7c99 fix(server): sync issue when delete remotes assets (#9479) 2024-05-14 11:51:15 -05:00
dependabot[bot]
f01cf63c70 chore(deps): bump tqdm from 4.66.1 to 4.66.3 in /machine-learning (#9481) 2024-05-14 16:51:07 +00:00
dependabot[bot]
e55c5091d9 chore(deps-dev): bump werkzeug from 3.0.1 to 3.0.3 in /machine-learning (#9480) 2024-05-14 16:37:50 +00:00
klahr
e8cdf1c300 Added Swedish translation of README. (#9464) 2024-05-14 10:35:52 -05:00
Fynn Petersen-Frey
116043b2b4 feat(mobile): use efficient sync (#8842)
* feat(mobile): use efficient sync

review feedback

* adapt to changed  server endpoints

* formatting

* fix memory lane bug

* fix: bad merge

* fix call not returning correct number of asset

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-05-14 10:35:37 -05:00
Michel Heusschen
acc611a3d9 fix(web): admin settings number input validation (#9470)
* fix(web): admin settings number input validation

* fix import by creating *.ts file

* just ignore import error
2024-05-14 15:35:16 +00:00
Fynn Petersen-Frey
4d7aa7effd fix(server): new full sync return stacked assets individually (#9189)
* fix(server): new full sync return stacked assets individually

* return archived partner assets (like old getAllAssets)

* fix

* fix test

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-05-14 10:30:33 -05:00
renovate[bot]
77b8c2f330 chore(deps): update machine-learning (#9478)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-14 10:57:15 -04:00
renovate[bot]
09e9e91b6a fix(deps): update machine-learning (#9304)
* fix(deps): update machine-learning

* use fastapi-slim

* fix lock

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: mertalev <101130780+mertalev@users.noreply.github.com>
2024-05-14 14:46:20 +00:00
Mert
ad915ccd64 docs(ml): update link (#9477)
update link
2024-05-14 15:34:36 +01:00
Zack Pollard
1ea55d642e feat(server): run microservices in worker thread (#9426)
feat: start microservices in worker thread and add internal microservices for the server
2024-05-14 15:28:20 +01:00
renovate[bot]
3d5e55bdaa chore(deps): update base-image to v20240514 (major) (#9469)
chore(deps): update base-image to v20240514

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-14 08:55:09 -04:00
Jason Rasmussen
46868b3336 refactor(server): logger (#9472) 2024-05-14 08:48:49 -04:00
Ben McCann
b1ca5455b5 docs: remove mention of external assets being read-only (#9465) 2024-05-14 11:02:26 +01:00
xiagw
462f0f76a4 fix install.sh add random password for .env (#9282)
* fix install.sh add random password for .env

* fix generate random password

* remove comment
2024-05-14 10:58:28 +01:00
renovate[bot]
3d4ae9c210 chore(deps): update node.js to 53108f6 (#9450)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-14 10:53:04 +01:00
Snowknight26
f2be310aec fix(web): decrease asset viewer navigation area size (#9455)
* fix(web): decrease asset viewer navigation area size

* Remove unneeded class

* Reduce wrapping div area
2024-05-14 10:52:39 +01:00
Eric Barch
6fd6a8ba15 fix(server): addAssets and removeAssets handle duplicate assetIds (#9436)
* fix(server): addAssets and removeAssets handle duplicate assetIds

* chore(server): Add e2e tests for duplicate album additions and removals
2024-05-14 03:29:32 +00:00
David Munn
e479e556bc Fix typo in error page title (#9451)
Fixes #9447
2024-05-14 02:14:44 +00:00
renovate[bot]
bf036f2f58 fix(deps): update typescript-projects (#9454)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-13 21:42:48 -04:00
renovate[bot]
6d575e692b chore(deps): update node.js to 291e84d (#9449)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-13 21:01:34 -04:00
Sushain Cherivirala
4e6aeeda4d fix(server): support special characters in library paths (#9385)
Support special characters in library paths
2024-05-13 21:44:21 +00:00
Jason Rasmussen
a05c990718 feat(web): combine auth settings (#9427) 2024-05-13 16:40:33 -04:00
Jason Rasmussen
844f5a16a1 chore(server): remove unused column (#9431)
* chore(server): remove unused column

* fix: broken migrations
2024-05-13 16:40:16 -04:00
Jason Rasmussen
1bebc7368c fix(server): regenerate (extract) motion videos (#9438) 2024-05-13 16:38:11 -04:00
Jason Rasmussen
b7ebf3152f fix(web): show w x h correctly when media is rotated (#9435) 2024-05-13 15:03:36 -05:00
Alex Tran
5985f72643 chore: post release tasks 2024-05-13 14:17:28 -05:00
Alex The Bot
a6f557c24c Version v1.104.0 2024-05-13 18:31:57 +00:00
Alex
1045957add Localizely: Translations update (#9432)
chore(mobile): translation update
2024-05-13 13:27:21 -05:00
FedericoCalzoni
540e568e9d docs: update external-library.md (#9420)
* Update external-library.md

I believe that displaying the code for both sections, even if it seems a bit repetitive, can help prevent fast readers from overlooking it

* Apply suggestions from code review

Co-authored-by: Matthew Momjian <50788000+mmomjian@users.noreply.github.com>

---------

Co-authored-by: Matthew Momjian <50788000+mmomjian@users.noreply.github.com>
2024-05-13 12:49:30 -04:00
Jason Rasmussen
9c5a2b97bf fix(server): put system config (#9425) 2024-05-13 16:29:39 +00:00
Alex
06402aa9fb chore(server): email notification button png (#9423)
* chore(server): email notification button png

* valid user name

---------

Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2024-05-13 15:36:35 +00:00
Matthew Momjian
45316f985b Update portainer install docs (#9421)
* Update portainer.md

* Update portainer.md

* chore: cleanup

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-05-13 09:46:38 -04:00
Andreas Gerstmayr
48927f5fb9 feat(server, web): include pictures of shared albums on map (#7439)
* feat(server, web): include pictures of shared albums on map

* run prettier

* re-create api clients

* implement suggestions from code review

* shared from partner -> shared from partners

* rename to 'include shared partner assets'

* chore: fix tsc error in server and prettier in web

* fix: include assets shared via owner albums

---------

Co-authored-by: Zack Pollard <zackpollard@ymail.com>
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-05-13 09:28:57 -04:00
renovate[bot]
d121903b38 fix(deps): update dependency nestjs-otel to v6 (#9415)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-13 08:23:12 -04:00
Matthew Momjian
e2a31323bb docs: add info about postgres database checksums (#9391)
* docs staging for next release

* linting

* newline

* remove old info
2024-05-13 11:23:20 +01:00
martin
aa1dc68867 feat(web): allow hiding all unnamed faces and reset hidden faces (#9378)
* feat: hide all unnamed

* feat: remove dispatch event

* pr feedback
2024-05-13 11:17:52 +01:00
Deedikjepijn
2ae44022c2 docs: update README_nl_NL.md with missing sections from english readme (#9399)
* Update README_nl_NL.md

Added missing Features for the dutch translation of the readme file,

Added "Activities" to the dutch version,

Added "star history",

Added "Contributors".

* Update README_nl_NL.md

Translated one additional word on the dutch activities panel

* Update readme_i18n/README_nl_NL.md

Co-authored-by: bo0tzz <git@bo0tzz.me>

* Update readme_i18n/README_nl_NL.md

Co-authored-by: bo0tzz <git@bo0tzz.me>

---------

Co-authored-by: Zack Pollard <zackpollard@ymail.com>
Co-authored-by: bo0tzz <git@bo0tzz.me>
2024-05-13 10:17:13 +00:00
Nicholas Flamy
a97e0caeb9 cho(web): Icon consistency and improvements (#9403)
* Add change from outline to regular icon in sidebar when page selected to more icons. Also change Favorites to single heart consistent with mobile app.

* Forgot to remove a few unused lines. Fixed.
2024-05-12 13:08:17 -05:00
Mert
f667c9597b chore(server): less rigid transcoding tests (#9401)
* use arrayContaining

* clean up
2024-05-12 00:04:31 -04:00
martin
8fdcabaf70 fix(web): page down shortcut (#9397)
fix: page down shortcut
2024-05-11 16:49:39 -05:00
Matthew Momjian
55a7e54011 feat(server): enable data page checksums (#9384) 2024-05-11 11:11:14 -04:00
aviv926
6f82f220b8 docs: small changes&updates (#9059)
* Updates

* Adding information to the PR Checklist

* fix  #5644

* PR feedback

* suggestions

* Revert dbname
2024-05-10 23:15:38 -05:00
Tushar Harsora
dd8d7732de fix(web): Fixed video unmutes when scrubbing (#9382)
Fixed video unmutes when scrubbing

Co-authored-by: Tushar Harsora <tushar.harsora.harsora@oracle.com>
2024-05-10 16:28:21 -04:00
Mert
bb4843747b perf: cache transcoding devices (#9381)
cache transcoding devices
2024-05-10 15:03:47 -04:00
Mert
f3fbb9b588 perf: cache getConfig (#9377)
* cache `getConfig`

* critical fix

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>

---------

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
2024-05-10 18:15:25 +00:00
Daniel Dietzler
35ef82ab6b docs: add discord link (#9379)
add discord link to docs
2024-05-10 12:38:35 -05:00
Alex
fa4cd74dfd fix(web): autofocus change name field (#9376) 2024-05-10 17:18:10 +00:00
Alex
fed8d11fb8 refactor(mobile): remove shared module (#9363) 2024-05-10 11:40:41 -05:00
Matthew Momjian
3a68190b99 docs: warn against use on NTFS / WSL (#9371)
* DB filesystem

* updates to requirements

* wording

* OS update

* Update environment-variables.md

* Update FAQ.mdx

* Update requirements.md
2024-05-10 11:02:48 -04:00
martin
757840c2fd fix(web): multiple fixes for people (#9343)
fix: multiple fixes for people

Co-authored-by: Alex <alex.tran1502@gmail.com>
2024-05-09 16:23:49 -05:00
Alex
a8abf2753e chore: put back warning banner (#9362) 2024-05-09 15:53:48 -05:00
Jason Rasmussen
8743e17528 refactor(server): auth route metadata (#9344) 2024-05-09 13:58:44 -04:00
Conner Hnatiuk
34d8879d32 fix(mobile) timeline slider no longer dissapears (#9358)
fix(mobile) added heightOffset, scrollbar accounts for main appBar and does not get squished

Co-authored-by: Alex <alex.tran1502@gmail.com>
2024-05-09 17:31:25 +00:00
Alex
55031cc117 Revert "feat(android) Check server is reachable before starting background backup (#8989)" (#9324)
This reverts commit 0435de50f8.
2024-05-09 12:16:53 -05:00
Snowknight26
f9dc870166 fix(web): fix logo size on shared pages (#9346) 2024-05-09 09:21:45 -05:00
Me-Mr
87053c8c0d docs: adds hyperlinks for other readme translations (#9345)
* Translation

* Translation

* fix

---------

Co-authored-by: Abdullah Karazon <abdullahkarazon@MacBook-Air.local>
2024-05-09 14:20:38 +00:00
Tushar Harsora
57429ddc76 fix(web): Selected face in search filter doesn't show border highlight when hovered in light theme (#9348)
use border-transparent only when person is not selected

Co-authored-by: Tushar Harsora <tushar.harsora.harsora@oracle.com>
2024-05-09 09:19:26 -05:00
Matthew Momjian
13a62715e4 docs: backup restore - add warning to not backup DB_DATA_LOCATION (#9353)
backup restore add warning for backing up folder
2024-05-09 09:18:52 -05:00
Me-Mr
f3407860a1 chore: add Arabic translations to readme (#9342)
* Translation

* Translation

---------

Co-authored-by: Abdullah Karazon <abdullahkarazon@MacBook-Air.local>
2024-05-08 20:03:25 -04:00
renovate[bot]
004074b25a chore(deps): update prom/prometheus docker digest to 5c43564 (#9341)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-08 19:20:58 -04:00
The Linux Cast
98f87b275b docs: update portainer.md (#9337)
Changed #3 to lowercase, as Portainer stacks do not accept capital letters.
2024-05-08 16:54:20 -05:00
Jason Rasmussen
8e9895df27 chore(server): remove unused deps (#9334) 2024-05-08 16:39:35 -04:00
Mert
1167f0f2b7 feat(server): optimize person thumbnail generation (#7513)
* do crop and resize together

* redundant `pipelineColorspace` call

* formatting

* fix rebase

* handle orientation

* remove unused import

* formatting

* use oriented dimensions for half size calculation

* default case for orientation

* simplify orientation code

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2024-05-08 08:09:34 -05:00
Me-Mr
81e4b69caf chore: arabic translations (#9325)
* Add Arabic translations for readme file

* Update ReadMe with Arabic Translations

* Delete readme_i18n/README_ar_JO.md

* Updated Arabic Translations for Readme

---------

Co-authored-by: Abdullah Karazon <abdullahkarazon@MacBook-Air.local>
2024-05-08 00:03:41 -04:00
Dhrumil Shah
e4b777ecef feat(mobile): Add pull to refresh to sharing page (#9274)
* Add pull to refresh to sharing page

* Fix format
2024-05-07 22:52:56 -05:00
Jason Rasmussen
4e7966c8e8 chore: move test assets submodule (#9321) 2024-05-07 22:33:17 -05:00
renovate[bot]
f10fb0723d chore(deps): update mobile (#7229)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-07 22:24:37 -05:00
Dhrumil Shah
ae08abde24 fix(mobile): Fix asset selector title bar text (#9273)
Fix title bar text
2024-05-07 22:24:30 -05:00
dependabot[bot]
3790d8fcbc chore(deps): bump tj-actions/verify-changed-files from 19 to 20 (#9280)
Bumps [tj-actions/verify-changed-files](https://github.com/tj-actions/verify-changed-files) from 19 to 20.
- [Release notes](https://github.com/tj-actions/verify-changed-files/releases)
- [Changelog](https://github.com/tj-actions/verify-changed-files/blob/main/HISTORY.md)
- [Commits](https://github.com/tj-actions/verify-changed-files/compare/v19...v20)

---
updated-dependencies:
- dependency-name: tj-actions/verify-changed-files
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-07 22:22:22 -05:00
Jan
535c7a8618 Random Endpoint use querybuilder and return exifInfo (#9301)
* Random Endpoint use querybuilder and return exifInfo

* Add GenerateSql

* Commit missing sql changes
2024-05-07 23:14:04 -04:00
renovate[bot]
725d594027 chore(deps): update node.js to fac6f74 (#9317)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-07 23:12:21 -04:00
renovate[bot]
8c56c1c22b chore(deps): update typescript-projects (#9314)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-07 23:07:26 -04:00
Snowknight26
6a5bc156a6 fix(web): allow deselecting all assets from select bar (#9320)
* fix(web): allow deselecting all assets from select bar

* Change deselect logo

* select remove

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2024-05-08 03:05:19 +00:00
aryiu
5eaf489ecf fix(mobile): fix ca-CA.json file name (#9288)
Update and rename ca.json to ca-CA.json
2024-05-08 03:03:14 +00:00
renovate[bot]
8c54c13307 chore(deps): update node.js to fe31b16 (#9318)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-07 23:01:04 -04:00
Alex
675fdc1d93 chore: remove funding file (#9319) 2024-05-07 22:35:57 -04:00
Jason Rasmussen
e79d1b1ec2 refactor: create album users (#9315) 2024-05-07 16:38:09 -04:00
vovchyk bratyk
e9f99673b9 docs: add custom map styles community guide (#9311)
* Create custom-map-styles.md

* add immich_map_styles_publish.png

* Update custom-map-styles.md
2024-05-07 20:17:21 +00:00
Alex
ee6995783f fix(mobile): add user to album using new dto property (#9312)
* fix(mobile): add user to album using new DTO property

* fix(mobile): add user to album using new DTO property
2024-05-07 20:00:38 +00:00
renovate[bot]
78b5990d2a chore(deps): update dependency @types/nodemailer to v6.4.15 (#9309)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-07 15:15:24 -04:00
renovate[bot]
f13100e261 fix(deps): update dependency exiftool-vendored to v26 (#9294)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-07 15:14:49 -04:00
renovate[bot]
21fe829a2c chore(deps): update base-image to v20240507 (major) (#9303)
chore(deps): update base-image to v20240507

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-07 11:10:21 -04:00
renovate[bot]
ad404d79d4 fix(deps): update typescript-projects (#9293)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-07 11:07:50 -04:00
Alex
5806a3ce25 refactor(mobile): widgets (#9291)
* refactor(mobile): widgets

* update
2024-05-06 23:04:21 -05:00
Alex
7520ffd6c3 fix(mobile): allow select empty album as backup album (#9290) 2024-05-06 23:00:52 -05:00
Alex
bbb9453e1a fix(server): show partners archived photos on mobile timeline (#9194)
fix(mobile): show partners archived photos on timeline
2024-05-06 22:49:56 -05:00
renovate[bot]
8ff397527f chore(deps): update machine-learning (#9292) 2024-05-06 22:21:24 -04:00
Alex
a2c5eaa73e Localizely: Translations update (#9287)
chore(mobile): translation update
2024-05-06 15:39:53 -05:00
Tushar Harsora
065f1410f8 chore(mobile) Linked pt-BR.json with Mobile app and localizely.yml (#9281)
Linked pt-BR.json with Android app and localizely.yml

Co-authored-by: Tushar Harsora <tushar.harsora.harsora@oracle.com>
2024-05-06 11:22:27 -05:00
Vietbao Tran
bc31404909 fix(web): stream 360 video instead of fetching entire video (#9279) 2024-05-06 11:20:20 -05:00
martin
f08e9a4447 fix(web): click on face in detail-panel (#9271)
fix: click on face in detail-panel
2024-05-05 18:11:55 -04:00
martin
cf79bc9ed7 fix(server, web): reassigning faces (#9265)
* fix: reassiging faces

* fix: rename
2024-05-05 13:16:44 -05:00
Alex
090592e5ae refactor(mobile): pages (#9246)
* refactor(mobile): pages

* refactor

* album pages

* pages

* pages

* use *.page.dart

* representation

* put back module
2024-05-05 13:14:49 -05:00
AmAn Sharma
398d99a052 fix(web): Individual Photo Sharing with Album Link #8887 (#9264) 2024-05-05 08:00:03 -04:00
Tushar Harsora
c18beddef8 fix(mobile): Reset dropdown values in search by location on mobile (#9261)
* set derived inputs as null if source input changes

* format

---------

Co-authored-by: Tushar Harsora <tushar.harsora.harsora@oracle.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-05-04 22:44:34 +00:00
Matthew Momjian
dc4f7bef69 Specify PowerShell for win backup/restore (#9262)
Update backup-and-restore.md
2024-05-04 16:23:53 -04:00
Ben
48b490f5e9 feat(web): increase usage of CircleIconButton (#9256) 2024-05-04 14:29:50 -04:00
Jason Rasmussen
5b87abb021 feat(server): read-write external assets (#9235)
* refactor: remove isReadOnly and isExternal usages

* chore: open api

* fix: linting

* remove mobile isReadOnly dependency

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-05-03 15:34:57 -04:00
Alon Budker
d26ac431b8 docs: update post-install-steps documentation (#9239)
* docs: update post-install-steps documentation

* fix: format code with prettier (ci)
2024-05-03 08:05:34 -04:00
bo0tzz
d7f53d93a6 chore(docs): Link to helm chart README (#9238) 2024-05-03 08:03:45 -04:00
TruongSinh Tran-Nguyen
ec4e6a143e feat(web): determine duplication of upload on client (#8825)
* web upload duplicate verification on client

* _

* fix formating

* chore: clean up

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-05-02 17:26:13 -04:00
Min Idzelis
7961d00e56 fix(web): Configure web's immich sdk to use event.fetch (#9233)
* Set event.fetch on @immich/sdk

* format
2024-05-02 16:12:01 -05:00
Alex
c1253663b7 refactor(mobile): services and providers (#9232)
* refactor(mobile): services and provider

* providers
2024-05-02 15:59:14 -05:00
Jason Rasmussen
ec4eb7cd19 feat(server): immich checksum header (#9229)
* feat: dedupe by checksum header

* chore: open api
2024-05-02 15:42:26 -04:00
bo0tzz
16706f7f49 chore: Remove sponsor link from homepage (#9226) 2024-05-02 11:42:54 -05:00
bo0tzz
bf100dcde1 chore: Remove donation info from readme_i18n (#9225) 2024-05-02 17:17:14 +01:00
aviv926
c2a525170e docs: add information about Shared Albums & Assets feature (#8626)
* Shared Albums & Assets

* npm run format:fix

* Files Custom Locations

* Revert "Files Custom Locations"

This reverts commit 5209845cae.

* add info: archive size limited
2024-05-02 16:57:42 +01:00
Ryan Ribeiro
0bf923feb4 chore(readme): add Brazilian Portuguese readme (#7107) 2024-05-02 16:44:32 +01:00
jzhangdev
42f03af2dc feat(mobile): play motion video with long press gesture support (#6543) 2024-05-02 15:37:39 +00:00
Nicolò
9bce3417e9 feat(server): email notifications (#8447)
* feat(server): add `react-mail` as mail template engine and `nodemailer`

* feat(server): add `smtp` related configs to `SystemConfig`

* feat(web): add page for SMTP settings

* feat(server): add `react-email.adapter`

This adapter render the React-Email into HTML and plain/text email.
The output is set as the body of the email.

* feat(server): add `MailRepository` and `MailService`

Allow to use the NestJS-modules-mailer module to send SMTP emails.
This is the base transport for the `NotificationRepository`

* feat(server): register the job dispatcher and Job for async email

This allows to queue email sending jobs for the `EmailService`.

* feat(server): add `NotificationRepository` and `NotificationService`

This act as a middleware to properly route the notification to the right transport.
As POC I've only implemented a simple SMTP transport.

* feat(server): add `welcome` email template

* feat(server): add the first notification on `createUser` in `UserService`

This trigger an event for the `NotificationRepository` that once processes
by using the global config and per-user config will carry the payload to the right notification transport.

* chore: clean up

* chore: clean up web

* fix: type errors"

* fix package lock

* fix mail sending, option to ignore certs

* chore: open api

* chore: clean up

* remove unused import

* feat: email feature flag

* chore: remove unused interface

* small styling

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-05-02 15:43:18 +01:00
aviv926
4b86c7a298 docs: New Futo milestones (#9211)
* new Futo milestones

* npm fix

* change to DateType.DATE

* Update docs/src/pages/milestones.tsx

* Update docs/src/pages/milestones.tsx

* fix: remove release from futo milestone

---------

Co-authored-by: Zack Pollard <zackpollard@ymail.com>
2024-05-02 00:47:09 +00:00
Zack Pollard
a2c040a47f fix(docs): allow no release specified for milestone (#9212)
fix(docs): make release optional in milestones
default to tag if not specified when tag is also specified
2024-05-02 01:33:17 +01:00
bo0tzz
959b3f05d2 chore: Move mobile backup FAQ to correct section (#9200) 2024-05-02 01:33:17 +01:00
Mert
a0c43a2b5a fix(server): person thumbnails not being queued during thumbnail generation (#9195)
use face id instead of asset id
2024-05-02 01:33:17 +01:00
Yu-Cheng, Xia
b7d0bc16bb fix(web): update assets after delete (#9151)
* upadte assets after delete

* fix lint/format

* handle archive action

* chore: cleanup

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-05-02 01:33:17 +01:00
renovate[bot]
ef09fc4157 fix(deps): update typescript-projects (#9187)
* fix(deps): update typescript-projects

* chore: missing dependency

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-05-02 01:33:17 +01:00
Jason Rasmussen
84d638645d docs: link to blog in announcement (#9208) 2024-05-02 01:33:17 +01:00
Zack Pollard
64e9791a3f chore: remove donation links from the readme, docs and release notes template (#9207) 2024-05-02 01:33:17 +01:00
Alex Tran
0aa3b29eeb feat: dream comes true 2024-05-02 01:33:17 +01:00
Zack Pollard
0a598ae1b8 chore: fvm and node dependency updates and pinning (#9203)
* chore(mobile): ignore .fvm directory and update .fvmrc version

* chore(cli): add nvm and volta node versioning files/config

* chore(docs): add nvm and volta node versioning files/config, bump to node v20

* chore(server): add nvm node versioning file

* chore(web): add nvm and volta node versioning files/config

* chore(e2e): add nvm and volta node versioning files/config
2024-05-01 14:05:18 +01:00
Alex
c0495ca23f fix(mobile): show all places don't show all places (#9193)
fix(mobile): show all places doesn't show all places
2024-04-30 22:14:33 -05:00
Alex
f057fe045e refactor(mobile): entities and models (#9182)
* refactor(mobile): entities

* store entity

* refactor: models

* remove domain

* save all

* bad refactor
2024-04-30 21:36:40 -05:00
renovate[bot]
eba42b245d chore(deps): update base-image to v20240430 (major) (#9179)
chore(deps): update base-image to v20240430

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-30 22:04:28 -04:00
Alex
2cf63eeeab fix(web): missing add photos button in album when viewed as owner (#9183) 2024-04-30 15:38:25 -05:00
renovate[bot]
926de96ce6 chore(deps): update dependency ruff to v0.4.2 (#9186) 2024-04-30 14:38:05 -04:00
renovate[bot]
374a9b557b chore(deps): update mambaorg/micromamba:bookworm-slim docker digest to f4c5c3c (#9181) 2024-04-30 13:37:29 -04:00
renovate[bot]
9e79a23bbe fix(deps): update typescript-projects (#9169)
* fix(deps): update typescript-projects

* fix: type error

* fix: more imports

---------

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-04-30 09:49:14 -04:00
renovate[bot]
3212069eec chore(deps): update node.js to 7a91aa3 (#9166)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-29 22:25:41 -04:00
renovate[bot]
c1636ef7ab chore(deps): update machine-learning (#9168)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-30 02:24:09 +00:00
Ben McCann
0b0ab99016 chore: upgrade docusaurus-preset-openapi to remove axios (#9167) 2024-04-29 22:19:18 -04:00
renovate[bot]
4f097d9106 chore(deps): update node.js to 142644a (#9165)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-29 22:15:22 -04:00
Yoonjae Choi
461f2595b5 fix(web): Prevent DELETE key from clearing DateInput in modal (#8846)
* fix(web): Prevent DELETE key from clearing DateInput in modal (#8804)

* chore: test prettier

* fix: stop propagation

* fix: stop propagation in modal

* fix: remove unnecessary function and revise lint issue

* chore: cleanup

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-04-29 17:59:32 -04:00
martin
5722c830ff refactor(web): search people (#9082)
* refactor: search people

* fix: test

* fix: timeout

* fix: callbacks

* fix: simplify

* remove unused var

* refactor: rename file

* fix: focus when deleting last character

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-04-29 21:38:15 +00:00
Ben
72ce81f0c2 feat(web,a11y): replace IconButton with CircleIconButton (#9153)
* feat(web,a11y): replace IconButton with CircleIconButton

* wip: cleanup

* wip: bring back viewbox

* fix: add label to search bar
2024-04-29 17:17:22 -04:00
renovate[bot]
0c9bf2835d chore(deps): update dependency supertest to v7 (#9156)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-29 14:44:56 -04:00
Alex The Bot
ba18776fc2 Version v1.103.1 2024-04-29 17:53:49 +00:00
Alex
fdae0dcbe5 fix(web): permanent url for partner page (#9158)
* fix(web): permanent url for partner page

* fix: format
2024-04-29 12:16:17 -05:00
Alex
25262b644f chore: post release tasks 2024-04-29 10:31:50 -05:00
Alex The Bot
4291b38769 Version v1.103.0 2024-04-29 14:42:49 +00:00
Matthew Momjian
ee4877b090 chore(docs): collapse some small sections and add a note on MEDIA_LOCATION variable (#9150)
* Update environment-variables.md

* Update config-file.md

* Update config-file.md

* Update command-line-interface.md

* linting
2024-04-29 09:23:13 -05:00
Alex
b4f6184aa6 chore(mobile): more logs (#9081) 2024-04-29 09:17:49 -05:00
Alex
87de809e3d fix(mobile): sync issue (#9149) 2024-04-29 08:54:21 -05:00
Jason Rasmussen
59caf1fce4 chore: lifecycle metadata (#9103)
feat(server): track endpoint lifecycle
2024-04-29 13:48:28 +00:00
Matthew Momjian
6eb5d2e95e docs: update custom locations and cleanup backup/restore paths (#9148)
* Create separate-storage.md

* Update separate-storage.md

* Update separate-storage.md

* Update separate-storage.md

* Update separate-storage.md

* Update separate-storage.md

* Update separate-storage.md

* Update separate-storage.md

* Update separate-storage.md

* Update separate-storage.md

* New article for docker storage on SSD

* Update separate-storage.md

* Update separate-storage.md

* In the process, found errors in backup and restore

* Update backup-and-restore.md

* Update backup-and-restore.md

* Update backup-and-restore.md

* Update custom-locations.md

* Update custom-locations.md

* Update custom-locations.md

* Update custom-locations.md

* linting
2024-04-29 09:47:07 -04:00
Fynn Petersen-Frey
32e7cfea3d fix(server): stacked assets for full sync, userIds as array for delta sync (#9100)
* fix(server): stacked assets for full sync, userIds as array for delta sync

* refactor(server): sync

* fix getDeltaSync after partner removal

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-04-28 23:24:21 -04:00
Matthew Momjian
fc2e709ad4 docs: update env vars for new deployment (#9145)
* Update environment-variables.md

* Update environment-variables.md

* add DB_DATA_LOCATION
2024-04-28 21:54:27 -04:00
Matthew Momjian
3c7b8d560f docs: update queries for descriptions (#9144)
* update queries for descriptions

* join to assets

* specify exif.

* Update database-queries.md

LEFT JOIN not needed here

* Update database-queries.md

* Update database-queries.md
2024-04-28 23:15:32 +00:00
Hermes Espínola González
4bb7d2df49 fix(server): Improve reverse geocoded location metadata (#9051)
* fix: improve reverse geocoding

* fix: update tests referencing states

* fix: expect state suggestion in any order

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-04-28 21:29:08 +01:00
Alex
a2cf8c7fc7 docs: background backup FAQ (#9142)
* docs: background backup FAQ

* Update docs/docs/FAQ.mdx

Co-authored-by: Matthew Momjian <50788000+mmomjian@users.noreply.github.com>

* Update docs/docs/FAQ.mdx

Co-authored-by: Matthew Momjian <50788000+mmomjian@users.noreply.github.com>

* Update docs/docs/FAQ.mdx

Co-authored-by: Matthew Momjian <50788000+mmomjian@users.noreply.github.com>

* Update docs/docs/FAQ.mdx

Co-authored-by: Matthew Momjian <50788000+mmomjian@users.noreply.github.com>

---------

Co-authored-by: Matthew Momjian <50788000+mmomjian@users.noreply.github.com>
2024-04-28 19:23:57 +00:00
Alex
56a42dad17 feat(web): add menu bar with action buttons to memory view (#9141)
feat(web): add menu bar with action button to memory lane
2024-04-28 13:39:57 -05:00
Ben
13e093b3c2 chore(web): remove CircleIconButton slot (#9131) 2024-04-28 15:20:16 +00:00
Mert
2648032163 fix(server): untracked thumbnail and preview images (#9126)
* delete old thumbnails

* add tests

* revert invisible asset handling
2024-04-27 18:43:05 -04:00
Ben
19aa97da02 feat(web): standardize CircleIconButton colors (#9127)
* feat(web): standardize CircleIconButton colors

* fix: memory lane close button color
2024-04-27 22:29:43 +00:00
Jason Rasmussen
034c928d9e feat(server): remove inactive sessions (#9121)
* feat(server): remove inactive sessions

* add rudimentary unit test

---------

Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2024-04-27 16:45:16 -04:00
dependabot[bot]
953896a35a chore(deps): bump onnx from 1.15.0 to 1.16.0 in /machine-learning (#9122)
Bumps [onnx](https://github.com/onnx/onnx) from 1.15.0 to 1.16.0.
- [Release notes](https://github.com/onnx/onnx/releases)
- [Changelog](https://github.com/onnx/onnx/blob/main/docs/Changelog-ml.md)
- [Commits](https://github.com/onnx/onnx/compare/v1.15.0...v1.16.0)

---
updated-dependencies:
- dependency-name: onnx
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-27 19:50:49 +00:00
Nguyễn Hoàng Đức
833a78181b fix(server): asset count on meta tag of shared links (#9107)
* fix asset count on meta tag of shared links

* fix lint

* fix style

* add e2e tests
2024-04-27 14:56:13 -04:00
Alex
cf01ec1eb0 chore(mobile): Use bulk update endpoint (#9110)
* chore(mobile): bulk update

* deterministic update on client
2024-04-27 13:10:27 -05:00
Daniel Dietzler
90882a9b26 refactor(server): repositories (#9119)
refactor repos
2024-04-27 13:52:05 -04:00
Thomas
0b68cc2da6 docs: correct milestones (#9120)
Update milestones.tsx
2024-04-27 17:48:35 +00:00
Jason Rasmussen
7ea539b753 chore(server): default postgres hostname to compose service name (#9118) 2024-04-27 13:43:45 -04:00
Daniel Dietzler
1e34b01986 docs: new milestones (#9113)
* 30k stars milestone

* metrics and contributors
2024-04-27 17:14:53 +00:00
Jason Rasmussen
0d8a04b43c chore: for redis, use service name as the default (#9109)
chore: use service name as default redis hostname
2024-04-27 11:04:23 -04:00
Jason Rasmussen
5a49de5592 chore(server): remove old asset search (#9104)
* chore(server): remove old asset search

* chore: remove more unused search code
2024-04-27 12:57:39 +00:00
Mx
0c60aaf557 Update hardware-transcoding.md (#9094)
* Update hardware-transcoding.md

Adding tutorial on how to add QuickSync on Immich dockerfile on Unraid

* cleanup

Co-authored-by: bo0tzz <git@bo0tzz.me>

* fix header

* fix header

---------

Co-authored-by: Mert <101130780+mertalev@users.noreply.github.com>
Co-authored-by: bo0tzz <git@bo0tzz.me>
2024-04-27 00:27:49 -04:00
bercribe
0571901288 Fix backup and restore docker compose issue (#8939)
For the backup and restore docker compose config to work, the `POSTGRES_DB` environment variable needs to be specified
2024-04-27 00:14:51 -04:00
mgabor
557f9d8e5f fix(web): solve error starting docker for local development (#8550) 2024-04-27 00:47:38 +00:00
Ben McCann
8d5729c3b2 chore(docs): upgrade docusaurus to address dependabot warnings (#9102) 2024-04-26 20:16:14 -04:00
Nguyễn Hoàng Đức
0ff0b891a7 feat(web): separate command to build with rollup plugin visualizer (#9097)
separate command to build with rollup plugin visualizer
2024-04-26 15:36:30 -04:00
Ben
53d571d29e feat(web,a11y): form and search filter accessibility (#9087)
* feat(web,a11y): search filter accessibility

- visible focus rings
- labels for text search
- responsive buttons / radio buttons / checkboxes
- buttons to lowercase
- add fieldsets to radio buttons and checkboxes, so the screen reader
  announces the label for the group

* feat: extract inputs into reusable components, replace all checkboxes

* chore: revert changes to responsive buttons

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-04-26 01:18:19 -05:00
DeadEnd
00d186ec52 chore(mobile): add toast notification for "Download Started" (#9023)
Add Toast for download started

Add Toast notification for mobile download started.
Added new placeholder in each language file - populated with best estimate translations.
2024-04-26 01:08:52 -05:00
Jason Rasmussen
69f8bfe874 fix(server): default host binding (#9090) 2024-04-26 01:02:19 -05:00
Alex
1d15cfb5f3 chore(server): remove unusuned endpoint/service/interface in asset-v1 (#9086) 2024-04-26 01:02:04 -05:00
Conner Hnatiuk
3e03f5348f fix(mobile): restore button in asset viewer (#8919)
* fix:(mobile): spell error in top_control_app_bar.dart in function buildAddToAlbumButtom

* fix(mobile): add restore button to individual image view of trashed assets

* formatting
2024-04-26 01:01:03 -05:00
Nguyễn Hoàng Đức
59537f8f1b fix(web): add debounce to location search (#9074) 2024-04-26 01:00:06 -05:00
Alex
f1083a4c73 fix(mobile): group setting change doesn't propagate across the app (#9066)
fix(mobile): group setting change doesn't propage across the app
2024-04-26 00:54:45 -05:00
Alex
52bcb46b42 fix(mobile): show places in Search page on mobile (#9085)
* fix(mobile): show map on mobile

* remove ununsed code
2024-04-26 00:49:31 -05:00
Mert
d52ed51aab fix(cli): dry run being inaccurate (#9088) 2024-04-25 21:14:48 -04:00
martin
c9dcb5c624 fix(server): activity with deleted assets / users (#9068)
fix: activity with deleted assets / users
2024-04-25 17:45:15 -04:00
Conner Hnatiuk
40899f6137 fix(web): the selected tab on the sidebar was not always shown as selected (#9084)
fix(web): the selected tab on the sidebar was not always shown as selected. Used regex to get necessary portion for comparison
2024-04-25 14:42:09 -05:00
Alex
912d0c4d74 feat(web): permanent url in shared link (#9083) 2024-04-25 14:32:21 -05:00
martin
adb607c3ee fix(web): closing asset viewer (#8933)
* fix: interact with stack trigger navigation

* fix: missing exifInfo

* fix: changing asset

* fix: col-span

* revert change

* fix: remove changes
2024-04-25 11:24:02 -05:00
Mert
c14a2eda5d fix(cli): --ignore getting ignored (#9071) 2024-04-25 10:48:17 -04:00
Conner Hnatiuk
a0d03925e0 fix(mobile): appBar on home screen animates out and doesnt alter asset grid position (#9026) 2024-04-25 09:36:35 -05:00
martyfuhry
732bd1e652 fix(mobile): Fixes memory image cache for local images (#9019)
* Fixes equality operator for immich local image provider

* Changes image cache manager to no longer be image cache managers

* Updates large image cache to 12 images

format

* Try 5 Image cache

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2024-04-24 23:30:32 -05:00
mgabor
2943f93098 feat: readonly album sharing (#8720)
* rename albums_shared_users_users to album_permissions and add readonly column

* disable synchronize on the original join table

* remove unnecessary FK names

* set readonly=true as default for new album shares

* separate and implement album READ and WRITE permission

* expose albumPermissions on the API, deprecate sharedUsers

* generate openapi

* create readonly view on frontend

* ??? move slideshow button out from ellipsis menu so that non-owners can have access too

* correct sharedUsers joins

* add album permission repository

* remove a log

* fix assetCount getting reset when adding users

* fix lint

* add set permission endpoint and UI

* sort users

* remove log

* Revert "??? move slideshow button out from ellipsis menu so that non-owners can have access too"

This reverts commit 1343bfa311.

* rename stuff

* fix db schema annotations

* sql generate

* change readonly default to follow migration

* fix deprecation notice

* change readonly boolean to role enum

* fix joincolumn as primary key

* rename albumUserRepository in album service

* clean up userId and albumId

* add write access to shared link

* fix existing tests

* switch to vitest

* format and fix tests on web

* add new test

* fix one e2e test

* rename new API field to albumUsers

* capitalize serverside enum

* remove unused ReadWrite type

* missed rename from previous commit

* rename to albumUsers in album entity as well

* remove outdated Equals calls

* unnecessary relation

* rename to updateUser in album service

* minor renamery

* move sorting to backend

* rename and separate ALBUM_WRITE as ADD_ASSET and REMOVE_ASSET

* fix tests

* fix "should migrate single moving picture" test failing on European system timezone

* generated changes after merge

* lint fix

* fix correct page to open after removing user from album

* fix e2e tests and some bugs

* rename updateAlbumUser rest endpoint

* add new e2e tests for updateAlbumUser endpoint

* small optimizations

* refactor album e2e test, add new album shared with viewer

* add new test to check if viewer can see the album

* add new e2e tests for readonly share

* failing test: User delete doesn't cascade to UserAlbum entity

* fix: handle deleted users

* use lodash for sort

* add role to addUsersToAlbum endpoint

* add UI for adding editors

* lint fixes

* change role back to editor as DB default

* fix server tests

* redesign user selection modal editor selector

* style tweaks

* fix type error

* Revert "style tweaks"

This reverts commit ab604f4c8f.

* Revert "redesign user selection modal editor selector"

This reverts commit e6f344856c.

* chore: cleanup and improve add user modal

* chore: open api

* small styling

---------

Co-authored-by: mgabor <>
Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-04-25 04:19:49 +00:00
Snowknight26
0b3373c552 fix(web): Move stack slideshow container higher in the DOM (#8998)
Move stack slideshow container higher in the DOM
2024-04-24 23:19:23 -05:00
Mert
a90138e42e docs(ml): clarify cuda and openvino requirements (#9072)
update docs
2024-04-24 23:15:26 -05:00
Mert
466451abc9 feat(server): env variable to skip migrations on startup (#9069)
* env variable to skip migrations

* update docs

* update env doc
2024-04-24 22:52:38 -04:00
renovate[bot]
f0f9053115 chore(deps): update base-image to v20240423 (major) (#9064)
chore(deps): update base-image to v20240423

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-24 17:31:29 -04:00
Arno Wiest
dc9b51ad02 feat(mobile): Added "jump to date" functionality to the memory view (#7323)
* implemented jump to date from memory

* Changed implementation to a ValueNotifier & fixes

* remove debug code

* feat(mobile):
- Added index bound checks
- Handled edge cases when scrolling to the very bottom of the grid-view
- removing the listener on dispose

* feat(mobile): fixed debug index offset & added debug toast for scroll errors

* feat(mobile): added more debug toasts...

* feat(mobile): scroll to month, if timeline is not grouped by days

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-04-24 15:02:03 -05:00
Alexandre Truppel
0dbe44cb78 feat(web): upload json config (#8953)
* Button added, config is uploaded

* Refactored to pass "npm run lint" (also verified other PR checklist Web checks)

* Auto-save on config upload

* Static input element

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-04-24 14:43:43 -05:00
Min Idzelis
a78260296c feat(web): assets now have a permanent URL (#8532)
* Remove asest redirect pages

* Rename route paths to handle optional assetId

* Update old references to new routes

* Load and display asset from all routes that can show assetId

* Add <main> in base layout, update portals to target it

* Wire up updating navigation in response to open/close/prev/next

* Replace events with navigation functions

* Add types to param matcher

* misc cleanup

* Fix reload on /search pages

* Avoid loading bar between photos nav. Delay loading bar by 200ms for all navigations

* Update url for maps routes. Note: on page reload, next/prev is not available

* Dynamically load asset-viewer on map page

* When reloading a url with assetUrl, hide background page to prevent flash during load

* Mostly style, review comments

* Load buckets for assets on demand

* Forgot this update call

* typo

* fix test

* Fix carelessness

* Review comment

* merge main

* remove assets

* fix submodule

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
Co-authored-by: Daniel Dietzler <mail@ddietzler.dev>
2024-04-24 19:24:19 +00:00
hdlineage
1e004611e4 feat(mobile) Add zh-TW.json for Traditional Chinese Language Support (#9046)
* Create zh-TW.json for Traditional Chinese Language Support

zh-TW for Traditional Chinese (Used in Taiwan and Hong Kong)

* Update zh-TW.json for missed translation.

Update missed translation at line 25.

* add zh-TW to localizely

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2024-04-24 15:28:47 +00:00
Julian Wachholz
2593110219 feat(web): display age with months if age is less than 2 (#9053) 2024-04-24 10:09:59 -05:00
Thoroslives
56ce58c718 Update community-projects.tsx (#9057)
* Update community-projects.tsx

I have made a repo for the remove offline assets python script that was linked as one of my gists. This repo has much more explanation as the the usage and troubleshooting and allows for the contribution from other community members.

* Update docs/src/components/community-projects.tsx

Co-authored-by: bo0tzz <git@bo0tzz.me>

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
Co-authored-by: bo0tzz <git@bo0tzz.me>
2024-04-24 14:53:30 +00:00
Nguyễn Hoàng Đức
aac789f788 fix(web): handle non-http client side page error (#8901)
handle non-http client side page error
2024-04-24 08:32:00 -04:00
martin
99ccf28bc6 fix(web): search people (#8955)
* fix: search people

* Update web/src/routes/(user)/people/+page.svelte

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>

* pr feedback

* pr feedback

---------

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
2024-04-23 17:03:29 -05:00
Alex
48b0b7e8bd fix(mobile): ios random logout (#8997)
* fix(mobile): random logout

* move logout mechanism to the end

* More logs

* wording

* more logs

* fixed

* Better check
2024-04-23 16:09:10 -05:00
renovate[bot]
70c78a09a4 chore(deps): update dependency ruff to v0.4.0 (#9047) 2024-04-23 17:03:32 -04:00
renovate[bot]
4458cc4370 chore(deps): update dependency @testing-library/svelte to v5 (#9044)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-23 21:01:58 +00:00
renovate[bot]
0466da03ae fix(deps): update typescript-projects (#9028)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-04-23 16:50:18 -04:00
renovate[bot]
7a2c1bab23 chore(deps): update machine-learning (#9029) 2024-04-23 14:33:12 -04:00
devjn
0435de50f8 feat(android) Check server is reachable before starting background backup (#8989)
* Check that server is reachable before starting backup work

* Fix iOS not starting background service

---------

Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
2024-04-23 12:50:34 -05:00
Adam Jackson
661540c886 refactor(server, web, mobile): Change wording of memory titles (#9025)
Change wording of memory titles
2024-04-23 15:08:02 +00:00
bo0tzz
f5cf057e84 fix: Make DB_DATA_LOCATION more prominent in .env file (#9035)
We've already had a case in #9033 of someone forgetting to change it and losing the data on reboot.
2024-04-23 08:09:24 -05:00
Matthew Momjian
a91fd772e4 docs: default to sed pg_catalog for Linux, document restore/reset for PG bind mount (#9021)
* Update FAQ.mdx

* Update FAQ.mdx

* Update FAQ.mdx

* Update FAQ.mdx

* Update FAQ.mdx

* Update FAQ.mdx

* Update backup-and-restore.md

* Update backup-and-restore.md

* linting

* Update backup-and-restore.md

* Update FAQ.mdx

* Update backup-and-restore.md

* Update docs/docs/administration/backup-and-restore.md

Co-authored-by: Mert <101130780+mertalev@users.noreply.github.com>

* Update FAQ.mdx

---------

Co-authored-by: Mert <101130780+mertalev@users.noreply.github.com>
2024-04-22 18:40:19 -04:00
Daniel Dietzler
7f1651df71 chore(server): enable swagger to take class validator rules into account (#9022)
* enable swagger to take class validator rules into account

* chore: open api
2024-04-22 18:13:44 -04:00
Aaron Berndsen
c9a079201a docs: update "move all data" instructions in FAQ (#8976)
* Update FAQ.mdx

chore(docs): update "move all data" FAQ instructions.

* Apply suggestions from code review

fix: (apply suggestions) use sql-compliant comments

Co-authored-by: Matthew Momjian <50788000+mmomjian@users.noreply.github.com>

* fix: Update FAQ.mdx

---------

Co-authored-by: Matthew Momjian <50788000+mmomjian@users.noreply.github.com>
2024-04-22 12:53:55 +00:00
Alex
be4a783845 fix(web): wrong month on timeline scrollbar cursor (#8996)
* fix(web): wrong month on timeline scrollbar cursor

* revert unnesessary change
2024-04-22 06:22:59 -05:00
Mert
c30cd3b378 chore: test more formats in e2e (#9001) 2024-04-22 01:35:27 -04:00
TruongSinh Tran-Nguyen
0d3cc28f45 feat(web): support 360 video (equirectangular) (#8762)
* [web]: support 360 video

* lint

* lint

* fix typing

---------

Co-authored-by: Alex <alex.tran1502@gmail.com>
2024-04-21 19:14:54 +00:00
Conner Hnatiuk
f004487be0 fix(web): trash page now auto refreshes (#8978)
* fix(web): the trash page now auto refreshes when restore all or empty trash is clicked. Also shows number of assets affected.

* formatting
2024-04-21 14:07:17 -05:00
clementdelestre
21231d53a5 feat(mobile): add i18n in multiselect-grid and update translation (en and fr) (#8993)
* add i18n in multiselect grid (en-fr)

* add FR translations from (haptic feedback)

* revert settings
2024-04-21 13:26:19 -05:00
Daniel Dietzler
a99862120d feat: mobile label for renovate pull requests (#8991)
mobile lable for renovate pull requests
2024-04-21 13:11:03 -05:00
shenlong
776023b149 dep(mobile): upgrade gradle (#8409)
* dep(mobile): upgrade gradle

* chore(deps): update kotlin & guava

* build: change java version and flutter test version

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
2024-04-20 23:07:32 -05:00
martin
7d4187962a feat(web): new look option for slideshow (#8924)
feat: new look option for slideshow
2024-04-20 23:06:49 -05:00
Jason Rasmussen
a93534fc3c refactor(server): session interface types (#8977) 2024-04-20 23:45:55 -04:00
Alex
cef84f6ced chore(mobile): override appbundle on PlayStore before getting released (#8960) 2024-04-20 19:56:03 -05:00
Alex The Bot
a2180a467d Version v1.102.3 2024-04-20 20:17:39 +00:00
Jason Rasmussen
1e3dceea4d fix(server): session refresh (#8974) 2024-04-20 15:15:25 -05:00
Mert
fd4514711f feat(server): enable AV1 encoding for NVENC (#8959)
allow av1 for nvenc
2024-04-20 14:52:50 -04:00
Alex
2dd7c13b88 Revert "feat(android) Check server is reachable before starting background backup (#8594)" (#8958)
This reverts commit 71b6d8b569.
2024-04-20 12:15:26 -05:00
Alex
40931b5668 chore: post release tasks 2024-04-20 11:15:41 -05:00
Alex The Bot
25549b87c9 Version v1.102.2 2024-04-20 15:55:32 +00:00
Alex
7ec62f12b5 Revert "fix(mobile): random logout (#8739)" (#8954)
This reverts commit 97c099e26d.
2024-04-20 10:53:52 -05:00
Jaryl Chng
caf76f0713 feat(server): enable AV1 encoding for QSV (#8942) 2024-04-20 10:36:00 -04:00
martin
6778653825 fix(web): keep focus when searching people (#8950)
fix: keep focus when searching people

Co-authored-by: Alex <alex.tran1502@gmail.com>
2024-04-20 14:18:31 +00:00
Alex
c858b43717 chore: post release tasks 2024-04-20 09:12:11 -05:00
Alex The Bot
6eb1b82541 Version v1.102.1 2024-04-20 13:43:46 +00:00
devjn
71b6d8b569 feat(android) Check server is reachable before starting background backup (#8594)
* Bump androidx work version to 2.9.0

* Check that server is reachable before starting backup work

* Dart format

* Cleanup debug logs

* Fix analysis
2024-04-20 08:39:04 -05:00
Conner
3abfe3c99e fix(web): restore button in asset viewer (#8935)
* fix(web): restore button added to trashed asset-view to restore single item

* fixed the asset-viewer menu to update upon restoration

* prettier formatting complete, testing passed

* chore: clean up

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2024-04-20 01:19:50 +00:00
Jason Rasmussen
171b6bb0a6 refactor: system metadata (#8923)
refactor(server): system metadata
2024-04-19 20:36:15 -04:00
Daniel Dietzler
78c7ff855d refactor(server): move file file report endpoints to their own controller (#8925)
* move file report to its own controller

* chore: open api
2024-04-19 20:35:54 -04:00
Alex
57be9182d4 chore: post release tasks 2024-04-19 15:32:45 -05:00
1545 changed files with 33647 additions and 21720 deletions

View File

@@ -23,7 +23,6 @@ server/node_modules/
server/upload/
server/dist/
server/www/
server/test/assets/
web/node_modules/
web/coverage/

5
.github/FUNDING.yml vendored
View File

@@ -1,5 +0,0 @@
# These are supported funding model platforms
github: immich-app
liberapay: alex.tran1502
custom: https://www.buymeacoffee.com/altran1502

View File

@@ -37,15 +37,15 @@ jobs:
- uses: actions/setup-java@v4
with:
distribution: "zulu"
java-version: "11.0.21+9"
cache: "gradle"
distribution: 'zulu'
java-version: '17'
cache: 'gradle'
- name: Setup Flutter SDK
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: "3.19.3"
channel: 'stable'
flutter-version: '3.22.0'
cache: true
- name: Create the Keystore

View File

@@ -22,8 +22,8 @@ jobs:
- name: Setup Flutter SDK
uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: "3.19.3"
channel: 'stable'
flutter-version: '3.22.0'
- name: Install dependencies
run: dart pub get

View File

@@ -208,7 +208,7 @@ jobs:
uses: subosito/flutter-action@v2
with:
channel: 'stable'
flutter-version: '3.16.9'
flutter-version: '3.22.0'
- name: Run tests
working-directory: ./mobile
run: flutter test -j 1
@@ -264,7 +264,7 @@ jobs:
- name: Run API generation
run: make open-api
- name: Find file changes
uses: tj-actions/verify-changed-files@v19
uses: tj-actions/verify-changed-files@v20
id: verify-changed-files
with:
files: |
@@ -319,7 +319,7 @@ jobs:
run: npm run typeorm:migrations:generate ./src/migrations/TestMigration
- name: Find file changes
uses: tj-actions/verify-changed-files@v19
uses: tj-actions/verify-changed-files@v20
id: verify-changed-files
with:
files: |
@@ -337,7 +337,7 @@ jobs:
DB_URL: postgres://postgres:postgres@localhost:5432/immich
- name: Find file changes
uses: tj-actions/verify-changed-files@v19
uses: tj-actions/verify-changed-files@v20
id: verify-changed-sql-files
with:
files: |

2
.gitmodules vendored
View File

@@ -2,5 +2,5 @@
path = mobile/.isar
url = https://github.com/isar/isar
[submodule "server/test/assets"]
path = server/test/assets
path = e2e/test-assets
url = https://github.com/immich-app/test-assets

14
.vscode/settings.json vendored
View File

@@ -1,6 +1,16 @@
{
"editor.formatOnSave": true,
"[javascript][typescript][css]": {
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 2,
"editor.formatOnSave": true
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 2,
"editor.formatOnSave": true
},
"[css]": {
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.tabSize": 2,
"editor.formatOnSave": true
@@ -31,4 +41,4 @@
"explorer.fileNesting.patterns": {
"*.ts": "${capture}.spec.ts,${capture}.mock.ts"
}
}
}

View File

@@ -18,6 +18,7 @@
</a>
<br/>
<p align="center">
<a href="readme_i18n/README_ca_ES.md">Català</a>
<a href="readme_i18n/README_es_ES.md">Español</a>
<a href="readme_i18n/README_fr_FR.md">Français</a>
@@ -29,6 +30,8 @@
<a href="readme_i18n/README_tr_TR.md">Türkçe</a>
<a href="readme_i18n/README_zh_CN.md">中文</a>
<a href="readme_i18n/README_ru_RU.md">Русский</a>
<a href="readme_i18n/README_pt_BR.md">Português Brasileiro</a>
<a href="readme_i18n/README_ar_JO.md">العربية</a>
</p>
## Disclaimer
@@ -47,7 +50,6 @@
- [Introduction](https://immich.app/docs/overview/introduction)
- [Installation](https://immich.app/docs/install/requirements)
- [Contribution Guidelines](https://immich.app/docs/overview/support-the-project)
- [Support The Project](#support-the-project)
## Documentation
@@ -70,6 +72,7 @@ Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
```
## Activities
![Activities](https://repobeats.axiom.co/api/embed/9e86d9dc3ddd137161f2f6d2e758d7863b1789cb.svg "Repobeats analytics image")
## Features
@@ -106,23 +109,6 @@ Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
| 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'](https://selfhosted.show/79?t=1418) 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](https://github.com/sponsors/immich-app) via GitHub Sponsors
- [One-time donation](https://github.com/sponsors/immich-app?frequency=one-time&sponsor=alextran1502) via GitHub Sponsors
- [Liberapay](https://liberapay.com/alex.tran1502/)
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
- Bitcoin: 3QVAb9dCHutquVejeNXitPqZX26Yg5kxb7
- ZCash: u1smm4wvqegcp46zss2jf5xptchgeczp4rx7a0wu3mermf2wxahm26yyz5w9mw3f2p4emwlljxjumg774kgs8rntt9yags0whnzane4n67z4c7gppq4yyvcj404ne3r769prwzd9j8ntvqp44fa6d67sf7rmcfjmds3gmeceff4u8e92rh38nd30cr96xw6vfhk6scu4ws90ldzupr3sz
## Contributors
<a href="https://github.com/alextran1502/immich/graphs/contributors">

1
cli/.nvmrc Normal file
View File

@@ -0,0 +1 @@
20.13

View File

@@ -1,4 +1,4 @@
FROM node:20-alpine3.19@sha256:ec0c413b1d84f3f7f67ec986ba885930c57b5318d2eb3abc6960ee05d4f2eb28 as core
FROM node:20-alpine3.19@sha256:291e84d956f1aff38454bbd3da38941461ad569a185c20aa289f71f37ea08e23 as core
WORKDIR /usr/src/open-api/typescript-sdk
COPY open-api/typescript-sdk/package*.json open-api/typescript-sdk/tsconfig*.json ./

680
cli/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -28,8 +28,7 @@
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^52.0.0",
"glob": "^10.3.1",
"eslint-plugin-unicorn": "^53.0.0",
"mock-fs": "^5.2.0",
"prettier": "^3.2.5",
"prettier-plugin-organize-imports": "^3.2.4",
@@ -59,6 +58,10 @@
"node": ">=20.0.0"
},
"dependencies": {
"fast-glob": "^3.3.2",
"lodash-es": "^4.17.21"
},
"volta": {
"node": "20.13.1"
}
}

View File

@@ -26,7 +26,7 @@ type Asset = { id: string; filepath: string };
interface UploadOptionsDto {
recursive?: boolean;
exclusionPatterns?: string[];
ignore?: string;
dryRun?: boolean;
skipHash?: boolean;
delete?: boolean;
@@ -75,7 +75,7 @@ const scan = async (pathsToCrawl: string[], options: UploadOptionsDto) => {
const files = await crawl({
pathsToCrawl,
recursive: options.recursive,
exclusionPatterns: options.exclusionPatterns,
exclusionPattern: options.ignore,
includeHidden: options.includeHidden,
extensions: [...image, ...video],
});
@@ -141,7 +141,7 @@ const uploadFiles = async (files: string[], { dryRun, concurrency }: UploadOptio
if (dryRun) {
console.log(`Would have uploaded ${files.length} asset${s(files.length)} (${byteSize(totalSize)})`);
return [];
return files.map((filepath) => ({ id: '', filepath }));
}
const uploadProgress = new SingleBar(
@@ -244,7 +244,7 @@ const deleteFiles = async (files: string[], options: UploadOptionsDto): Promise<
}
if (options.dryRun) {
console.log(`Would now have deleted assets, but skipped due to dry run`);
console.log(`Would have deleted ${files.length} local asset${s(files.length)}`);
return;
}
@@ -285,7 +285,7 @@ const updateAlbums = async (assets: Asset[], options: UploadOptionsDto) => {
if (dryRun) {
// TODO print asset counts for new albums
console.log(`Would have created ${newAlbums.size} new album${s(newAlbums.size)}`);
console.log(`Would have updated ${assets.length} asset${s(assets.length)}`);
console.log(`Would have updated albums of ${assets.length} asset${s(assets.length)}`);
return;
}

View File

@@ -44,7 +44,7 @@ program
.description('Upload assets')
.usage('[paths...] [options]')
.addOption(new Option('-r, --recursive', 'Recursive').env('IMMICH_RECURSIVE').default(false))
.addOption(new Option('-i, --ignore [paths...]', 'Paths to ignore').env('IMMICH_IGNORE_PATHS').default([]))
.addOption(new Option('-i, --ignore <pattern>', 'Pattern to ignore').env('IMMICH_IGNORE_PATHS'))
.addOption(new Option('-h, --skip-hash', "Don't hash files before upload").env('IMMICH_SKIP_HASH').default(false))
.addOption(new Option('-H, --include-hidden', 'Include hidden folders').env('IMMICH_INCLUDE_HIDDEN').default(false))
.addOption(
@@ -60,7 +60,8 @@ program
.addOption(
new Option('-n, --dry-run', "Don't perform any actions, just show what will be done")
.env('IMMICH_DRY_RUN')
.default(false),
.default(false)
.conflicts('skipHash'),
)
.addOption(
new Option('-c, --concurrency <number>', 'Number of assets to upload at the same time')

View File

@@ -66,7 +66,7 @@ const tests: Test[] = [
test: 'should exclude by file extension',
options: {
pathsToCrawl: ['/photos/'],
exclusionPatterns: ['**/*.tif'],
exclusionPattern: '**/*.tif',
},
files: {
'/photos/image.jpg': true,
@@ -77,7 +77,7 @@ const tests: Test[] = [
test: 'should exclude by file extension without case sensitivity',
options: {
pathsToCrawl: ['/photos/'],
exclusionPatterns: ['**/*.TIF'],
exclusionPattern: '**/*.TIF',
},
files: {
'/photos/image.jpg': true,
@@ -88,7 +88,7 @@ const tests: Test[] = [
test: 'should exclude by folder',
options: {
pathsToCrawl: ['/photos/'],
exclusionPatterns: ['**/raw/**'],
exclusionPattern: '**/raw/**',
recursive: true,
},
files: {
@@ -218,7 +218,7 @@ const tests: Test[] = [
test: 'should support ignoring full filename',
options: {
pathsToCrawl: ['/photos'],
exclusionPatterns: ['**/image2.jpg'],
exclusionPattern: '**/image2.jpg',
},
files: {
'/photos/image1.jpg': true,
@@ -230,7 +230,7 @@ const tests: Test[] = [
test: 'should support ignoring file extensions',
options: {
pathsToCrawl: ['/photos'],
exclusionPatterns: ['**/*.png'],
exclusionPattern: '**/*.png',
},
files: {
'/photos/image1.jpg': true,
@@ -243,7 +243,7 @@ const tests: Test[] = [
options: {
pathsToCrawl: ['/photos'],
recursive: true,
exclusionPatterns: ['**/raw/**'],
exclusionPattern: '**/raw/**',
},
files: {
'/photos/image1.jpg': true,
@@ -258,7 +258,7 @@ const tests: Test[] = [
options: {
pathsToCrawl: ['/'],
recursive: true,
exclusionPatterns: ['/images/**'],
exclusionPattern: '/images/**',
},
files: {
'/photos/image1.jpg': true,

View File

@@ -1,9 +1,9 @@
import { defaults, getMyUserInfo, isHttpError } from '@immich/sdk';
import { glob } from 'glob';
import { getMyUserInfo, init, isHttpError } from '@immich/sdk';
import { glob } from 'fast-glob';
import { createHash } from 'node:crypto';
import { createReadStream } from 'node:fs';
import { readFile, stat, writeFile } from 'node:fs/promises';
import { join } from 'node:path';
import { join, resolve } from 'node:path';
import yaml from 'yaml';
export interface BaseOptions {
@@ -46,8 +46,7 @@ export const connect = async (url: string, key: string) => {
// noop
}
defaults.baseUrl = url;
defaults.headers = { 'x-api-key': key };
init({ baseUrl: url, apiKey: key });
const [error] = await withError(getMyUserInfo());
if (isHttpError(error)) {
@@ -104,11 +103,11 @@ export interface CrawlOptions {
pathsToCrawl: string[];
recursive?: boolean;
includeHidden?: boolean;
exclusionPatterns?: string[];
exclusionPattern?: string;
extensions: string[];
}
export const crawl = async (options: CrawlOptions): Promise<string[]> => {
const { extensions: extensionsWithPeriod, recursive, pathsToCrawl, exclusionPatterns, includeHidden } = options;
const { extensions: extensionsWithPeriod, recursive, pathsToCrawl, exclusionPattern, includeHidden } = options;
const extensions = extensionsWithPeriod.map((extension) => extension.replace('.', ''));
if (pathsToCrawl.length === 0) {
@@ -120,11 +119,12 @@ export const crawl = async (options: CrawlOptions): Promise<string[]> => {
for await (const currentPath of pathsToCrawl) {
try {
const stats = await stat(currentPath);
const absolutePath = resolve(currentPath);
const stats = await stat(absolutePath);
if (stats.isFile() || stats.isSymbolicLink()) {
crawledFiles.push(currentPath);
crawledFiles.push(absolutePath);
} else {
patterns.push(currentPath);
patterns.push(absolutePath);
}
} catch (error: any) {
if (error.code === 'ENOENT') {
@@ -152,13 +152,13 @@ export const crawl = async (options: CrawlOptions): Promise<string[]> => {
const globbedFiles = await glob(searchPattern, {
absolute: true,
nocase: true,
nodir: true,
caseSensitiveMatch: false,
onlyFiles: true,
dot: includeHidden,
ignore: exclusionPatterns,
ignore: [`**/${exclusionPattern}`],
});
return [...crawledFiles, ...globbedFiles].sort();
globbedFiles.push(...crawledFiles);
return globbedFiles.sort();
};
export const sha1 = (filepath: string) => {

View File

@@ -4,32 +4,29 @@
name: immich-dev
x-server-build: &server-common
image: immich-server-dev:latest
build:
context: ../
dockerfile: server/Dockerfile
target: dev
restart: always
volumes:
- ../server:/usr/src/app
- ../open-api:/usr/src/open-api
- ${UPLOAD_LOCATION}/photos:/usr/src/app/upload
- ${UPLOAD_LOCATION}/photos/upload:/usr/src/app/upload/upload
- /usr/src/app/node_modules
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
ulimits:
nofile:
soft: 1048576
hard: 1048576
services:
immich-server:
container_name: immich_server
command: ['/usr/src/app/bin/immich-dev', 'immich']
<<: *server-common
command: ['/usr/src/app/bin/immich-dev']
image: immich-server-dev:latest
build:
context: ../
dockerfile: server/Dockerfile
target: dev
restart: always
volumes:
- ../server:/usr/src/app
- ../open-api:/usr/src/open-api
- ${UPLOAD_LOCATION}/photos:/usr/src/app/upload
- ${UPLOAD_LOCATION}/photos/upload:/usr/src/app/upload/upload
- /usr/src/app/node_modules
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
ulimits:
nofile:
soft: 1048576
hard: 1048576
ports:
- 3001:3001
- 9230:9230
@@ -37,19 +34,6 @@ services:
- redis
- database
immich-microservices:
container_name: immich_microservices
command: ['/usr/src/app/bin/immich-dev', 'microservices']
<<: *server-common
# extends:
# file: hwaccel.transcoding.yml
# service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
ports:
- 9231:9230
depends_on:
- database
- immich-server
immich-web:
container_name: immich_web
image: immich-web-dev:latest
@@ -97,7 +81,7 @@ services:
redis:
container_name: immich_redis
image: redis:6.2-alpine@sha256:84882e87b54734154586e5f8abd4dce69fe7311315e2fc6d67c29614c8de2672
image: redis:6.2-alpine@sha256:c0634a08e74a4bb576d02d1ee993dc05dba10e8b7b9492dfa28a7af100d46c01
database:
container_name: immich_postgres
@@ -108,11 +92,13 @@ services:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: ${DB_DATABASE_NAME}
POSTGRES_INITDB_ARGS: '--data-checksums'
volumes:
- ${UPLOAD_LOCATION}/postgres:/var/lib/postgresql/data
ports:
- 5432:5432
command: ["postgres", "-c" ,"shared_preload_libraries=vectors.so", "-c", 'search_path="$$user", public, vectors', "-c", "logging_collector=on", "-c", "max_wal_size=2GB", "-c", "shared_buffers=512MB", "-c", "wal_compression=on"]
# set IMMICH_METRICS=true in .env to enable metrics
# immich-prometheus:
# container_name: immich_prometheus

View File

@@ -1,40 +1,24 @@
name: immich-prod
x-server-build: &server-common
image: immich-server:latest
build:
context: ../
dockerfile: server/Dockerfile
volumes:
- ${UPLOAD_LOCATION}/photos:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
restart: always
services:
immich-server:
container_name: immich_server
command: ['start.sh', 'immich']
<<: *server-common
image: immich-server:latest
build:
context: ../
dockerfile: server/Dockerfile
volumes:
- ${UPLOAD_LOCATION}/photos:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
restart: always
ports:
- 2283:3001
depends_on:
- redis
- database
immich-microservices:
container_name: immich_microservices
command: ['start.sh', 'microservices']
<<: *server-common
# extends:
# file: hwaccel.transcoding.yml
# service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
depends_on:
- redis
- database
- immich-server
immich-machine-learning:
container_name: immich_machine_learning
image: immich-machine-learning:latest
@@ -54,7 +38,7 @@ services:
redis:
container_name: immich_redis
image: redis:6.2-alpine@sha256:84882e87b54734154586e5f8abd4dce69fe7311315e2fc6d67c29614c8de2672
image: redis:6.2-alpine@sha256:c0634a08e74a4bb576d02d1ee993dc05dba10e8b7b9492dfa28a7af100d46c01
restart: always
database:
@@ -66,17 +50,19 @@ services:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: ${DB_DATABASE_NAME}
POSTGRES_INITDB_ARGS: '--data-checksums'
volumes:
- ${UPLOAD_LOCATION}/postgres:/var/lib/postgresql/data
ports:
- 5432:5432
command: ["postgres", "-c" ,"shared_preload_libraries=vectors.so", "-c", 'search_path="$$user", public, vectors', "-c", "logging_collector=on", "-c", "max_wal_size=2GB", "-c", "shared_buffers=512MB", "-c", "wal_compression=on"]
# set IMMICH_METRICS=true in .env to enable metrics
immich-prometheus:
container_name: immich_prometheus
ports:
- 9090:9090
image: prom/prometheus@sha256:4f6c47e39a9064028766e8c95890ed15690c30f00c4ba14e7ce6ae1ded0295b1
image: prom/prometheus@sha256:5c435642ca4d8427ca26f4901c11114023004709037880cd7860d5b7176aa731
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus-data:/prometheus
@@ -88,7 +74,7 @@ services:
command: ['./run.sh', '-disable-reporting']
ports:
- 3000:3000
image: grafana/grafana:10.4.2-ubuntu@sha256:4f55071b556fb03f12b41423c98a185ed6695ed9ff2558e35805f0dd765fd958
image: grafana/grafana:11.0.0-ubuntu@sha256:02e99d1ee0b52dc9d3000c7b5314e7a07e0dfd69cc49bb3f8ce323491ed3406b
volumes:
- grafana-data:/var/lib/grafana

View File

@@ -12,7 +12,6 @@ services:
immich-server:
container_name: immich_server
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
command: ['start.sh', 'immich']
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
@@ -25,23 +24,6 @@ services:
- database
restart: always
immich-microservices:
container_name: immich_microservices
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
# extends: # uncomment this section for hardware acceleration - see https://immich.app/docs/features/hardware-transcoding
# file: hwaccel.transcoding.yml
# service: cpu # set to one of [nvenc, quicksync, rkmpp, vaapi, vaapi-wsl] for accelerated transcoding
command: ['start.sh', 'microservices']
volumes:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
env_file:
- .env
depends_on:
- redis
- database
restart: always
immich-machine-learning:
container_name: immich_machine_learning
# For hardware acceleration, add one of -[armnn, cuda, openvino] to the image tag.
@@ -58,19 +40,21 @@ services:
redis:
container_name: immich_redis
image: registry.hub.docker.com/library/redis:6.2-alpine@sha256:84882e87b54734154586e5f8abd4dce69fe7311315e2fc6d67c29614c8de2672
image: docker.io/redis:6.2-alpine@sha256:c0634a08e74a4bb576d02d1ee993dc05dba10e8b7b9492dfa28a7af100d46c01
restart: always
database:
container_name: immich_postgres
image: registry.hub.docker.com/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
image: docker.io/tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: ${DB_DATABASE_NAME}
POSTGRES_INITDB_ARGS: '--data-checksums'
volumes:
- ${DB_DATA_LOCATION}:/var/lib/postgresql/data
restart: always
command: ["postgres", "-c" ,"shared_preload_libraries=vectors.so", "-c", 'search_path="$$user", public, vectors', "-c", "logging_collector=on", "-c", "max_wal_size=2GB", "-c", "shared_buffers=512MB", "-c", "wal_compression=on"]
volumes:
model-cache:

View File

@@ -2,6 +2,8 @@
# The location where your uploaded files are stored
UPLOAD_LOCATION=./library
# The location where your database files are stored
DB_DATA_LOCATION=./postgres
# The Immich version to use. You can pin this to a specific version like "v1.71.0"
IMMICH_VERSION=release
@@ -11,9 +13,5 @@ DB_PASSWORD=postgres
# The values below this line do not need to be changed
###################################################################################
DB_HOSTNAME=immich_postgres
DB_USERNAME=postgres
DB_DATABASE_NAME=immich
DB_DATA_LOCATION=./postgres
REDIS_HOSTNAME=immich_redis

View File

@@ -1,9 +1,7 @@
version: "3.8"
# Configurations for hardware-accelerated machine learning
# If using Unraid or another platform that doesn't allow multiple Compose files,
# you can inline the config for a backend by copying its contents
# you can inline the config for a backend by copying its contents
# into the immich-machine-learning service in the docker-compose.yml file.
# See https://immich.app/docs/features/ml-hardware-acceleration for info on usage.
@@ -30,7 +28,7 @@ services:
openvino:
device_cgroup_rules:
- "c 189:* rmw"
- 'c 189:* rmw'
devices:
- /dev/dri:/dev/dri
volumes:

View File

@@ -1,5 +1,3 @@
version: "3.8"
# Configurations for hardware-accelerated transcoding
# If using Unraid or another platform that doesn't allow multiple Compose files,

1
docs/.nvmrc Normal file
View File

@@ -0,0 +1 @@
20.13

View File

@@ -5,13 +5,13 @@ This website is built using [Docusaurus](https://docusaurus.io/), a modern stati
### Installation
```
$ yarn
$ npm install
```
### Local Development
```
$ yarn start
$ npm run start
```
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
@@ -19,7 +19,7 @@ This command starts a local development server and opens up a browser window. Mo
### Build
```
$ yarn build
$ npm run build
```
This command generates static content into the `build` directory and can be served using any static contents hosting service.
@@ -29,13 +29,13 @@ This command generates static content into the `build` directory and can be serv
Using SSH:
```
$ USE_SSH=true yarn deploy
$ USE_SSH=true npm run deploy
```
Not using SSH:
```
$ GIT_USER=<Your GitHub username> yarn deploy
$ GIT_USER=<Your GitHub username> npm run deploy
```
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.

View File

@@ -0,0 +1,75 @@
---
title: The Immich core team goes full-time
authors: [alextran]
tags: [update, announcement, futo]
date: 2024-05-01T00:00
---
**Immich is joining [FUTO](https://futo.org/)!**
Since the beginning of this adventure, my goal has always been to create a better world for my children. Memories are priceless, and privacy should not be a luxury. However, building quality open source has its challenges. Over the past two years, it has taken significant dedication, time, and effort.
Recently, a company in Austin, Texas, called FUTO contacted the team. FUTO strives to develop quality and sustainable open software. They build software alternatives that focus on giving control to users. From their mission statement:
“Computers should belong to you, the people. We develop and fund technology to give them back.”
FUTO loved Immich and wanted to see if wed consider working with them to take the project to the next level. In short, FUTO offered to:
- Pay the core team to work on Immich full-time
- Let us keep full autonomy about the projects direction and leadership
- Continue to license Immich under AGPL
- Keep Immichs development direction with no paywalled features
- Keep Immich “built for the people” (no ads, data mining/selling, or alternative motives)
- Provide us with financial, technical, legal, and administrative support
After careful deliberation, the team decided that FUTOs vision closely aligns with our own: to build a better future by providing a polished, performant, and privacy-preserving open-source software solution for photo and video management delivered in a sustainable way.
Immichs future has never looked brighter, and we look forward to realizing our vision for Immich as part of FUTO.
If you have more questions, well host a Q&A live stream on May 9th at 3PM UTC (10AM CST). [You can ask questions here](https://www.live-ask.com/event/01HWP2SB99A1K8EXFBDKZ5Z9CF), and the stream will be live [here on our YouTube channel](https://youtube.com/live/cwz2iZwYpgg).
Cheers,
The Immich Team
---
## FAQs
### What is FUTO?
[https://futo.org/what-is-futo/](https://futo.org/what-is-futo/)
### Will the license change?
No. Immich will continue to be licensed under AGPL without a CLA.
### Will Immich continue to be free?
Yes. The Immich source code will remain freely available under the AGPL license.
### Is Immich getting VC funding?
No. Venture capital implies investment in a business, often with the expectation of a future payout (exit plan). Immich is neither a business that can be acquired nor comes with a money-making exit plan.
### I am currently supporting Immich through GitHub sponsors. What will happen to my donation?
Effective immediately, all donations to the Immich organization will be canceled. In the future, we will offer an optional, modest payment option instead. Thank you to everyone who donated to help us get this far!
### How is funding sustainable?
Immich and FUTO believe a sustainable future requires a model that does not rely on users-as-a-product. To this end, FUTO advocates that users pay for good, open software. In keeping with this model, we will adopt a purchase price. This means we no longer accept donations, but — _without limiting features for those who do not pay_ — we will soon allow you to purchase Immich through a modest payment. We encourage you to pay for the high-quality software you use to foster a healthy software culture where developers build great applications without hidden motives for their users.
### When does this change take effect?
This change takes effect immediately.
### What will change?
The following things will change as Immich joins FUTO:
- The brand, logo, and other Immich trademarks will be transferred to FUTO.
- We will stop all donations to the project.
- The core team can now dedicate our full attention to Immich
- Before the end of the year, we plan to have a roadmap for what it will take to get Immich to a stable release.
- Bugs will be squashed, and features will be delivered faster.

View File

@@ -36,6 +36,22 @@ If you still cannot log in to the app, try the following:
- Check the mobile logs
- Make sure login credentials are correct by logging in on the web app
### Why does foreground backup stop when I navigate away from the app? Shouldn't it transfer the job to background backup?
Foreground backup and background backup are two separate mechanisms. They don't communicate or interact with each other.
Foreground backup is controlled by the user's action, while background backup is controlled by your device's operating system. When the app is put in the background, the invocation of background tasks is delegated to the device's operating system scheduler. It decides when the background task can run and how long it can run.
The behaviors differ based on your device manufacturer and operating system, but most are related to battery-saving policies.
### Why is background backup on iOS not working?
On iOS (iPhone and iPad), the operating system determines if a particular app can invoke background tasks based on multiple factors, most of which the Immich app has no control over. To increase the likelihood that the background backup task is run, follow the steps below:
- Enable Background App Refresh for Immich in the iOS settings at `Settings > General > Background App Refresh`.
- Disable Background App Refresh for apps that don't need background tasks to run. This will reduce the competition for background task invocation for Immich.
- Use the Immich app more often.
---
## Assets
@@ -117,7 +133,7 @@ For example, say you have existing transcodes with the policy "Videos higher tha
No. Our design principle is that the original assets should always be untouched.
### How can I move all data (photos, persons, albums) from one user to another?
### How can I move all data (photos, persons, albums, libraries) from one user to another?
This is not officially supported but can be accomplished with some database updates. You can do this on the command line (in the PostgreSQL container using the `psql` command), or you can add, for example, an [Adminer](https://www.adminer.org/) container to the `docker-compose.yml` file so that you can use a web interface.
@@ -128,18 +144,23 @@ This is not officially supported but can be accomplished with some database upda
2. Find the ID of both the 'source' and the 'destination' user (it's the id column in the `users` table)
3. Three tables need to be updated:
3. Four tables need to be updated:
```sql
// Reassign albums
BEGIN;
-- reassign albums
UPDATE albums SET "ownerId" = '<destinationId>' WHERE "ownerId" = '<sourceId>';
// Reassign people
-- reassign people
UPDATE person SET "ownerId" = '<destinationId>' WHERE "ownerId" = '<sourceId>';
// reassign assets
-- reassign assets
UPDATE assets SET "ownerId" = '<destinationId>' WHERE "ownerId" = '<sourceId>'
AND CHECKSUM NOT IN (SELECT CHECKSUM FROM assets WHERE "ownerId" = '<destinationId>');
-- reassign external libraries
UPDATE libraries SET "ownerId" = '<destinationId>' WHERE "ownerId" = '<sourceId>';
COMMIT;
```
4. There might be left-over assets in the 'source' user's library if they are skipped by the last query because of duplicate checksums. These are probably duplicates anyway, and can probably be removed.
@@ -227,14 +248,19 @@ to increase the bar for what the algorithm considers a "core face" for that pers
### The immich_model-cache volume takes up a lot of space, what could be the problem?
If you installed several models and chose not to use some of them, it might be worth deleting the old models that are in immich_model-cache.
If you installed several models and chose not to use some of them, it might be worth deleting the old models that are in immich_model-cache. To do this you can mount the model cache and remove the undesired models.
To do this you can run:
<details>
<summary>Steps</summary>
- `docker run -it --rm -v immich_model-cache:/mnt ubuntu bash`
- `cd mnt`
- `ls`
- and delete unused models with `rm -r <model_name>`.
```bash
docker run -it --rm -v immich_model-cache:/mnt-models alpine sh
cd /mnt-models
ls clip/ facial-recognition/
# rm -r clip/ABC facial-recognition/DEF # delete unused models
```
</details>
---
@@ -258,6 +284,9 @@ The initial backup is the most intensive due to the number of jobs running. The
By default, a container has no resource constraints and can use as much of a given resource as the host's kernel scheduler allows. To limit this, you can add the following to the `docker-compose.yml` block of any containers that you want to have limited resources.
<details>
<summary>docker-compose.yml</summary>
```yaml
deploy:
resources:
@@ -268,6 +297,7 @@ deploy:
memory: '1G'
```
</details>
For more details, you can look at the [original docker docs](https://docs.docker.com/config/containers/resource_constraints/) or use this [guide](https://www.baeldung.com/ops/docker-memory-limit).
### How can I boost machine learning speed?
@@ -316,6 +346,9 @@ The non-root user/group needs read/write access to the volume mounts, including
For a further hardened system, you can add the following block to every container except for `immich_postgres`.
<details>
<summary>docker-compose.yml</summary>
```yaml
security_opt:
# Prevent escalation of privileges after the container is started
@@ -325,15 +358,15 @@ cap_drop:
- NET_RAW
```
### How can I **purge** data from Immich?
</details>
### How can I purge data from Immich?
Data for Immich comes in two forms:
1. **Metadata** stored in a Postgres database, persisted via the `pg_data` volume
1. **Metadata** stored in a Postgres database, stored in the `DB_DATA_LOCATION` folder (previously `pg_data` Docker volume).
2. **Files** (originals, thumbs, profile, etc.), stored in the `UPLOAD_LOCATION` folder, more [info](/docs/administration/backup-and-restore#asset-types-and-storage-locations).
To remove the **Metadata** you can stop Immich and delete the volume.
:::warning
This will destroy your database and reset your instance, meaning that you start from scratch.
:::
@@ -342,13 +375,16 @@ This will destroy your database and reset your instance, meaning that you start
docker compose down -v
```
After removing the containers and volumes, there are a few directories that need to be deleted to reset Immich to a new installation. Once they are deleted, Immich can be started back up and will be a fresh installation.
- `DB_DATA_LOCATION` contains the database, media info, and settings.
- `UPLOAD_LOCATION` contains all the media uploaded to Immich.
:::note Portainer
If you use portainer, bring down the stack in portainer. Go into the volumes section
and remove all the volumes related to immich then restart the stack.
:::
After removing the containers and volumes, the **Files** should be removed from the `UPLOAD_LOCATION` to provide a clean start.
### Why does the machine learning service report workers crashing?
:::note
@@ -363,8 +399,47 @@ If it mentions SIGILL (note the lack of a K) or error code 132, it most likely m
If your version of Immich is below 1.92.0 and the crash occurs after logs about tracing or exporting a model, consider either upgrading or disabling the Tag Objects job.
### Why does Immich log migration errors on startup?
## Database
Sometimes Immich logs errors such as "duplicate key value violates unique constraint" or "column (...) of relation (...) already exists". Because of Immich's container structure, this error can be seen when both immich and immich-microservices start at the same time and attempt to migrate or create the database structure. Since the database migration is run sequentially and inside of transactions, this error message does not cause harm to your installation of Immich and can safely be ignored. If needed, you can manually restart Immich by running `docker restart immich immich-microservices`.
### Why am I getting database ownership errors?
If you get database errors such as `FATAL: data directory "/var/lib/postgresql/data" has wrong ownership` upon database startup, this is likely due to an issue with your filesystem.
NTFS and ex/FAT/32 filesystems are not supported. See [here](/docs/install/environment-variables#supported-filesystems) for more details.
### How can I verify the integrity of my database?
If you installed Immich using v1.104.0 or later, you likely have database checksums enabled by default. You can check this by running the following command.
A result of `on` means that checksums are enabled.
<details>
<summary>Check if checksums are enabled</summary>
```bash
docker exec -it immich_postgres psql --dbname=immich --username=<DB_USERNAME> --command="show data_checksums"
data_checksums
----------------
on
(1 row)
```
</details>
If checksums are enabled, you can check the status of the database with the following command. A normal result is all zeroes.
<details>
<summary>Check for database corruption</summary>
```bash
docker exec -it immich_postgres psql --dbname=immich --username=<DB_USERNAME> --command="SELECT datname, checksum_failures, checksum_last_failure FROM pg_stat_database WHERE datname IS NOT NULL"
datname | checksum_failures | checksum_last_failure
-----------+-------------------+-----------------------
postgres | 0 |
immich | 0 |
template1 | 0 |
template0 | 0 |
(4 rows)
```
</details>
[huggingface]: https://huggingface.co/immich-app

View File

@@ -15,10 +15,14 @@ Immich saves [file paths in the database](https://github.com/immich-app/immich/d
Refer to the official [postgres documentation](https://www.postgresql.org/docs/current/backup.html) for details about backing up and restoring a postgres database.
:::
The recommended way to backup and restore the Immich database is to use the `pg_dumpall` command.
The recommended way to backup and restore the Immich database is to use the `pg_dumpall` command. When restoring, you need to delete the `DB_DATA_LOCATION` folder (if it exists) to reset the database.
:::caution
It is not recommended to directly backup the `DB_DATA_LOCATION` folder. Doing so while the database is running can lead to a corrupted backup that cannot be restored.
:::
<Tabs>
<TabItem value="Linux system based Backup" label="Linux system based Backup" default>
<TabItem value="Linux system" label="Linux system" default>
```bash title='Backup'
docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=postgres | gzip > "/path/to/backup/dump.sql.gz"
@@ -26,16 +30,19 @@ docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=postgre
```bash title='Restore'
docker compose down -v # CAUTION! Deletes all Immich data to start from scratch.
# rm -rf DB_DATA_LOCATION # CAUTION! Deletes all Immich data to start from scratch.
docker compose pull # Update to latest version of Immich (if desired)
docker compose create # Create Docker containers for Immich apps without running them.
docker start immich_postgres # Start Postgres server
sleep 10 # Wait for Postgres server to start up
gunzip < "/path/to/backup/dump.sql.gz" | docker exec -i immich_postgres psql --username=postgres # Restore Backup
gunzip < "/path/to/backup/dump.sql.gz" \
| sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" \
| docker exec -i immich_postgres psql --username=postgres # Restore Backup
docker compose up -d # Start remainder of Immich apps
```
</TabItem>
<TabItem value="Windows system based Backup" label="Windows system based Backup">
<TabItem value="Windows system (PowerShell)" label="Windows system (PowerShell)">
```powershell title='Backup'
docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=postgres > "\path\to\backup\dump.sql"
@@ -43,11 +50,12 @@ docker exec -t immich_postgres pg_dumpall --clean --if-exists --username=postgre
```powershell title='Restore'
docker compose down -v # CAUTION! Deletes all Immich data to start from scratch.
# Remove-Item -Recurse -Force DB_DATA_LOCATION # CAUTION! Deletes all Immich data to start from scratch.
docker compose pull # Update to latest version of Immich (if desired)
docker compose create # Create Docker containers for Immich apps without running them.
docker start immich_postgres # Start Postgres server
sleep 10 # Wait for Postgres server to start up
gc "C:\path\to\backup\dump.sql" | docker exec -i immich_postgres psql --username=postgres # Restore Backup
gc "C:\path\to\backup\dump.sql" | docker exec -i immich_postgres psql --username=postgres # Restore Backup
docker compose up -d # Start remainder of Immich apps
```
@@ -56,6 +64,10 @@ docker compose up -d # Start remainder of Immich apps
Note that for the database restore to proceed properly, it requires a completely fresh install (i.e. the Immich server has never run since creating the Docker containers). If the Immich app has run, Postgres conflicts may be encountered upon database restoration (relation already exists, violated foreign key constraints, multiple primary keys, etc.).
:::tip
Some deployment methods make it difficult to start the database without also starting the server or microservices. In these cases, you may set the environmental variable `DB_SKIP_MIGRATIONS=true` before starting the services. This will prevent the server from running migrations that interfere with the restore process. Note that both the server and microservices must have this variable set to prevent the migrations from running. Be sure to remove this variable and restart the services after the database is restored.
:::
The database dumps can also be automated (using [this image](https://github.com/prodrigestivill/docker-postgres-backup-local)) by editing the docker compose file to match the following:
```yaml
@@ -71,6 +83,7 @@ services:
POSTGRES_CLUSTER: 'TRUE'
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_DATABASE_NAME}
SCHEDULE: "@daily"
POSTGRES_EXTRA_OPTS: '--clean --if-exists'
BACKUP_DIR: /db_dumps
@@ -83,7 +96,9 @@ services:
Then you can restore with the same command but pointed at the latest dump.
```bash title='Automated Restore'
gunzip < db_dumps/last/immich-latest.sql.gz | docker exec -i immich_postgres psql --username=postgres
gunzip < db_dumps/last/immich-latest.sql.gz \
| sed "s/SELECT pg_catalog.set_config('search_path', '', false);/SELECT pg_catalog.set_config('search_path', 'public, pg_catalog', true);/g" \
| docker exec -i immich_postgres psql --username=postgres
```
:::note
@@ -94,12 +109,16 @@ Example: `gunzip < "/path/to/backup/dump.sql.gz" | sed "s/SELECT pg_catalog.set_
## Filesystem
Immich stores two types of content in the filesystem: (1) original, unmodified content, and (2) generated content. Only the original content needs to be backed-up, which includes the following folders:
Immich stores two types of content in the filesystem: (1) original, unmodified assets (photos and videos), and (2) generated content. Only the original content needs to be backed-up, which is stored in the following folders:
1. `UPLOAD_LOCATION/library`
2. `UPLOAD_LOCATION/upload`
3. `UPLOAD_LOCATION/profile`
:::caution
If you moved some of these folders onto a different storage device, such as `profile/`, make sure to adjust the backup path to match your setup
:::
### Asset Types and Storage Locations
Some storage locations are impacted by the Storage Template. See below for more details.
@@ -108,7 +127,8 @@ Some storage locations are impacted by the Storage Template. See below for more
<TabItem value="Storage Template Off (Default)." label="Storage Template Off (Default)." default>
:::note
`UPLOAD_LOCATION/library` folder is not used by default on new machines running version 1.92.0. These are if the system administrator activated the storage template engine, for [more info](https://github.com/immich-app/immich/releases/tag/v1.92.0#:~:text=the%20partner%E2%80%99s%20assets.-,Hardening%20storage%20template).
The `UPLOAD_LOCATION/library` folder is not used by default on new machines running version 1.92.0. It is used only if the system administrator activated the storage template engine,
for more info read the [release notes](https://github.com/immich-app/immich/releases/tag/v1.92.0#:~:text=the%20partner%E2%80%99s%20assets.-,Hardening%20storage%20template).
:::
**1. User-Specific Folders:**
@@ -120,16 +140,16 @@ Some storage locations are impacted by the Storage Template. See below for more
- **Source Assets:**
- Original assets uploaded through the browser interface & mobile & CLI.
- Stored in `/library/upload/<userID>`.
- Stored in `UPLOAD_LOCATION/upload/<userID>`.
- **Avatar Images:**
- User profile images.
- Stored in `/library/profile/<userID>`.
- Stored in `UPLOAD_LOCATION/profile/<userID>`.
- **Thumbs Images:**
- Preview images (blurred, small, large) for each asset and thumbnails for recognized faces.
- Stored in `/library/thumbs/<userID>`.
- Preview images (small thumbnails and large previews) for each asset and thumbnails for recognized faces.
- Stored in `UPLOAD_LOCATION/thumbs/<userID>`.
- **Encoded Assets:**
- By default, unless otherwise specified re-encoded video assets for wider compatibility.
- Stored in `/library/encoded-video/<userID>`.
- Videos that have been re-encoded from the original for wider compatibility. The original is not removed.
- Stored in `UPLOAD_LOCATION/encoded-video/<userID>`.
</TabItem>
<TabItem value="Storage Template On" label="Storage Template On">
@@ -137,34 +157,34 @@ Some storage locations are impacted by the Storage Template. See below for more
:::note
If you choose to activate the storage template engine, it will move all assets to `UPLOAD_LOCATION/library/<userID>`.
When you turn off the storage template engine, it will leave the assets in `UPLOAD_LOCATION/library/<userID>` and will not return them to `/library/upload`.
**New assets** will be saved to `/library/upload`.
When you turn off the storage template engine, it will leave the assets in `UPLOAD_LOCATION/library/<userID>` and will not return them to `UPLOAD_LOCATION/upload`.
**New assets** will be saved to `UPLOAD_LOCATION/upload`.
:::
**1. User-Specific Folders:**
- Each user has a unique string representing them.
- The main user is "Admin" (but only for `UPLOAD_LOCATION/library`)
- Other users have different string identifiers.
- You can find your user ID in Account Account Settings -> Account -> User ID.
- The administrator can set a Storage Label for a user, which will be used instead of `<userID>` for the `library/` folder.
- The Admin has a default storage label of `admin`.
- You can find your user ID and Storage Label in Account Account Settings -> Account -> User ID.
**2. Asset Types and Storage Locations:**
- **Source Assets:**
- Original assets uploaded through the browser interface & mobile & CLI.
- Original assets uploaded through the browser interface, mobile, and CLI.
- Stored in `UPLOAD_LOCATION/library/<userID>`.
- **Avatar Images:**
- User profile images.
- Stored in `/library/profile/<userID>`.
- Stored in `UPLOAD_LOCATION/profile/<userID>`.
- **Thumbs Images:**
- Preview images (blurred, small, large) for each asset and thumbnails for recognized faces.
- Stored in `/library/thumbs/<userID>`.
- Stored in `UPLOCAD_LOCATION/thumbs/<userID>`.
- **Encoded Assets:**
- By default, unless otherwise specified re-encoded video assets for wider compatibility .
- Stored in `/library/encoded-video/<userID>`.
- Videos that have been re-encoded from the original for wider compatibility. The original is not removed.
- Stored in `UPLOAD_LOCATION/encoded-video/<userID>`.
- **Files in Upload Queue (Mobile):**
- Files uploaded through mobile apps.
- Temporarily located in `/library/upload/<userID>`.
- Temporarily located in `UPLOAD_LOCATION/upload/<userID>`.
- Transferred to `UPLOAD_LOCATION/library/<userID>` upon successful upload.
</TabItem>

View File

@@ -5,7 +5,7 @@ While not officially recommended, it is possible to run Immich using a pre-exist
By default, Immich expects superuser permission on the Postgres database and requires certain extensions to be installed. This guide outlines the steps required to prepare a pre-existing Postgres server to be used by Immich.
:::tip
Running with a pre-existing Postgres server can unlock powerful administrative features, including logical replication, data page checksums, and streaming write-ahead log backups using programs like pgBackRest or Barman.
Running with a pre-existing Postgres server can unlock powerful administrative features, including logical replication and streaming write-ahead log backups using programs like pgBackRest or Barman.
:::
## Prerequisites
@@ -32,10 +32,6 @@ DB_URL='postgresql://immichdbusername:immichdbpassword@postgreshost:postgresport
# DB_URL='postgresql://immichdbusername:immichdbpassword@postgreshost:postgresport/immichdatabasename?sslmode=require&sslmode=no-verify'
```
:::info
When `DB_URL` is defined, the other database (`DB_*`) variables are ignored, with the exception of `DB_VECTOR_EXTENSION`.
:::
## With superuser permission
Typically Immich expects superuser permission in the database, which you can grant by running `ALTER USER <immichdbusername> WITH SUPERUSER;` at the `psql` console. If you prefer not to grant superuser permissions, follow the instructions in the next section.

View File

@@ -18,7 +18,7 @@ In any other situation, there are 3 different options that can appear:
- MATCHES - These files are matched by their checksums.
- OFFLINE PATHS - These files are the result of manually deleting files in the upload library or a failed file move in the past (losing track of a file).
- OFFLINE PATHS - These files are the result of manually deleting files from immich or a failed file move in the past (losing track of a file).
- UNTRACKED FILES - These files are not tracked by the application. They can be the result of failed moves, interrupted uploads, or left behind due to a bug.

View File

@@ -9,6 +9,11 @@ When contributing code through a pull request, please check the following:
- [ ] `npm run check:svelte` (Type checking via SvelteKit)
- [ ] `npm test` (unit tests)
## Documentation
- [ ] `npm run format` (formatting via Prettier)
- [ ] Update the `_redirects` file if you have renamed a page or removed it from the documentation.
:::tip AIO
Run all web checks with `npm run check:all`
:::

View File

@@ -44,11 +44,11 @@ Please modify the `IMMICH_INSTANCE_URL` and `IMMICH_API_KEY` environment variabl
## Usage
```
immich
```
<details>
<summary>Usage</summary>
```
$ immich
Usage: immich [options] [command]
Command line interface for Immich
@@ -69,44 +69,46 @@ Commands:
help [command] display help for command
```
</details>
## Commands
The upload command supports the following options:
<details>
<summary>Options</summary>
```
Usage: immich upload [paths...] [options]
Upload assets
Arguments:
paths One or more paths to assets to be uploaded
paths One or more paths to assets to be uploaded
Options:
-r, --recursive Recursive (default: false, env: IMMICH_RECURSIVE)
-i, --ignore [paths...] Paths to ignore (default: [], env: IMMICH_IGNORE_PATHS)
-h, --skip-hash Don't hash files before upload (default: false, env: IMMICH_SKIP_HASH)
-H, --include-hidden Include hidden folders (default: false, env: IMMICH_INCLUDE_HIDDEN)
-a, --album Automatically create albums based on folder name (default: false, env: IMMICH_AUTO_CREATE_ALBUM)
-A, --album-name <name> Add all assets to specified album (env: IMMICH_ALBUM_NAME)
-n, --dry-run Don't perform any actions, just show what will be done (default: false, env: IMMICH_DRY_RUN)
-c, --concurrency <number> Number of assets to upload at the same time (default: 4, env: IMMICH_UPLOAD_CONCURRENCY)
--delete Delete local assets after upload (env: IMMICH_DELETE_ASSETS)
--help display help for command
-r, --recursive Recursive (default: false, env: IMMICH_RECURSIVE)
-i, --ignore [paths...] Paths to ignore (default: [], env: IMMICH_IGNORE_PATHS)
-h, --skip-hash Don't hash files before upload (default: false, env: IMMICH_SKIP_HASH)
-H, --include-hidden Include hidden folders (default: false, env: IMMICH_INCLUDE_HIDDEN)
-a, --album Automatically create albums based on folder name (default: false, env: IMMICH_AUTO_CREATE_ALBUM)
-A, --album-name <name> Add all assets to specified album (env: IMMICH_ALBUM_NAME)
-n, --dry-run Don't perform any actions, just show what will be done (default: false, env: IMMICH_DRY_RUN)
-c, --concurrency <number> Number of assets to upload at the same time (default: 4, env: IMMICH_UPLOAD_CONCURRENCY)
--delete Delete local assets after upload (env: IMMICH_DELETE_ASSETS)
--help display help for command
```
</details>
Note that the above options can read from environment variables as well.
## Quick Start
You begin by authenticating to your Immich server.
```bash
immich login [url] [key]
```
For instance,
You begin by authenticating to your Immich server. For instance:
```bash
# immich login [url] [key]
immich login http://192.168.1.216:2283/api HFEJ38DNSDUEG
```

View File

@@ -22,7 +22,8 @@ You do not need to redo any transcoding jobs after enabling hardware acceleratio
- WSL2 does not support Quick Sync.
- Raspberry Pi is currently not supported.
- Two-pass mode is only supported for NVENC. Other APIs will ignore this setting.
- Only encoding is currently hardware accelerated, so the CPU is still used for software decoding and tone-mapping.
- By default, only encoding is currently hardware accelerated. This means the CPU is still used for software decoding and tone-mapping.
- NVENC and RKMPP can be fully accelerated by enabling hardware decoding in the video transcoding settings.
- Hardware dependent
- Codec support varies, but H.264 and HEVC are usually supported.
- Notably, NVIDIA and AMD GPUs do not support VP9 encoding.
@@ -33,7 +34,7 @@ You do not need to redo any transcoding jobs after enabling hardware acceleratio
#### NVENC
- You must have the official NVIDIA driver installed on the server.
- On Linux (except for WSL2), you also need to have [NVIDIA Container Runtime][nvcr] installed.
- On Linux (except for WSL2), you also need to have [NVIDIA Container Toolkit][nvct] installed.
#### QSV
@@ -65,6 +66,7 @@ For RKMPP to work:
3. Redeploy the `immich-microservices` container with these updated settings.
4. In the Admin page under `Video transcoding settings`, change the hardware acceleration setting to the appropriate option and save.
5. (Optional) If using a compatible backend, you may enable hardware decoding for optimal performance.
#### Single Compose File
@@ -102,7 +104,14 @@ Once this is done, you can continue to step 3 of "Basic Setup".
#### All-In-One - Unraid Setup
##### NVENC - NVIDIA GPUs
##### QSV
1. Unraid > Docker > (Stop) Immich container > Edit
2. Scroll down and select `Add another Path, Port, Variable, Label or Device`
3. In the drop-down menu, select `Device` and an entry with any name and the value `/dev/dri`.
4. Continue to step 4 of "Basic Setup".
##### NVENC
1. In the container app, add this environmental variable: Key=`NVIDIA_VISIBLE_DEVICES` Value=`all`
2. While still in the container app, change the container from Basic Mode to Advanced Mode and add the following parameter to the Extra Parameters field: `--runtime=nvidia`
@@ -115,7 +124,7 @@ Once this is done, you can continue to step 3 of "Basic Setup".
- While you can use VAAPI with NVIDIA and Intel devices, prefer the more specific APIs since they're more optimized for their respective devices
[hw-file]: https://github.com/immich-app/immich/releases/latest/download/hwaccel.transcoding.yml
[nvcr]: https://github.com/NVIDIA/nvidia-container-runtime/
[nvct]: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
[jellyfin-lp]: https://jellyfin.org/docs/general/administration/hardware-acceleration/intel/#configure-and-verify-lp-mode-on-linux
[jellyfin-kernel-bug]: https://jellyfin.org/docs/general/administration/hardware-acceleration/intel/#known-issues-and-limitations
[libmali-rockchip]: https://github.com/tsukumijima/libmali-rockchip/releases

View File

@@ -4,13 +4,9 @@
Immich supports the creation of libraries which is a top-level asset container. Currently, there are two types of libraries: traditional upload libraries that can sync with a mobile device, and external libraries, that keeps up to date with files on disk. Libraries are different from albums in that an asset can belong to multiple albums but only one library, and deleting a library deletes all assets contained within. As of August 2023, this is a new feature and libraries have a lot of potential for future development beyond what is documented here. This document attempts to describe the current state of libraries.
## The Upload Library
Immich comes preconfigured with an upload library for each user. All assets uploaded to Immich are added to this library. This library can be renamed, but not deleted. The upload library is the only library that can be synced with a mobile device. No items in an upload library is allowed to have the same sha1 hash as another item in the same library in order to prevent duplicates.
## External Libraries
External libraries tracks assets stored outside of immich, i.e. in the file system. Immich will only read data from the files, and will not modify them in any way. Therefore, the delete button is disabled for external assets. When the external library is scanned, immich will read the metadata from the file and create an asset in the library for each image or video file. These items will then be shown in the main timeline, and they will look and behave like any other asset, including viewing on the map, adding to albums, etc.
External libraries tracks assets stored outside of Immich, i.e. in the file system. When the external library is scanned, Immich will read the metadata from the file and create an asset in the library for each image or video file. These items will then be shown in the main timeline, and they will look and behave like any other asset, including viewing on the map, adding to albums, etc.
If a file is modified outside of Immich, the changes will not be reflected in immich until the library is scanned again. There are different ways to scan a library depending on the use case:

View File

@@ -10,15 +10,14 @@ You do not need to redo any machine learning jobs after enabling hardware accele
## Supported Backends
- ARM NN (Mali)
- CUDA (NVIDIA) Note: It is supported with [compute capability](https://developer.nvidia.com/cuda-gpus) 5.2 or higher
- OpenVINO (Intel)
- CUDA (NVIDIA GPUs with [compute capability](https://developer.nvidia.com/cuda-gpus) 5.2 or higher)
- OpenVINO (Intel discrete GPUs such as Iris Xe and Arc)
## Limitations
- The instructions and configurations here are specific to Docker Compose. Other container engines may require different configuration.
- Only Linux and Windows (through WSL2) servers are supported.
- ARM NN is only supported on devices with Mali GPUs. Other Arm devices are not supported.
- There is currently an upstream issue with OpenVINO, so whether it will work is device-dependent.
- Some models may not be compatible with certain backends. CUDA is the most reliable.
## Prerequisites
@@ -36,8 +35,15 @@ You do not need to redo any machine learning jobs after enabling hardware accele
#### CUDA
- You must have the official NVIDIA driver installed on the server.
- On Linux (except for WSL2), you also need to have [NVIDIA Container Runtime][nvcr] installed.
- The GPU must have compute capability 5.2 or greater.
- The server must have the official NVIDIA driver installed.
- The installed driver must be >= 535 (it must support CUDA 12.2).
- On Linux (except for WSL2), you also need to have [NVIDIA Container Toolkit][nvct] installed.
#### OpenVINO
- The server must have a discrete GPU, i.e. Iris Xe or Arc. Expect issues when attempting to use integrated graphics.
- Ensure the server's kernel version is new enough to use the device for hardware accceleration.
## Setup
@@ -89,11 +95,11 @@ immich-machine-learning:
Once this is done, you can redeploy the `immich-machine-learning` container.
:::info
You can confirm the device is being recognized and used by checking its utilization (via `nvtop` for CUDA, `intel_gpu_top` for OpenVINO, etc.). You can also enable debug logging by setting `LOG_LEVEL=debug` in the `.env` file and restarting the `immich-machine-learning` container. When a Smart Search or Face Detection job begins, you should see a log for `Available ORT providers` containing the relevant provider. In the case of ARM NN, the absence of a `Could not load ANN shared libraries` log entry means it loaded successfully.
You can confirm the device is being recognized and used by checking its utilization (via `nvtop` for CUDA, `intel_gpu_top` for OpenVINO, etc.). You can also enable debug logging by setting `IMMICH_LOG_LEVEL=debug` in the `.env` file and restarting the `immich-machine-learning` container. When a Smart Search or Face Detection job begins, you should see a log for `Available ORT providers` containing the relevant provider. In the case of ARM NN, the absence of a `Could not load ANN shared libraries` log entry means it loaded successfully.
:::
[hw-file]: https://github.com/immich-app/immich/releases/latest/download/hwaccel.ml.yml
[nvcr]: https://github.com/NVIDIA/nvidia-container-runtime/
[nvct]: https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
## Tips

View File

@@ -0,0 +1,252 @@
# Shared Albums & Assets
## Shared Albums
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
Album sharing allows you to share assets with other users or with people from around the world via a link or invitation (for system users).
When sharing shared albums, whats shared is:
- The selected assets.
- Metadata of the assets (Can be removed by sharing via link).
## Shared Album Features
- Download all assets as zip file (Web only).
:::info Archive size limited.
If the size of the album exceeds 4GB, the archive files will be divided into 4GB each.
:::
- Add a description to the album (Web only).
- Slideshow view (Web only).
- Add or remove photos to an album.
- Comment on the album (for system users only).
:::info
When you create an album and it is not shared with anyone, it will be added to the Albums category.
:::
<Tabs>
<TabItem value="Computer" label="Computer" default>
### Create a Shared Album
1. Select the assets (Shift can be used for multiple selection).
2. Click on the + on the top right -> add to a shared album.
3. Name the new album and add the album.
## Sharing Between Users
### Shared Album Options
- Add or remove users from the album.
:::info remove user(s)
When a user is removed from the album, the photos he uploaded will still appear in the album.
:::
- Enable or disable comments & likes.
- Replace the album cover.
- Display order (newest first / oldest first).
To change these settings click on the 3 dots on the right -> options.
:::info Known bug
Currently it is not possible to remove people through the options.
This is a [known problem and it has a temporary solution](https://github.com/immich-app/immich/issues/7954)
:::
## Share Album via Link
:::info
When wanting to share with people outside the home network via a link, Immich needs to be exposed to the world wide web, read more to [learn the ways to do this](/docs/guides/remote-access.md).
:::
1. Enter the shared album.
2. Click on the share icon.
3. Click on Create link.
You can edit the link properties, options include;
- **Description -** adding a description to the album (optional)
- **Password -** adding a password to the album (optional), it is required to activate Require password.
- **Show metadata -** whether to show metadata to whoever the link will be shared with (optional).
- **Allow public user to download -** whether to allow whoever has the link to download all the images or a certain image (optional).
- **Allow public user to upload -** whether to allow whoever has the link to upload assets to the album (optional).
:::info
whoever has the link and have uploaded files cannot delete them.
:::
- **Expire after -** adding an expiration date to the link (optional).
## Share Specific Assets
A user can share specific assets without linking them to a specific album.
in order to do so;
1. Go to the timeline
2. Select the assets (Shift can be used for multiple selection)
3. Click the share button
:::info
Assets shared in this way will not be displayed in the Sharing category, in order to expect to remove or change the link settings of assets shared in this way, you must use the Manage generated links option.
:::
:::tip
For temporary sharing, you can add an expiration date to assets shared this way.
:::
## Manage generated links
A user can copy, delete and change the link settings he created for specific albums or assets, in order to do so;
1. Go to the Immich home page.
2. Select the Sharing category.
3. On the top right, select Shared links.
:::info remove links/users.
When making a shared album private, the added photos will **still** be saved in the album.
:::
## Activity
:::info
Activity is not visible when sharing an album via external link.
New users added to the album will be able to see previous activity.
:::
### Add a Comment or Like to the Album
1. Enter the shared album.
2. From the bottom right you can add comment(s) or delete old comments.
3. To add a like (heart) to the album, click the heart button to the left of the "say something" button.
#### Add a Comment or Like to a Specific Photo
:::info Activity
Activity of a comment or heart on a specific photo will appear in the general activity of the album.
:::
1. Enter the shared album.
2. Enter the picture.
3. From the bottom right you can add comment(s) or delete old comments.
4. To add a like (heart) to the album, click the heart button to the left of the "say something" button.
### Viewing Activity in the Album
To view album activity on comments or likes
1. Enter the shared album
2. On the bottom right click on the message icon
</TabItem>
<TabItem value="Mobile" label="Mobile">
:::note mobile app
Some of the features are not available on mobile, to understand what the full features of shared albums are, it is recommended to additionally read the explanation for the computer version.
:::
### Create a Shared Album
1. Select the assets.
2. Swipe up and click on Create new album.
3. Name the new album and add the album.
## Sharing Between Users
#### Add or remove users from the album.
:::info remove user(s)
When a user is removed from the album, the photos he uploaded will still appear in the album.
:::
After creating the album, click the Add User button and select the user you want to share with.
### Shared Album Options
- Enable or disable comments & likes.
- Add or remove users
To change these settings click on the 3 dots on the top right -> options.
## Share Album via Link
:::info
When wanting to share with people outside the home network via a link, Immich needs to be exposed to the world wide web, read more to [learn the ways to do this](/docs/guides/remote-access.md).
:::
1. Enter the shared album.
2. Click 3 dots on the top right.
3. Click on Share.
You can edit the link properties, options include;
- **Description -** adding a description to the album (optional)
- **Password -** adding a password to the album (optional)
- **Show metadata -** whether to show metadata to whoever the link will be shared with (optional).
- **Allow public user to download -** whether to allow whoever has the link to download all the images or a certain image (optional).
- **Allow public user to upload -** whether to allow whoever has the link to upload assets to the album (optional).
::: info
whoever has the link and have uploaded files cannot delete them.
:::
- **Expire after -** adding an expiration date to the link (optional).
## Share Specific Assets
A user can share specific assets without linking them to a specific album.
in order to do so;
1. Go to the timeline
2. Select the assets.
3. Click the share button
:::info
Assets shared in this way will not be displayed in the Sharing category, in order to expect to remove or change the link settings of assets shared in this way, you must use the Manage generated links option.
:::
:::tip
For temporary sharing, you can add an expiration date to assets shared this way.
:::
## Manage generated links
A user can copy, delete and change the link settings he created for specific albums or assets, in order to do so;
1. Go to Sharing category.
2. Select Shared links at the top right.
:::info remove links/users.
When making a shared album private, the added photos will **still** be saved in the album.
:::
## Activity
:::info
Activity is not visible when sharing an album via external link.
New users added to the album will be able to see previous activity.
:::
### Add a Comment or Like to the Album
1. Enter the shared album.
2. From the top right you can add comment(s) or delete old comments.
3. To add a like (heart) to the album, click the heart button to the right of the "say something" button.
#### Add a Comment or Like to a Specific Photo
:::info Activity
Activity of a comment or heart on a specific photo will appear in the general activity of the album.
:::
1. Enter the shared album.
2. Enter the picture.
3. From the top right you can add comment(s) or delete old comments.
4. To add a like (heart) to the album, click the heart button to the right of the "say something" button.
### Viewing Activity in the Album
To view album activity on comments or likes
1. Enter the shared album
2. On the top right click on the message icon
</TabItem>
</Tabs>

View File

@@ -2,8 +2,8 @@
This guide explains storing generated and raw files with docker's volume mount in different locations.
:::note Backup
It is important to remember to update the backup settings after following the guide to back up the new backup paths if using automatic backup tools.
:::caution Backup
It is important to remember to update the backup settings after following the guide to back up the new backup paths if using automatic backup tools, especially `profile/`.
:::
In our `.env` file, we will define variables that will help us in the future when we want to move to a more advanced server in the future
@@ -12,10 +12,11 @@ In our `.env` file, we will define variables that will help us in the future whe
# You can find documentation for all the supported env variables at https://immich.app/docs/install/environment-variables
# Custom location where your uploaded, thumbnails, and transcoded video files are stored
- {UPLOAD_LOCATION}=./library
+ {UPLOAD_LOCATION}=/custom/location/on/your/system/
+ {THUMB_LOCATION}=/custom/location/on/your/system/
+ {ENCODED_VIDEO_LOCATION}=/custom/location/on/your/system/
- UPLOAD_LOCATION=./library
+ UPLOAD_LOCATION=/custom/location/on/your/system/immich/immich_files
+ THUMB_LOCATION=/custom/location/on/your/system/immich/thumbs
+ ENCODED_VIDEO_LOCATION=/custom/location/on/your/system/immich/encoded-video
+ PROFILE_LOCATION=/custom/location/on/your/system/immich/profile
...
```
@@ -28,6 +29,7 @@ services:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
+ - ${THUMB_LOCATION}:/usr/src/app/upload/thumbs
+ - ${ENCODED_VIDEO_LOCATION}:/usr/src/app/upload/encoded-video
+ - ${PROFILE_LOCATION}:/usr/src/app/upload/profile
- /etc/localtime:/etc/localtime:ro
...
@@ -37,6 +39,7 @@ services:
- ${UPLOAD_LOCATION}:/usr/src/app/upload
+ - ${THUMB_LOCATION}:/usr/src/app/upload/thumbs
+ - ${ENCODED_VIDEO_LOCATION}:/usr/src/app/upload/encoded-video
+ - ${PROFILE_LOCATION}:/usr/src/app/upload/profile
- /etc/localtime:/etc/localtime:ro
```
@@ -47,4 +50,14 @@ docker compose down
docker compose up -d
```
:::note
Because of the underlying properties of docker bind mounts, it is not recommended to mount the `upload/` and `library/` folders as separate bind mounts if they are on the same device.
For this reason, we mount the HDD or network storage to `/usr/src/app/upload` and then mount the folders we want quick access to below this folder.
The `thumbs/` folder contains both the small thumbnails shown in the timeline, and the larger previews shown when clicking into an image. These cannot be split up.
The storage metrics of the Immich server will track the storage available at `UPLOAD_LOCATION`,
so the administrator should setup some kind of monitoring to make sure the SSD does not run out of space. The `profile/` folder is much smaller, typically less than 1 MB.
:::
Thanks to [Jrasm91](https://github.com/immich-app/immich/discussions/2110#discussioncomment-5477767) for writing the guide.

View File

@@ -0,0 +1,16 @@
# Create Custom Map Styles for Immich Using Maptiler
You may decide that you'd like to modify the style document which is used to draw the maps in Immich. This can be done easily using Maptiler, if you do not want to write an entire JSON document by hand.
## Steps
1. Create a free account at https://cloud.maptiler.com
2. Once logged in, you can either create a brand new map by clicking on **New Map**, selecting a starter map, and then clicking **Customize**, OR by selecting a **Standard Map** and customizing it from there.
3. The **editor** interface is self-explanatory. You can change colors, remove visible layers, or add optional layers (e.g., administrative, topo, hydro, etc.) in the composer.
4. Once you have your map composed, click on **Save** at the top right. Give it a unique name to save it to your account.
5. Next, **Publish** your style using the **Publish** button at the top right. This will deploy it to production, which means it is able to be exposed over the Internet. Maptiler will present an interactive side-by-side map with the original and your changes prior to publication.<br/>![Maptiler Publication Settings](img/immich_map_styles_publish.png)
6. Maptiler will warn you that changing the map will change it across all apps using the map. Since no apps are using the map yet, this is okay.
7. Clicking on the name of your new map at the top left will bring you to the item's **details** page. From here, copy the link to the JSON style under **Use vector style**. This link will automatically contain your personal API key to Maptiler.
8. In **Immich**, navigate to **Administration --> Settings --> Map & GPS Settings** and expand the **Map Settings** subsection.
9. Paste the link to your JSON style in either the **Light Style** or **Dark Style**. (You can add different styles which will help make the map style more appropriate depending on whether you set **Immich** to Light or Dark mode.
10. Save your selections. Reload the map, and enjoy your custom map style!

View File

@@ -2,48 +2,52 @@
A short guide on connecting [pgAdmin](https://www.pgadmin.org/) to Immich.
:::note
In order to connect to the database the immich_postgres container **must be running**.
The passwords and usernames used below match the ones specified in the example `.env` file. If changed, please use actual values instead.
**Optional:** To connect to the database **outside** of your Docker's network:
- Expose port 5432 in your `docker-compose.yml` file.
- Edit the PostgreSQL [`pg_hba.conf`](https://www.postgresql.org/docs/current/auth-pg-hba-conf.html) file.
- Make sure your firewall does not block access to port 5432.
Note that exposing the database port increases the risk of getting attacked by hackers.
Make sure to remove the binding port after finishing the database's tasks.
:::
## 1. Install pgAdmin
Download and install [pgAdmin](https://www.pgadmin.org/download/) following the official documentation.
Add a file `docker-compose-pgadmin.yml` next to your `docker-compose.yml` with the following content:
```
name: immich
services:
pgadmin:
image: dpage/pgadmin4
container_name: pgadmin4_container
restart: always
ports:
- "8888:80"
environment:
PGADMIN_DEFAULT_EMAIL: user-name@domain-name.com
PGADMIN_DEFAULT_PASSWORD: strong-password
volumes:
- pgadmin-data:/var/lib/pgadmin
volumes:
pgadmin-data:
```
Change the values of `PGADMIN_DEFAULT_EMAIL` and `PGADMIN_DEFAULT_PASSWORD` in this file.
Run `docker compose -f docker-compose.yml -f docker-compose-pgadmin.yml up` to start immich along with `pgAdmin`.
## 2. Add a Server
Open pgAdmin and click "Add New Server".
Open [localhost:8888](http://localhost:8888) and login with the default credentials from above.
<img src={require('./img/add-new-server-option.png').default} width="50%" title="new server option" />
Right click on `Servers` and click on `Register >> Server..` then enter the values below in the `Connection` tab.
## 3. Enter Connection Details
<img src={require('./img/pgadmin-add-new-server.png').default} width="50%" title="new server option" />
| Name | Value |
| -------------------- | ----------- |
| Host name/address | `localhost` |
| Port | `5432` |
| Maintenance database | `immich` |
| Username | `postgres` |
| Password | `postgres` |
:::note
The parameters used here match those specified in the example `.env` file. If you have changed your `.env` file, you'll need to adjust accordingly.
:::
<img src={require('./img/Connection-Pgadmin.png').default} width="75%" title="Connection" />
## 4. Save Connection
| Name | Value |
| -------------------- | ----------------- |
| Host name/address | `immich_postgres` |
| Port | `5432` |
| Maintenance database | `immich` |
| Username | `postgres` |
| Password | `postgres` |
Click on "Save" to connect to the Immich database.
:::tip
View [Database Queries](/docs/guides/database-queries/) for common database queries.
:::

View File

@@ -34,12 +34,22 @@ You can calculate the checksum for a particular file by using the command `sha1s
```sql title="Find by checksum (SHA-1)"
SELECT encode("checksum", 'hex') FROM "assets";
SELECT * FROM "assets" WHERE "checksum" = decode('69de19c87658c4c15d9cacb9967b8e033bf74dd1', 'hex');
SELECT * FROM "assets" WHERE "checksum" = '\x69de19c87658c4c15d9cacb9967b8e033bf74dd1'; -- alternate notation
```
```sql title="Live photos"
SELECT * FROM "assets" WHERE "livePhotoVideoId" IS NOT NULL;
```
```sql title="By description"
SELECT "assets".*, "exif"."description" FROM "exif"
JOIN "assets" ON "assets"."id" = "exif"."assetId"
WHERE TRIM("exif"."description") <> ''; -- all files with a description
SELECT "assets".*, "exif"."description" FROM "exif"
JOIN "assets" ON "assets"."id" = "exif"."assetId"
WHERE "exif"."description" ILIKE '%string to match%'; -- search by string
```
```sql title="Without metadata"
SELECT "assets".* FROM "exif"
LEFT JOIN "assets" ON "assets"."id" = "exif"."assetId"
@@ -86,7 +96,7 @@ SELECT * FROM "users";
## System Config
```sql title="Custom settings"
SELECT "key", "value" FROM "system_config";
SELECT "key", "value" FROM "system_metadata" WHERE "key" = 'system-config';
```
(Only used when not using the [config file](/docs/install/config-file))
@@ -94,5 +104,5 @@ SELECT "key", "value" FROM "system_config";
## Persons
```sql title="Delete person and unset it for the faces it was associated with"
DELETE FROM person WHERE name = 'PersonNameHere';
DELETE FROM "person" WHERE "name" = 'PersonNameHere';
```

View File

@@ -6,15 +6,19 @@ in a directory on the same machine.
# Mount the directory into the containers.
Edit `docker-compose.yml` to add two new mount points under `volumes:`
Edit `docker-compose.yml` to add two new mount points in the sections `immich-server:` and `immich-microservices:` under `volumes:`
```
immich-server:
```diff
immich-server:
volumes:
- ${EXTERNAL_PATH}:/usr/src/app/external
+ - ${EXTERNAL_PATH}:/usr/src/app/external
immich-microservices:
volumes:
+ - ${EXTERNAL_PATH}:/usr/src/app/external
```
Be sure to add exactly the same line to both `immich-server:` and `immich-microservices:`.
Be sure to add exactly the same path to both services.
Edit `.env` to define `EXTERNAL_PATH`, substituting in the correct path for your computer:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

View File

@@ -7,6 +7,9 @@ A config file can be provided as an alternative to the UI configuration.
In JSON format, create a new config file (e.g. `immich.json`) and put it in a location that can be accessed by Immich.
The default configuration looks like this:
<details>
<summary>immich.json</summary>
```json
{
"ffmpeg": {
@@ -74,6 +77,10 @@ The default configuration looks like this:
"enabled": true,
"modelName": "ViT-B-32__openai"
},
"duplicateDetection": {
"enabled": false,
"maxDistance": 0.03
},
"facialRecognition": {
"enabled": true,
"modelName": "buffalo_l",
@@ -157,6 +164,8 @@ The default configuration looks like this:
}
```
</details>
:::tip
In Administration > Settings is a button to copy the current configuration to your clipboard.
So you can just grab it from there, paste it into a file and you're pretty much good to go.

View File

@@ -17,28 +17,39 @@ If this should not work, try running `docker compose up -d --force-recreate`.
## Docker Compose
| Variable | Description | Default | Services |
| :---------------- | :-------------------- | :-------: | :-------------------------------------- |
| `IMMICH_VERSION` | Image tags | `release` | server, microservices, machine learning |
| `UPLOAD_LOCATION` | Host Path for uploads | | server, microservices |
| Variable | Description | Default | Services |
| :----------------- | :------------------------------ | :-------: | :-------------------------------------- |
| `IMMICH_VERSION` | Image tags | `release` | server, microservices, machine learning |
| `UPLOAD_LOCATION` | Host Path for uploads | | server, microservices |
| `DB_DATA_LOCATION` | Host Path for Postgres database | | database |
:::tip
These environment variables are used by the `docker-compose.yml` file and do **NOT** affect the containers directly.
:::
### Supported filesystems
The Immich Postgres database (`DB_DATA_LOCATION`) must be located on a filesystem that supports user/group
ownership and permissions (EXT2/3/4, ZFS, APFS, BTRFS, XFS, etc.). It will not work on any filesystem formatted in NTFS or ex/FAT/32.
It will not work in WSL (Windows Subsystem for Linux) when using a mounted host directory (commonly under `/mnt`).
If this is an issue, you can change the bind mount to a Docker volume instead.
Regardless of filesystem, it is not recommended to use a network share for your database location due to performance and possible data loss issues.
## General
| Variable | Description | Default | Services |
| :------------------------------ | :------------------------------------------- | :------------------: | :-------------------------------------- |
| `TZ` | Timezone | | microservices |
| `NODE_ENV` | Environment (production, development) | `production` | server, microservices, machine learning |
| `LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, microservices, machine learning |
| `IMMICH_MEDIA_LOCATION` | Media Location | `./upload` | server, microservices |
| `IMMICH_CONFIG_FILE` | Path to config file | | server, microservices |
| `IMMICH_WEB_ROOT` | Path of root index.html | `/usr/src/app/www` | server |
| `IMMICH_REVERSE_GEOCODING_ROOT` | Path of reverse geocoding dump directory | `/usr/src/resources` | microservices |
| Variable | Description | Default | Services |
| :------------------------------ | :------------------------------------------- | :----------------------: | :-------------------------------------- |
| `TZ` | Timezone | | microservices |
| `IMMICH_ENV` | Environment (production, development) | `production` | server, microservices, machine learning |
| `IMMICH_LOG_LEVEL` | Log Level (verbose, debug, log, warn, error) | `log` | server, microservices, machine learning |
| `IMMICH_MEDIA_LOCATION` | Media Location | `./upload`<sup>\*1</sup> | server, microservices |
| `IMMICH_CONFIG_FILE` | Path to config file | | server, microservices |
| `IMMICH_WEB_ROOT` | Path of root index.html | `/usr/src/app/www` | server |
| `IMMICH_REVERSE_GEOCODING_ROOT` | Path of reverse geocoding dump directory | `/usr/src/resources` | microservices |
\*1: With the default `WORKDIR` of `/usr/src/app`, this path will resolve to `/usr/src/app/upload`.
It only need to be set if the Immich deployment method is changing.
:::tip
`TZ` should be set to a `TZ identifier` from [this list][tz-list]. For example, `TZ="Etc/UTC"`.
@@ -48,45 +59,45 @@ These environment variables are used by the `docker-compose.yml` file and do **N
## Ports
| Variable | Description | Default | Services |
| :---------------------- | :-------------------- | :-------: | :-------------------- |
| `HOST` | Host | `0.0.0.0` | server, microservices |
| `SERVER_PORT` | Server Port | `3001` | server |
| `MICROSERVICES_PORT` | Microservices Port | `3002` | microservices |
| `MACHINE_LEARNING_HOST` | Machine Learning Host | `0.0.0.0` | machine learning |
| `MACHINE_LEARNING_PORT` | Machine Learning Port | `3003` | machine learning |
| Variable | Description | Default |
| :------------ | :------------- | :------------------------------------: |
| `IMMICH_HOST` | Listening host | `0.0.0.0` |
| `IMMICH_PORT` | Listening port | 3001 (server), 3003 (machine learning) |
## Database
| Variable | Description | Default | Services |
| :---------------------------------- | :------------------------------------------------------------ | :----------: | :-------------------- |
| `DB_URL` | Database URL | | server, microservices |
| `DB_HOSTNAME` | Database Host | `localhost` | server, microservices |
| `DB_PORT` | Database Port | `5432` | server, microservices |
| `DB_USERNAME` | Database User | `postgres` | server, microservices |
| `DB_PASSWORD` | Database Password | `postgres` | server, microservices |
| `DB_DATABASE_NAME` | Database Name | `immich` | server, microservices |
| `DB_VECTOR_EXTENSION`<sup>\*1</sup> | Database Vector Extension (one of [`pgvector`, `pgvecto.rs`]) | `pgvecto.rs` | server, microservices |
| Variable | Description | Default | Services |
| :---------------------------------- | :----------------------------------------------------------------------- | :----------: | :-------------------------------------------- |
| `DB_URL` | Database URL | | server, microservices |
| `DB_HOSTNAME` | Database Host | `database` | server, microservices |
| `DB_PORT` | Database Port | `5432` | server, microservices |
| `DB_USERNAME` | Database User | `postgres` | server, microservices, database<sup>\*1</sup> |
| `DB_PASSWORD` | Database Password | `postgres` | server, microservices, database<sup>\*1</sup> |
| `DB_DATABASE_NAME` | Database Name | `immich` | server, microservices, database<sup>\*1</sup> |
| `DB_VECTOR_EXTENSION`<sup>\*2</sup> | Database Vector Extension (one of [`pgvector`, `pgvecto.rs`]) | `pgvecto.rs` | server, microservices |
| `DB_SKIP_MIGRATIONS` | Whether to skip running migrations on startup (one of [`true`, `false`]) | `false` | server, microservices |
\*1: This setting cannot be changed after the server has successfully started up
\*1: The values of `DB_USERNAME`, `DB_PASSWORD`, and `DB_DATABASE_NAME` are passed to the Postgres container as the variables `POSTGRES_USER`, `POSTGRES_PASSWORD`, and `POSTGRES_DB` in `docker-compose.yml`.
\*2: This setting cannot be changed after the server has successfully started up.
:::info
When `DB_URL` is defined, the other database (`DB_*`) variables are ignored, with the exception of `DB_VECTOR_EXTENSION`.
When `DB_URL` is defined, the `DB_HOSTNAME`, `DB_PORT`, `DB_USERNAME`, `DB_PASSWORD` and `DB_DATABASE_NAME` database variables are ignored.
:::
## Redis
| Variable | Description | Default | Services |
| :--------------- | :------------- | :------------: | :-------------------- |
| `REDIS_URL` | Redis URL | | server, microservices |
| `REDIS_HOSTNAME` | Redis Host | `immich_redis` | server, microservices |
| `REDIS_PORT` | Redis Port | `6379` | server, microservices |
| `REDIS_DBINDEX` | Redis DB Index | `0` | server, microservices |
| `REDIS_USERNAME` | Redis Username | | server, microservices |
| `REDIS_PASSWORD` | Redis Password | | server, microservices |
| `REDIS_SOCKET` | Redis Socket | | server, microservices |
| Variable | Description | Default | Services |
| :--------------- | :------------- | :-----: | :-------------------- |
| `REDIS_URL` | Redis URL | | server, microservices |
| `REDIS_HOSTNAME` | Redis Host | `redis` | server, microservices |
| `REDIS_PORT` | Redis Port | `6379` | server, microservices |
| `REDIS_DBINDEX` | Redis DB Index | `0` | server, microservices |
| `REDIS_USERNAME` | Redis Username | | server, microservices |
| `REDIS_PASSWORD` | Redis Password | | server, microservices |
| `REDIS_SOCKET` | Redis Socket | | server, microservices |
:::info
@@ -100,6 +111,9 @@ More info can be found in the upstream [ioredis][redis-api] documentation.
Redis (Sentinel) URL example JSON before encoding:
<details>
<summary>JSON</summary>
```json
{
"sentinels": [
@@ -120,6 +134,8 @@ Redis (Sentinel) URL example JSON before encoding:
}
```
</details>
## Machine Learning
| Variable | Description | Default | Services |

View File

@@ -4,7 +4,7 @@ sidebar_position: 40
# Kubernetes
You can deploy Immich on Kubernetes using [the official Helm chart](https://github.com/immich-app/immich-charts/tree/main/charts/immich).
You can deploy Immich on Kubernetes using [the official Helm chart](https://github.com/immich-app/immich-charts/blob/main/README.md).
You can view some [examples](https://kubesearch.dev/#/immich) of how other people run Immich on Kubernetes, using the official chart or otherwise.

View File

@@ -8,7 +8,7 @@ Install Immich using Portainer's Stack feature.
1. Go to "**Stacks**" in the left sidebar.
2. Click on "**Add stack**".
3. Give the stack a name (i.e. Immich), and select "**Web Editor**" as the build method.
3. Give the stack a name (i.e. immich), and select "**Web Editor**" as the build method.
4. Copy the content of the `docker-compose.yml` file from the [GitHub repository](https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml).
5. Replace `.env` with `stack.env` for all containers that need to use environment variables in the web editor.
@@ -38,8 +38,9 @@ style={{border: '1px solid #ddd'}}
alt="Dot Env Example"
/>
- Populate custom database information if necessary.
- Populate `UPLOAD_LOCATION` with your preferred location for storing backup assets.
- Change the default `DB_PASSWORD`, and add custom database connection information if necessary.
- Change `DB_DATA_LOCATION` to a folder where the database will be saved to disk.
- Change `UPLOAD_LOCATION` to a folder where media (uploaded and generated) will be stored.
11. Click on "**Deploy the stack**".

View File

@@ -15,12 +15,15 @@ Hardware and software requirements for Immich
Immich requires the command `docker compose` - the similarly named `docker-compose` is [deprecated](https://docs.docker.com/compose/migrate/) and is no longer compatible with Immich.
:::
:::info Podman
You can also use Podman to run the application. However, additional configuration might be required.
:::
## Hardware
- **OS**: Preferred unix-based operating system (Ubuntu, Debian, MacOS, etc). Windows works too, with [Docker Desktop on Windows](https://docs.docker.com/desktop/install/windows-install/)
- **RAM**: At least 4GB, preferred 6GB.
- **CPU**: At least 2 cores, preferred 4 cores.
- **OS**: Recommended Linux operating system (Ubuntu, Debian, etc).
- Windows is supported with [Docker Desktop on Windows](https://docs.docker.com/desktop/install/windows-install/) or [WSL 2](https://docs.docker.com/desktop/wsl/).
- macOS is supported with [Docker Desktop on Mac](https://docs.docker.com/desktop/install/mac-install/).
- **RAM**: Minimum 4GB, recommended 6GB.
- **CPU**: Minimum 2 cores, recommended 4 cores.
- **Storage**: Recommended Unix-compatible filesystem (EXT4, ZFS, APFS, etc.) with support for user/group ownership and permissions.
- This can present an issue for Windows users. See [here](/docs/install/environment-variables#supported-filesystems)
for more details and alternatives.
- The generation of thumbnails and transcoded video can increase the size of the photo library by 10-20% on average.
- Network shares are supported for the storage of image and video assets only.

View File

@@ -4,24 +4,8 @@ sidebar_position: 5
# Support The Project
I am 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-which-must-not-be-named is a Hostile Actor'](https://selfhosted.show/79?t=1418) said, this is a massive undertaking that the team and I are doing. 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 see yourself using for a long time, please consider supporting the project with one of the options below.
## Donation
- Monthly donation via [GitHub Sponsors](https://github.com/sponsors/immich-app)
- One-time donation via [GitHub Sponsors](https://github.com/sponsors/immich-app?frequency=one-time)
- [Librepay](https://liberapay.com/alex.tran1502/)
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
- Bitcoin: 3QVAb9dCHutquVejeNXitPqZX26Yg5kxb7
## Contributing
There are lots of non-monetary ways to contribute to Immich as well.
1. Testing - Using Immich and reporting bugs is a great way to help support the project. Found a bug? [Open an issue on GitHub][github-issue].
1. Translations - The Immich mobile app has been translated into [17 languages][github-langs] so far! To contribute with translations, email me at alex.tran1502@gmail.com or send me a message on discord.
1. Development - If you are a programmer or developer, take a look at Immich's [technology stack](/docs/developer/architecture.mdx) and consider fixing bugs or building new features. The team and I are always looking for new contributors. For information about how to contribute as a developer, see the [Developer](/docs/developer/architecture.mdx) section.

View File

@@ -14,6 +14,12 @@ The admin user can set the template by using the template builder in the `Admini
Year/Year-Month-Day/Filename.Extension
```
If you want to change the storage template during the initial setup, first enable the feature.
<img src={require('./img/enable-storage-template.png').default} width="100%" title="Enable Storage Template Setting" />
Then, customize your storage template.
<img src={require('./img/storage-template.png').default} width="100%" title="Storage Template Setting" />
:::tip

View File

@@ -1,3 +1,9 @@
If you have friends or family members who want to use the application as well, you can create addition accounts. The default password is `password`, and the user has to change their password after logging in to the application for the first time. The system administrator can disable this option by unchecking the option "Require user to change password on first login" in the user registration interface.
If you have friends or family members who want to use the application as well, you can create additional accounts for them.
<img src={require('./img/create-new-user.png').default} width="90%" title='New User Registration' />
In the Administration panel, you can click on the **Create user** button, and you'll be presented with the following dialog:
<img src={require('./img/create-new-user-dialog.png').default} width="90%" title='New User Registration Dialog' />
You can then set an initial password. If you'd like, you can use an easy password like `password`, and keep the setting **Require user to change password on first login** active. This will prompt the user to change their password on first login, to a password that they choose.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 334 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View File

@@ -119,6 +119,11 @@ const config = {
label: 'GitHub',
position: 'right',
},
{
href: 'https://discord.gg/D8JsnBEuKb',
label: 'Discord',
position: 'right',
},
],
},
footer: {

3878
docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,8 +16,8 @@
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "^3.1.0",
"@docusaurus/preset-classic": "^3.1.0",
"@docusaurus/core": "^3.2.1",
"@docusaurus/preset-classic": "^3.2.1",
"@mdi/js": "^7.3.67",
"@mdi/react": "^1.6.1",
"@mdx-js/react": "^3.0.0",
@@ -25,7 +25,7 @@
"classnames": "^2.3.2",
"clsx": "^2.0.0",
"docusaurus-lunr-search": "^3.3.2",
"docusaurus-preset-openapi": "^0.7.3",
"docusaurus-preset-openapi": "^0.7.5",
"postcss": "^8.4.25",
"prism-react-renderer": "^2.3.1",
"raw-loader": "^4.0.2",
@@ -53,6 +53,9 @@
]
},
"engines": {
"node": ">=16.14"
"node": ">=20"
},
"volta": {
"node": "20.13.1"
}
}

View File

@@ -10,17 +10,17 @@ interface CommunityGuidesProps {
const guides: CommunityGuidesProps[] = [
{
title: 'Cloudflare Tunnels with SSO/OAuth',
description: `Setting up Cloudflare Tunnels and a SaaS App for immich.`,
description: `Setting up Cloudflare Tunnels and a SaaS App for Immich.`,
url: 'https://github.com/immich-app/immich/discussions/8299',
},
{
title: 'Database backup in Truenas',
description: `Create a database backup with pgAdmin in Truenas.`,
title: 'Database backup in TrueNAS',
description: `Create a database backup with pgAdmin in TrueNAS.`,
url: 'https://github.com/immich-app/immich/discussions/8809',
},
{
title: 'Unraid backup scripts',
description: `Back up your assets in Unarid with a pre-prepared script.`,
description: `Back up your assets in Unraid with a pre-prepared script.`,
url: 'https://github.com/immich-app/immich/discussions/8416',
},
{
@@ -33,6 +33,11 @@ const guides: CommunityGuidesProps[] = [
description: 'Documentation for simple podman setup using quadlets.',
url: 'https://github.com/tbelway/immich-podman-quadlets/blob/main/docs/install/podman-quadlet.md',
},
{
title: 'Google Photos import + albums',
description: 'Import your Google Photos files into Immich and add your albums',
url: 'https://github.com/immich-app/immich/discussions/1340',
},
];
function CommunityGuide({ title, description, url }: CommunityGuidesProps): JSX.Element {

View File

@@ -10,7 +10,7 @@ interface CommunityProjectProps {
const projects: CommunityProjectProps[] = [
{
title: 'immich-go',
description: `An alternative to the immich-CLI command that doesn't depend on nodejs installation. It tries its best for importing google photos takeout archives.`,
description: `An alternative to the immich-CLI that doesn't depend on nodejs. It specializes in importing Google Photos Takeout archives.`,
url: 'https://github.com/simulot/immich-go',
},
{
@@ -25,14 +25,19 @@ const projects: CommunityProjectProps[] = [
},
{
title: 'Remove offline files',
description: 'A Python script to remove offline files.',
url: 'https://gist.github.com/Thoroslives/ca5d8e1efd15111febc1e7b34ac72668',
description: 'A simple way to remove orphaned offline assets from the Immich database',
url: 'https://github.com/Thoroslives/immich_remove_offline_files',
},
{
title: 'Create albums from folders',
description: 'A Python script to create albums based on the folder structure of an external library.',
url: 'https://github.com/Salvoxia/immich-folder-album-creator',
},
{
title: 'Immich-Tools',
description: 'Provides scripts for handling problems on the repair page.',
url: 'https://github.com/clumsyCoder00/Immich-Tools',
},
{
title: 'Lightroom Publisher: mi.Immich.Publisher',
description: 'Lightroom plugin to publish photos from Lightroom collections to Immich albums.',

View File

@@ -7,7 +7,7 @@ export interface Item {
icon: string;
title: string;
description?: string;
release: string;
release?: string;
tag?: string;
date: Date;
dateType: DateType;
@@ -70,10 +70,10 @@ export default function Timeline({ items }: Props): JSX.Element {
target="_blank"
rel="noopener"
>
[{item.release}]{' '}
[{item.release ?? item.tag}]{' '}
</a>
) : (
<span>[{item.release}]</span>
item.release && <span>[{item.release}]</span>
)}
</span>
</div>

View File

@@ -19,8 +19,8 @@ button {
:root {
--ifm-color-primary: #4250af;
--ifm-color-primary-dark: #4250af;
--ifm-color-primary-darker: 4250af;
--ifm-color-primary-darkest: 4250af;
--ifm-color-primary-darker: #4250af;
--ifm-color-primary-darkest: #4250af;
--ifm-color-primary-light: #4250af;
--ifm-color-primary-lighter: #4250af;
--ifm-color-primary-lightest: #4250af;
@@ -41,6 +41,10 @@ button {
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
}
div[class^='announcementBar_'] {
height: 2rem;
}
.navbar__brand .navbar__title {
@apply font-immich-title text-2xl font-normal text-immich-primary dark:text-immich-dark-primary;
}

View File

@@ -34,14 +34,12 @@ function HomepageHeader() {
Demo portal
</Link>
<a
href="https://github.com/sponsors/immich-app"
target="_blank"
rel="noreferrer"
className="flex place-items-center place-content-center py-3 px-8 border bg-immich-sponsor rounded-full no-underline hover:no-underline text-white dark:text-immich-dark-bg dark:bg-immich-sponsor hover:text-white font-bold uppercase"
<Link
className="flex place-items-center place-content-center py-3 px-8 border bg-immich-dark-primary dark:bg-immich-primary rounded-full hover:no-underline text-immich-primary dark:text-immich-dark-bg font-bold uppercase"
to="https://discord.gg/D8JsnBEuKb"
>
Sponsor
</a>
Discord
</Link>
</div>
<img src="/img/immich-screenshots.png" alt="screenshots" width={'70%'} />
<div className="flex flex-col sm:flex-row place-items-center place-content-center mt-4 gap-1">

View File

@@ -52,12 +52,50 @@ import {
mdiWeb,
mdiScaleBalance,
mdiMagnifyScan,
mdiChartBoxMultipleOutline,
mdiAccountGroupOutline,
mdiFlowerPoppy,
mdiHandshakeOutline,
} from '@mdi/js';
import Layout from '@theme/Layout';
import React from 'react';
import Timeline, { DateType, Item } from '../components/timeline';
const items: Item[] = [
{
icon: mdiHandshakeOutline,
description: 'Joined Futo and Immich core team goes full-time',
title: 'Immich joins FUTO!',
date: new Date(2024, 4, 1),
dateType: DateType.DATE,
},
{
icon: mdiStar,
description: 'Reached 30K Stars on GitHub!',
title: '30,000 Stars',
release: 'v1.102.0',
tag: 'v1.102.0',
date: new Date(2024, 3, 15),
dateType: DateType.RELEASE,
},
{
icon: mdiChartBoxMultipleOutline,
description: 'OpenTelemetry metrics for local evaluation and advanced debugging',
title: 'OpenTelemetry metrics',
release: 'v1.99.0',
tag: 'v1.99.0',
date: new Date(2024, 2, 20),
dateType: DateType.RELEASE,
},
{
icon: mdiFlowerPoppy,
description: 'Immich got its new logo',
title: 'New logo',
release: 'v1.98.0',
tag: 'v1.98.0',
date: new Date(2024, 2, 7),
dateType: DateType.RELEASE,
},
{
icon: mdiMagnifyScan,
description: 'Advanced search with filters by date, location and more',
@@ -94,6 +132,15 @@ const items: Item[] = [
date: new Date(2024, 0, 31),
dateType: DateType.RELEASE,
},
{
icon: mdiAccountGroupOutline,
description: '250 amazing people contributed to Immich',
title: '250 unique contributors',
release: 'v1.93.0',
tag: 'v1.93.0',
date: new Date(2024, 0, 19),
dateType: DateType.RELEASE,
},
{
icon: mdiMatrix,
description: 'Moved the search from typesense to pgvecto.rs',
@@ -142,7 +189,7 @@ const items: Item[] = [
},
{
icon: mdiStar,
description: 'Reach 20K Stars on GitHub!',
description: 'Reached 20K Stars on GitHub!',
title: '20,000 Stars',
release: 'v1.83.0',
tag: 'v1.83.0',
@@ -403,7 +450,7 @@ const items: Item[] = [
},
{
icon: mdiStar,
description: 'Reach 10K Stars on GitHub!',
description: 'Reached 10K Stars on GitHub!',
title: '10,000 Stars',
release: 'v1.54.0',
tag: 'v1.54.0',

BIN
docs/static/img/ios-app-store-badge.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -20,8 +20,6 @@ module.exports = {
'immich-dark-bg': 'black',
'immich-dark-fg': '#e5e7eb',
'immich-dark-gray': '#212121',
'immich-sponsor': '#db61a2',
},
fontFamily: {
'immich-title': ['Snowburst One', 'cursive'],

1
e2e/.nvmrc Normal file
View File

@@ -0,0 +1 @@
20.13

View File

@@ -2,41 +2,32 @@ version: '3.8'
name: immich-e2e
x-server-build: &server-common
image: immich-server:latest
build:
context: ../
dockerfile: server/Dockerfile
environment:
- DB_HOSTNAME=database
- DB_USERNAME=postgres
- DB_PASSWORD=postgres
- DB_DATABASE_NAME=immich
- REDIS_HOSTNAME=redis
- IMMICH_MACHINE_LEARNING_ENABLED=false
- IMMICH_METRICS=true
volumes:
- upload:/usr/src/app/upload
- ../server/test/assets:/data/assets
depends_on:
- redis
- database
services:
immich-server:
container_name: immich-e2e-server
command: ['./start.sh', 'immich']
<<: *server-common
command: ['./start.sh']
image: immich-server:latest
build:
context: ../
dockerfile: server/Dockerfile
environment:
- DB_HOSTNAME=database
- DB_USERNAME=postgres
- DB_PASSWORD=postgres
- DB_DATABASE_NAME=immich
- IMMICH_MACHINE_LEARNING_ENABLED=false
- IMMICH_METRICS=true
volumes:
- upload:/usr/src/app/upload
- ./test-assets:/test-assets
depends_on:
- redis
- database
ports:
- 2283:3001
immich-microservices:
container_name: immich-e2e-microservices
command: ['./start.sh', 'microservices']
<<: *server-common
redis:
image: redis:6.2-alpine@sha256:84882e87b54734154586e5f8abd4dce69fe7311315e2fc6d67c29614c8de2672
image: redis:6.2-alpine@sha256:c0634a08e74a4bb576d02d1ee993dc05dba10e8b7b9492dfa28a7af100d46c01
database:
image: tensorchord/pgvecto-rs:pg14-v0.2.0@sha256:90724186f0a3517cf6914295b5ab410db9ce23190a2d9d0b9dd6463e3fa298f0

610
e2e/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{
"name": "immich-e2e",
"version": "1.102.0",
"version": "1.105.1",
"description": "",
"main": "index.js",
"type": "module",
@@ -33,17 +33,20 @@
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-unicorn": "^52.0.0",
"exiftool-vendored": "^24.5.0",
"eslint-plugin-unicorn": "^53.0.0",
"exiftool-vendored": "^26.0.0",
"luxon": "^3.4.4",
"pg": "^8.11.3",
"pngjs": "^7.0.0",
"prettier": "^3.2.5",
"prettier-plugin-organize-imports": "^3.2.4",
"socket.io-client": "^4.7.4",
"supertest": "^6.3.4",
"supertest": "^7.0.0",
"typescript": "^5.3.3",
"utimes": "^5.2.1",
"vitest": "^1.3.0"
},
"volta": {
"node": "20.13.1"
}
}

View File

@@ -1,6 +1,7 @@
import {
ActivityCreateDto,
AlbumResponseDto,
AlbumUserRole,
AssetFileUploadResponseDto,
LoginResponseDto,
ReactionType,
@@ -33,7 +34,7 @@ describe('/activity', () => {
createAlbumDto: {
albumName: 'Album 1',
assetIds: [asset.id],
sharedWithUserIds: [nonOwner.userId],
albumUsers: [{ userId: nonOwner.userId, role: AlbumUserRole.Editor }],
},
},
{ headers: asBearerAuth(admin.accessToken) },

View File

@@ -1,12 +1,13 @@
import {
addAssetsToAlbum,
AlbumResponseDto,
AlbumUserRole,
AssetFileUploadResponseDto,
AssetOrder,
LoginResponseDto,
SharedLinkType,
addAssetsToAlbum,
deleteUser,
getAlbumInfo,
LoginResponseDto,
SharedLinkType,
} from '@immich/sdk';
import { createUserDto, uuidDto } from 'src/fixtures';
import { errorDto } from 'src/responses';
@@ -14,7 +15,8 @@ import { app, asBearerAuth, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
const user1SharedUser = 'user1SharedUser';
const user1SharedEditorUser = 'user1SharedEditorUser';
const user1SharedViewerUser = 'user1SharedViewerUser';
const user1SharedLink = 'user1SharedLink';
const user1NotShared = 'user1NotShared';
const user2SharedUser = 'user2SharedUser';
@@ -47,11 +49,10 @@ describe('/album', () => {
utils.createAsset(user1.accessToken),
]);
const albums = await Promise.all([
// user 1
user1Albums = await Promise.all([
utils.createAlbum(user1.accessToken, {
albumName: user1SharedUser,
sharedWithUserIds: [user2.userId],
albumName: user1SharedEditorUser,
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Editor }],
assetIds: [user1Asset1.id],
}),
utils.createAlbum(user1.accessToken, {
@@ -62,31 +63,36 @@ describe('/album', () => {
albumName: user1NotShared,
assetIds: [user1Asset1.id, user1Asset2.id],
}),
// user 2
utils.createAlbum(user2.accessToken, {
albumName: user2SharedUser,
sharedWithUserIds: [user1.userId],
}),
utils.createAlbum(user2.accessToken, { albumName: user2SharedLink }),
utils.createAlbum(user2.accessToken, { albumName: user2NotShared }),
// user 3
utils.createAlbum(user3.accessToken, {
albumName: 'Deleted',
sharedWithUserIds: [user1.userId],
utils.createAlbum(user1.accessToken, {
albumName: user1SharedViewerUser,
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }],
assetIds: [user1Asset1.id],
}),
]);
user2Albums = await Promise.all([
utils.createAlbum(user2.accessToken, {
albumName: user2SharedUser,
albumUsers: [
{ userId: user1.userId, role: AlbumUserRole.Editor },
{ userId: user3.userId, role: AlbumUserRole.Editor },
],
}),
utils.createAlbum(user2.accessToken, { albumName: user2SharedLink }),
utils.createAlbum(user2.accessToken, { albumName: user2NotShared }),
]);
await utils.createAlbum(user3.accessToken, {
albumName: 'Deleted',
albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }],
});
await addAssetsToAlbum(
{ id: albums[3].id, bulkIdsDto: { ids: [user1Asset1.id] } },
{ id: user2Albums[0].id, bulkIdsDto: { ids: [user1Asset1.id] } },
{ headers: asBearerAuth(user1.accessToken) },
);
albums[3] = await getAlbumInfo({ id: albums[3].id }, { headers: asBearerAuth(user2.accessToken) });
user1Albums = albums.slice(0, 3);
user2Albums = albums.slice(3, 6);
user2Albums[0] = await getAlbumInfo({ id: user2Albums[0].id }, { headers: asBearerAuth(user2.accessToken) });
await Promise.all([
// add shared link to user1SharedLink album
@@ -144,7 +150,7 @@ describe('/album', () => {
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toHaveLength(3);
expect(body).toHaveLength(4);
expect(body).toEqual(
expect.arrayContaining([
expect.objectContaining({
@@ -154,7 +160,12 @@ describe('/album', () => {
}),
expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedUser,
albumName: user1SharedEditorUser,
shared: true,
}),
expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedViewerUser,
shared: true,
}),
expect.objectContaining({
@@ -169,12 +180,17 @@ describe('/album', () => {
it('should return the album collection including owned and shared', async () => {
const { status, body } = await request(app).get('/album').set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toHaveLength(3);
expect(body).toHaveLength(4);
expect(body).toEqual(
expect.arrayContaining([
expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedUser,
albumName: user1SharedEditorUser,
shared: true,
}),
expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedViewerUser,
shared: true,
}),
expect.objectContaining({
@@ -196,12 +212,17 @@ describe('/album', () => {
.get('/album?shared=true')
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toHaveLength(3);
expect(body).toHaveLength(4);
expect(body).toEqual(
expect.arrayContaining([
expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedUser,
albumName: user1SharedEditorUser,
shared: true,
}),
expect.objectContaining({
ownerId: user1.userId,
albumName: user1SharedViewerUser,
shared: true,
}),
expect.objectContaining({
@@ -248,7 +269,7 @@ describe('/album', () => {
.get(`/album?shared=true&assetId=${user1Asset1.id}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toHaveLength(4);
expect(body).toHaveLength(5);
});
it('should return the album collection filtered by assetId and ignores shared=false', async () => {
@@ -256,7 +277,7 @@ describe('/album', () => {
.get(`/album?shared=false&assetId=${user1Asset1.id}`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toHaveLength(4);
expect(body).toHaveLength(5);
});
});
@@ -279,16 +300,22 @@ describe('/album', () => {
});
});
it('should return album info for shared album', async () => {
it('should return album info for shared album (editor)', async () => {
const { status, body } = await request(app)
.get(`/album/${user2Albums[0].id}?withoutAssets=false`)
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual({
...user2Albums[0],
assets: [expect.objectContaining({ id: user2Albums[0].assets[0].id })],
});
expect(body).toMatchObject({ id: user2Albums[0].id });
});
it('should return album info for shared album (viewer)', async () => {
const { status, body } = await request(app)
.get(`/album/${user1Albums[3].id}?withoutAssets=false`)
.set('Authorization', `Bearer ${user2.accessToken}`);
expect(status).toBe(200);
expect(body).toMatchObject({ id: user1Albums[3].id });
});
it('should return album info with assets when withoutAssets is undefined', async () => {
@@ -330,7 +357,7 @@ describe('/album', () => {
.set('Authorization', `Bearer ${user1.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual({ owned: 3, shared: 3, notShared: 1 });
expect(body).toEqual({ owned: 4, shared: 4, notShared: 1 });
});
});
@@ -357,6 +384,7 @@ describe('/album', () => {
albumThumbnailAssetId: null,
shared: false,
sharedUsers: [],
albumUsers: [],
hasSharedLink: false,
assets: [],
assetCount: 0,
@@ -395,6 +423,31 @@ describe('/album', () => {
expect(status).toBe(200);
expect(body).toEqual([expect.objectContaining({ id: asset.id, success: true })]);
});
it('should not be able to add assets to album as a viewer', async () => {
const asset = await utils.createAsset(user2.accessToken);
const { status, body } = await request(app)
.put(`/album/${user1Albums[3].id}/assets`)
.set('Authorization', `Bearer ${user2.accessToken}`)
.send({ ids: [asset.id] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Not found or no album.addAsset access'));
});
it('should add duplicate assets only once', async () => {
const asset = await utils.createAsset(user1.accessToken);
const { status, body } = await request(app)
.put(`/album/${user1Albums[0].id}/assets`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ ids: [asset.id, asset.id] });
expect(status).toBe(200);
expect(body).toEqual([
expect.objectContaining({ id: asset.id, success: true }),
expect.objectContaining({ id: asset.id, success: false, error: 'duplicate' }),
]);
});
});
describe('PATCH /album/:id', () => {
@@ -425,6 +478,26 @@ describe('/album', () => {
description: 'An album description',
});
});
it('should not be able to update as a viewer', async () => {
const { status, body } = await request(app)
.patch(`/album/${user1Albums[3].id}`)
.set('Authorization', `Bearer ${user2.accessToken}`)
.send({ albumName: 'New album name' });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Not found or no album.update access'));
});
it('should not be able to update as an editor', async () => {
const { status, body } = await request(app)
.patch(`/album/${user1Albums[0].id}`)
.set('Authorization', `Bearer ${user2.accessToken}`)
.send({ albumName: 'New album name' });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Not found or no album.update access'));
});
});
describe('DELETE /album/:id/assets', () => {
@@ -488,6 +561,29 @@ describe('/album', () => {
expect(status).toBe(200);
expect(body).toEqual([expect.objectContaining({ id: user1Asset1.id, success: true })]);
});
it('should not be able to remove assets from album as a viewer', async () => {
const { status, body } = await request(app)
.delete(`/album/${user1Albums[3].id}/assets`)
.set('Authorization', `Bearer ${user2.accessToken}`)
.send({ ids: [user1Asset1.id] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Not found or no album.removeAsset access'));
});
it('should remove duplicate assets only once', async () => {
const { status, body } = await request(app)
.delete(`/album/${user1Albums[1].id}/assets`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ ids: [user1Asset1.id, user1Asset1.id] });
expect(status).toBe(200);
expect(body).toEqual([
expect.objectContaining({ id: user1Asset1.id, success: true }),
expect.objectContaining({ id: user1Asset1.id, success: false, error: 'not_found' }),
]);
});
});
describe('PUT :id/users', () => {
@@ -510,7 +606,7 @@ describe('/album', () => {
const { status, body } = await request(app)
.put(`/album/${album.id}/users`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ sharedUserIds: [user2.userId] });
.send({ albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Editor }] });
expect(status).toBe(200);
expect(body).toEqual(
@@ -524,7 +620,7 @@ describe('/album', () => {
const { status, body } = await request(app)
.put(`/album/${album.id}/users`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ sharedUserIds: [user1.userId] });
.send({ albumUsers: [{ userId: user1.userId, role: AlbumUserRole.Editor }] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Cannot be shared with owner'));
@@ -534,15 +630,58 @@ describe('/album', () => {
await request(app)
.put(`/album/${album.id}/users`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ sharedUserIds: [user2.userId] });
.send({ albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Editor }] });
const { status, body } = await request(app)
.put(`/album/${album.id}/users`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ sharedUserIds: [user2.userId] });
.send({ albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Editor }] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('User already added'));
});
});
describe('PUT :id/user/:userId', () => {
it('should allow the album owner to change the role of a shared user', async () => {
const album = await utils.createAlbum(user1.accessToken, {
albumName: 'testAlbum',
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }],
});
expect(album.albumUsers[0].role).toEqual(AlbumUserRole.Viewer);
const { status } = await request(app)
.put(`/album/${album.id}/user/${user2.userId}`)
.set('Authorization', `Bearer ${user1.accessToken}`)
.send({ role: AlbumUserRole.Editor });
expect(status).toBe(200);
// Get album to verify the role change
const { body } = await request(app).get(`/album/${album.id}`).set('Authorization', `Bearer ${user1.accessToken}`);
expect(body).toEqual(
expect.objectContaining({
albumUsers: [expect.objectContaining({ role: AlbumUserRole.Editor })],
}),
);
});
it('should not allow a shared user to change the role of another shared user', async () => {
const album = await utils.createAlbum(user1.accessToken, {
albumName: 'testAlbum',
albumUsers: [{ userId: user2.userId, role: AlbumUserRole.Viewer }],
});
expect(album.albumUsers[0].role).toEqual(AlbumUserRole.Viewer);
const { status, body } = await request(app)
.put(`/album/${album.id}/user/${user2.userId}`)
.set('Authorization', `Bearer ${user2.accessToken}`)
.send({ role: AlbumUserRole.Editor });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Not found or no album.share access'));
});
});
});

View File

@@ -2,10 +2,8 @@ import {
AssetFileUploadResponseDto,
AssetResponseDto,
AssetTypeEnum,
LibraryResponseDto,
LoginResponseDto,
SharedLinkType,
getAllLibraries,
getAssetInfo,
updateAssets,
} from '@immich/sdk';
@@ -102,7 +100,6 @@ describe('/asset', () => {
utils.createAsset(user1.accessToken),
utils.createAsset(user1.accessToken, {
isFavorite: true,
isReadOnly: true,
fileCreatedAt: yesterday.toISO(),
fileModifiedAt: yesterday.toISO(),
assetData: { filename: 'example.mp4' },
@@ -572,6 +569,22 @@ describe('/asset', () => {
}
const tests = [
{
input: 'formats/avif/8bit-sRGB.avif',
expected: {
type: AssetTypeEnum.Image,
originalFileName: '8bit-sRGB.avif',
resized: true,
exifInfo: {
description: '',
exifImageHeight: 1080,
exifImageWidth: 1617,
fileSizeInByte: 862_424,
latitude: null,
longitude: null,
},
},
},
{
input: 'formats/jpg/el_torcal_rocks.jpg',
expected: {
@@ -596,6 +609,22 @@ describe('/asset', () => {
},
},
},
{
input: 'formats/jxl/8bit-sRGB.jxl',
expected: {
type: AssetTypeEnum.Image,
originalFileName: '8bit-sRGB.jxl',
resized: true,
exifInfo: {
description: '',
exifImageHeight: 1080,
exifImageWidth: 1440,
fileSizeInByte: 1_780_777,
latitude: null,
longitude: null,
},
},
},
{
input: 'formats/heic/IMG_2682.heic',
expected: {
@@ -681,6 +710,80 @@ describe('/asset', () => {
},
},
},
{
input: 'formats/raw/Panasonic/DMC-GH4/4_3.rw2',
expected: {
type: AssetTypeEnum.Image,
originalFileName: '4_3.rw2',
resized: true,
fileCreatedAt: '2018-05-10T08:42:37.842Z',
exifInfo: {
make: 'Panasonic',
model: 'DMC-GH4',
exifImageHeight: 3456,
exifImageWidth: 4608,
exposureTime: '1/100',
fNumber: 3.2,
focalLength: 35,
iso: 400,
fileSizeInByte: 19_587_072,
dateTimeOriginal: '2018-05-10T08:42:37.842Z',
latitude: null,
longitude: null,
orientation: '1',
},
},
},
{
input: 'formats/raw/Sony/ILCE-6300/12bit-compressed-(3_2).arw',
expected: {
type: AssetTypeEnum.Image,
originalFileName: '12bit-compressed-(3_2).arw',
resized: true,
fileCreatedAt: '2016-09-27T10:51:44.000Z',
exifInfo: {
make: 'SONY',
model: 'ILCE-6300',
exifImageHeight: 4024,
exifImageWidth: 6048,
exposureTime: '1/320',
fNumber: 8,
focalLength: 97,
iso: 100,
lensModel: 'E PZ 18-105mm F4 G OSS',
fileSizeInByte: 25_001_984,
dateTimeOriginal: '2016-09-27T10:51:44.000Z',
latitude: null,
longitude: null,
orientation: '1',
},
},
},
{
input: 'formats/raw/Sony/ILCE-7M2/14bit-uncompressed-(3_2).arw',
expected: {
type: AssetTypeEnum.Image,
originalFileName: '14bit-uncompressed-(3_2).arw',
resized: true,
fileCreatedAt: '2016-01-08T15:08:01.000Z',
exifInfo: {
make: 'SONY',
model: 'ILCE-7M2',
exifImageHeight: 4024,
exifImageWidth: 6048,
exposureTime: '1.3',
fNumber: 22,
focalLength: 25,
iso: 100,
lensModel: 'E 25mm F2',
fileSizeInByte: 49_512_448,
dateTimeOriginal: '2016-01-08T15:08:01.000Z',
latitude: null,
longitude: null,
orientation: '1',
},
},
},
];
for (const { input, expected } of tests) {
@@ -714,25 +817,6 @@ describe('/asset', () => {
expect(duplicate).toBe(true);
});
it("should not upload to another user's library", async () => {
const libraries = await getAllLibraries({}, { headers: asBearerAuth(admin.accessToken) });
const library = libraries.find((library) => library.ownerId === user1.userId) as LibraryResponseDto;
const { body, status } = await request(app)
.post('/asset/upload')
.set('Authorization', `Bearer ${admin.accessToken}`)
.field('libraryId', library.id)
.field('deviceAssetId', 'example-image')
.field('deviceId', 'e2e')
.field('fileCreatedAt', new Date().toISOString())
.field('fileModifiedAt', new Date().toISOString())
.field('duration', '0:00:00.000000')
.attach('assetData', makeRandomImage(), 'example.png');
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Not found or no asset.upload access'));
});
it('should update the used quota', async () => {
const { body, status } = await request(app)
.post('/asset/upload')
@@ -900,7 +984,7 @@ describe('/asset', () => {
id: expect.any(String),
lat: expect.closeTo(39.115),
lon: expect.closeTo(-108.400_968),
state: 'Mesa County, Colorado',
state: 'Colorado',
},
{
city: 'Ralston',
@@ -908,7 +992,7 @@ describe('/asset', () => {
id: expect.any(String),
lat: expect.closeTo(41.2203),
lon: expect.closeTo(-96.071_625),
state: 'Douglas County, Nebraska',
state: 'Nebraska',
},
]);
});
@@ -927,7 +1011,7 @@ describe('/asset', () => {
id: expect.any(String),
lat: expect.closeTo(39.115),
lon: expect.closeTo(-108.400_968),
state: 'Mesa County, Colorado',
state: 'Colorado',
},
{
city: 'Ralston',
@@ -935,7 +1019,7 @@ describe('/asset', () => {
id: expect.any(String),
lat: expect.closeTo(41.2203),
lon: expect.closeTo(-96.071_625),
state: 'Douglas County, Nebraska',
state: 'Nebraska',
},
]);
});

View File

@@ -1,11 +1,4 @@
import {
LibraryResponseDto,
LibraryType,
LoginResponseDto,
ScanLibraryDto,
getAllLibraries,
scanLibrary,
} from '@immich/sdk';
import { LibraryResponseDto, LoginResponseDto, ScanLibraryDto, getAllLibraries, scanLibrary } from '@immich/sdk';
import { cpSync, existsSync } from 'node:fs';
import { Socket } from 'socket.io-client';
import { userDto, uuidDto } from 'src/fixtures';
@@ -29,7 +22,7 @@ describe('/library', () => {
admin = await utils.adminSetup();
await utils.resetAdminConfig(admin.accessToken);
user = await utils.userSetup(admin.accessToken, userDto.user1);
library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId, type: LibraryType.External });
library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId });
websocket = await utils.connectWebsocket(admin.accessToken);
utils.createImageFile(`${testAssetDir}/temp/directoryA/assetA.png`);
utils.createImageFile(`${testAssetDir}/temp/directoryB/assetB.png`);
@@ -50,24 +43,6 @@ describe('/library', () => {
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should start with a default upload library', async () => {
const { status, body } = await request(app).get('/library').set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual(
expect.arrayContaining([
expect.objectContaining({
ownerId: admin.userId,
type: LibraryType.Upload,
name: 'Default Library',
refreshedAt: null,
assetCount: 0,
importPaths: [],
exclusionPatterns: [],
}),
]),
);
});
});
describe('POST /library', () => {
@@ -81,7 +56,7 @@ describe('/library', () => {
const { status, body } = await request(app)
.post('/library')
.set('Authorization', `Bearer ${user.accessToken}`)
.send({ ownerId: admin.userId, type: LibraryType.External });
.send({ ownerId: admin.userId });
expect(status).toBe(403);
expect(body).toEqual(errorDto.forbidden);
@@ -91,13 +66,12 @@ describe('/library', () => {
const { status, body } = await request(app)
.post('/library')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ ownerId: admin.userId, type: LibraryType.External });
.send({ ownerId: admin.userId });
expect(status).toBe(201);
expect(body).toEqual(
expect.objectContaining({
ownerId: admin.userId,
type: LibraryType.External,
name: 'New External Library',
refreshedAt: null,
assetCount: 0,
@@ -113,7 +87,6 @@ describe('/library', () => {
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({
ownerId: admin.userId,
type: LibraryType.External,
name: 'My Awesome Library',
importPaths: ['/path/to/import'],
exclusionPatterns: ['**/Raw/**'],
@@ -134,7 +107,6 @@ describe('/library', () => {
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({
ownerId: admin.userId,
type: LibraryType.External,
name: 'My Awesome Library',
importPaths: ['/path', '/path'],
exclusionPatterns: ['**/Raw/**'],
@@ -150,7 +122,6 @@ describe('/library', () => {
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({
ownerId: admin.userId,
type: LibraryType.External,
name: 'My Awesome Library',
importPaths: ['/path/to/import'],
exclusionPatterns: ['**/Raw/**', '**/Raw/**'],
@@ -159,60 +130,6 @@ describe('/library', () => {
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(["All exclusionPatterns's elements must be unique"]));
});
it('should create an upload library with defaults', async () => {
const { status, body } = await request(app)
.post('/library')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ ownerId: admin.userId, type: LibraryType.Upload });
expect(status).toBe(201);
expect(body).toEqual(
expect.objectContaining({
ownerId: admin.userId,
type: LibraryType.Upload,
name: 'New Upload Library',
refreshedAt: null,
assetCount: 0,
importPaths: [],
exclusionPatterns: [],
}),
);
});
it('should create an upload library with options', async () => {
const { status, body } = await request(app)
.post('/library')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ ownerId: admin.userId, type: LibraryType.Upload, name: 'My Awesome Library' });
expect(status).toBe(201);
expect(body).toEqual(
expect.objectContaining({
name: 'My Awesome Library',
}),
);
});
it('should not allow upload libraries to have import paths', async () => {
const { status, body } = await request(app)
.post('/library')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ ownerId: admin.userId, type: LibraryType.Upload, importPaths: ['/path/to/import'] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Upload libraries cannot have import paths'));
});
it('should not allow upload libraries to have exclusion patterns', async () => {
const { status, body } = await request(app)
.post('/library')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ ownerId: admin.userId, type: LibraryType.Upload, exclusionPatterns: ['**/Raw/**'] });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Upload libraries cannot have exclusion patterns'));
});
});
describe('PUT /library/:id', () => {
@@ -332,10 +249,7 @@ describe('/library', () => {
});
it('should get library by id', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
});
const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId });
const { status, body } = await request(app)
.get(`/library/${library.id}`)
@@ -345,7 +259,6 @@ describe('/library', () => {
expect(body).toEqual(
expect.objectContaining({
ownerId: admin.userId,
type: LibraryType.External,
name: 'New External Library',
refreshedAt: null,
assetCount: 0,
@@ -373,24 +286,9 @@ describe('/library', () => {
expect(body).toEqual(errorDto.unauthorized);
});
it('should not scan an upload library', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.Upload,
});
const { status, body } = await request(app)
.post(`/library/${library.id}/scan`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest('Can only refresh external libraries'));
});
it('should scan external library', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp/directoryA`],
});
@@ -406,7 +304,6 @@ describe('/library', () => {
it('should scan external library with exclusion pattern', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp`],
exclusionPatterns: ['**/directoryA'],
});
@@ -423,7 +320,6 @@ describe('/library', () => {
it('should scan multiple import paths', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp/directoryA`, `${testAssetDirInternal}/temp/directoryB`],
});
@@ -440,7 +336,6 @@ describe('/library', () => {
it('should pick up new files', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp`],
});
@@ -466,7 +361,6 @@ describe('/library', () => {
utils.createImageFile(`${testAssetDir}/temp/directoryA/assetB.png`);
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp`],
});
@@ -493,7 +387,6 @@ describe('/library', () => {
it('should scan new files', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp`],
});
@@ -521,7 +414,6 @@ describe('/library', () => {
it('should reimport modified files', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp`],
});
@@ -549,7 +441,6 @@ describe('/library', () => {
it('should not reimport unmodified files', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp`],
});
@@ -579,7 +470,6 @@ describe('/library', () => {
it('should reimport all files', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp`],
});
@@ -617,7 +507,6 @@ describe('/library', () => {
it('should remove offline files', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp`],
});
@@ -658,7 +547,6 @@ describe('/library', () => {
it('should not remove online files', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp`],
});
@@ -737,37 +625,8 @@ describe('/library', () => {
expect(body).toEqual(errorDto.unauthorized);
});
it('should not delete the last upload library', async () => {
const libraries = await getAllLibraries(
{ $type: LibraryType.Upload },
{ headers: asBearerAuth(admin.accessToken) },
);
const adminLibraries = libraries.filter((library) => library.ownerId === admin.userId);
expect(adminLibraries.length).toBeGreaterThanOrEqual(1);
const lastLibrary = adminLibraries.pop() as LibraryResponseDto;
// delete all but the last upload library
for (const library of adminLibraries) {
const { status } = await request(app)
.delete(`/library/${library.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(204);
}
const { status, body } = await request(app)
.delete(`/library/${lastLibrary.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(body).toEqual(errorDto.noDeleteUploadLibrary);
expect(status).toBe(400);
});
it('should delete an external library', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
});
const library = await utils.createLibrary(admin.accessToken, { ownerId: admin.userId });
const { status, body } = await request(app)
.delete(`/library/${library.id}`)
@@ -776,7 +635,7 @@ describe('/library', () => {
expect(status).toBe(204);
expect(body).toEqual({});
const libraries = await getAllLibraries({}, { headers: asBearerAuth(admin.accessToken) });
const libraries = await getAllLibraries({ headers: asBearerAuth(admin.accessToken) });
expect(libraries).not.toEqual(
expect.arrayContaining([
expect.objectContaining({
@@ -789,7 +648,6 @@ describe('/library', () => {
it('should delete an external library with assets', async () => {
const library = await utils.createLibrary(admin.accessToken, {
ownerId: admin.userId,
type: LibraryType.External,
importPaths: [`${testAssetDirInternal}/temp`],
});
@@ -803,7 +661,7 @@ describe('/library', () => {
expect(status).toBe(204);
expect(body).toEqual({});
const libraries = await getAllLibraries({}, { headers: asBearerAuth(admin.accessToken) });
const libraries = await getAllLibraries({ headers: asBearerAuth(admin.accessToken) });
expect(libraries).not.toEqual(
expect.arrayContaining([
expect.objectContaining({

View File

@@ -24,12 +24,12 @@ describe('/search', () => {
// let assetRidge: AssetFileUploadResponseDto;
// let assetPolemonium: AssetFileUploadResponseDto;
// let assetWood: AssetFileUploadResponseDto;
// let assetGlarus: AssetFileUploadResponseDto;
let assetHeic: AssetFileUploadResponseDto;
let assetRocks: AssetFileUploadResponseDto;
let assetOneJpg6: AssetFileUploadResponseDto;
let assetOneHeic6: AssetFileUploadResponseDto;
let assetOneJpg5: AssetFileUploadResponseDto;
let assetGlarus: AssetFileUploadResponseDto;
let assetSprings: AssetFileUploadResponseDto;
let assetLast: AssetFileUploadResponseDto;
let cities: string[];
@@ -52,11 +52,12 @@ describe('/search', () => {
{ filename: '/formats/motionphoto/Samsung One UI 6.jpg' },
{ filename: '/formats/motionphoto/Samsung One UI 6.heic' },
{ filename: '/formats/motionphoto/Samsung One UI 5.jpg' },
{ filename: '/formats/raw/Nikon/D80/glarus.nef', dto: { isReadOnly: true } },
{ filename: '/metadata/gps-position/thompson-springs.jpg', dto: { isArchived: true } },
// used for search suggestions
{ filename: '/formats/png/density_plot.png' },
{ filename: '/formats/raw/Nikon/D80/glarus.nef' },
{ filename: '/formats/raw/Nikon/D700/philadelphia.nef' },
{ filename: '/albums/nature/orychophragmus_violaceus.jpg' },
{ filename: '/albums/nature/tanners_ridge.jpg' },
@@ -93,9 +94,9 @@ describe('/search', () => {
{ latitude: 23.133_02, longitude: -82.383_04 }, // havana
{ latitude: 41.694_11, longitude: 44.833_68 }, // tbilisi
{ latitude: 31.222_22, longitude: 121.458_06 }, // shanghai
{ latitude: 47.040_57, longitude: 9.068_04 }, // glarus
{ latitude: 38.9711, longitude: -109.7137 }, // thompson springs
{ latitude: 40.714_27, longitude: -74.005_97 }, // new york
{ latitude: 47.040_57, longitude: 9.068_04 }, // glarus
{ latitude: 32.771_52, longitude: -89.116_73 }, // philadelphia
{ latitude: 31.634_16, longitude: -7.999_94 }, // marrakesh
{ latitude: 38.523_735_4, longitude: -78.488_619_4 }, // tanners ridge
@@ -123,9 +124,9 @@ describe('/search', () => {
assetOneJpg6,
assetOneHeic6,
assetOneJpg5,
assetGlarus,
assetSprings,
assetDensity,
// assetGlarus,
// assetPhiladelphia,
// assetOrychophragmus,
// assetRidge,
@@ -190,16 +191,7 @@ describe('/search', () => {
dto: { size: -1.5 },
expected: ['size must not be less than 1', 'size must be an integer number'],
},
...[
'isArchived',
'isFavorite',
'isReadOnly',
'isExternal',
'isEncoded',
'isMotion',
'isOffline',
'isVisible',
].map((value) => ({
...['isArchived', 'isFavorite', 'isEncoded', 'isMotion', 'isOffline', 'isVisible'].map((value) => ({
should: `should reject ${value} not a boolean`,
dto: { [value]: 'immich' },
expected: [`${value} must be a boolean value`],
@@ -255,14 +247,6 @@ describe('/search', () => {
should: 'should search by isArchived (false)',
deferred: () => ({ dto: { size: 1, isArchived: false }, assets: [assetLast] }),
},
{
should: 'should search by isReadOnly (true)',
deferred: () => ({ dto: { isReadOnly: true }, assets: [assetGlarus] }),
},
{
should: 'should search by isReadOnly (false)',
deferred: () => ({ dto: { size: 1, isReadOnly: false }, assets: [assetLast] }),
},
{
should: 'should search by type (image)',
deferred: () => ({ dto: { size: 1, type: 'IMAGE' }, assets: [assetLast] }),
@@ -469,7 +453,8 @@ describe('/search', () => {
const { status, body } = await request(app)
.get('/search/suggestions?type=state')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(body).toEqual(states);
expect(body).toHaveLength(states.length);
expect(body).toEqual(expect.arrayContaining(states));
expect(status).toBe(200);
});

View File

@@ -1,4 +1,4 @@
import { LoginResponseDto, getServerConfig } from '@immich/sdk';
import { LoginResponseDto } from '@immich/sdk';
import { createUserDto } from 'src/fixtures';
import { errorDto } from 'src/responses';
import { app, utils } from 'src/utils';
@@ -66,6 +66,7 @@ describe('/server-info', () => {
expect(body).toEqual({
smartSearch: false,
configFile: false,
duplicateDetection: false,
facialRecognition: false,
map: true,
reverseGeocoding: true,
@@ -75,6 +76,7 @@ describe('/server-info', () => {
search: true,
sidecar: true,
trash: true,
email: false,
});
});
});
@@ -162,19 +164,4 @@ describe('/server-info', () => {
});
});
});
describe('POST /server-info/admin-onboarding', () => {
it('should set admin onboarding', async () => {
const config = await getServerConfig({});
expect(config.isOnboarded).toBe(false);
const { status } = await request(app)
.post('/server-info/admin-onboarding')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(204);
const newConfig = await getServerConfig({});
expect(newConfig.isOnboarded).toBe(true);
});
});
});

View File

@@ -9,7 +9,7 @@ import {
} from '@immich/sdk';
import { createUserDto, uuidDto } from 'src/fixtures';
import { errorDto } from 'src/responses';
import { app, asBearerAuth, utils } from 'src/utils';
import { app, asBearerAuth, shareUrl, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, describe, expect, it } from 'vitest';
@@ -89,6 +89,31 @@ describe('/shared-link', () => {
await deleteUser({ id: user2.userId, deleteUserDto: {} }, { headers: asBearerAuth(admin.accessToken) });
});
describe('GET /share/${key}', () => {
it('should have correct asset count in meta tag for non-empty album', async () => {
const resp = await request(shareUrl).get(`/${linkWithMetadata.key}`);
expect(resp.status).toBe(200);
expect(resp.header['content-type']).toContain('text/html');
expect(resp.text).toContain(
`<meta name="description" content="${metadataAlbum.assets.length} shared photos & videos" />`,
);
});
it('should have correct asset count in meta tag for empty album', async () => {
const resp = await request(shareUrl).get(`/${linkWithAlbum.key}`);
expect(resp.status).toBe(200);
expect(resp.header['content-type']).toContain('text/html');
expect(resp.text).toContain(`<meta name="description" content="0 shared photos & videos" />`);
});
it('should have correct asset count in meta tag for shared asset', async () => {
const resp = await request(shareUrl).get(`/${linkWithAssets.key}`);
expect(resp.status).toBe(200);
expect(resp.header['content-type']).toContain('text/html');
expect(resp.text).toContain(`<meta name="description" content="1 shared photos & videos" />`);
});
});
describe('GET /shared-link', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/shared-link');

View File

@@ -86,6 +86,26 @@ describe('/system-config', () => {
expect(body).toEqual(errorDto.unauthorized);
});
it('should always return the new config', async () => {
const config = await getSystemConfig(admin.accessToken);
const response1 = await request(app)
.put('/system-config')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ ...config, newVersionCheck: { enabled: false } });
expect(response1.status).toBe(200);
expect(response1.body).toEqual({ ...config, newVersionCheck: { enabled: false } });
const response2 = await request(app)
.put('/system-config')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ ...config, newVersionCheck: { enabled: true } });
expect(response2.status).toBe(200);
expect(response2.body).toEqual({ ...config, newVersionCheck: { enabled: true } });
});
it('should reject an invalid config entry', async () => {
const { status, body } = await request(app)
.put('/system-config')

View File

@@ -0,0 +1,76 @@
import { LoginResponseDto, getServerConfig } from '@immich/sdk';
import { createUserDto } from 'src/fixtures';
import { errorDto } from 'src/responses';
import { app, utils } from 'src/utils';
import request from 'supertest';
import { beforeAll, describe, expect, it } from 'vitest';
describe('/server-info', () => {
let admin: LoginResponseDto;
let nonAdmin: LoginResponseDto;
beforeAll(async () => {
await utils.resetDatabase();
admin = await utils.adminSetup({ onboarding: false });
nonAdmin = await utils.userSetup(admin.accessToken, createUserDto.user1);
});
describe('POST /system-metadata/admin-onboarding', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).post('/system-metadata/admin-onboarding').send({ isOnboarded: true });
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should only work for admins', async () => {
const { status, body } = await request(app)
.post('/system-metadata/admin-onboarding')
.set('Authorization', `Bearer ${nonAdmin.accessToken}`)
.send({ isOnboarded: true });
expect(status).toBe(403);
expect(body).toEqual(errorDto.forbidden);
});
it('should set admin onboarding', async () => {
const config = await getServerConfig({});
expect(config.isOnboarded).toBe(false);
const { status } = await request(app)
.post('/system-metadata/admin-onboarding')
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ isOnboarded: true });
expect(status).toBe(204);
const newConfig = await getServerConfig({});
expect(newConfig.isOnboarded).toBe(true);
});
});
describe('GET /system-metadata/reverse-geocoding-state', () => {
it('should require authentication', async () => {
const { status, body } = await request(app).get('/system-metadata/reverse-geocoding-state');
expect(status).toBe(401);
expect(body).toEqual(errorDto.unauthorized);
});
it('should only work for admins', async () => {
const { status, body } = await request(app)
.get('/system-metadata/reverse-geocoding-state')
.set('Authorization', `Bearer ${nonAdmin.accessToken}`);
expect(status).toBe(403);
expect(body).toEqual(errorDto.forbidden);
});
it('should get the reverse geocoding state', async () => {
const { status, body } = await request(app)
.get('/system-metadata/reverse-geocoding-state')
.set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(200);
expect(body).toEqual({
lastUpdate: expect.any(String),
lastImportFileName: 'cities500.txt',
});
});
});
});

View File

@@ -34,7 +34,6 @@ describe('/timeline', () => {
utils.createAsset(user.accessToken),
utils.createAsset(user.accessToken, {
isFavorite: true,
isReadOnly: true,
fileCreatedAt: yesterday.toISO(),
fileModifiedAt: yesterday.toISO(),
assetData: { filename: 'example.mp4' },

View File

@@ -32,8 +32,7 @@ describe('/trash', () => {
await utils.deleteAssets(admin.accessToken, [assetId]);
const before = await getAllAssets({}, { headers: asBearerAuth(admin.accessToken) });
expect(before.length).toBeGreaterThanOrEqual(1);
expect(before).toStrictEqual([expect.objectContaining({ id: assetId, isTrashed: true })]);
const { status } = await request(app).post('/trash/empty').set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(204);
@@ -57,14 +56,14 @@ describe('/trash', () => {
const { id: assetId } = await utils.createAsset(admin.accessToken);
await utils.deleteAssets(admin.accessToken, [assetId]);
const before = await utils.getAssetInfo(admin.accessToken, assetId);
expect(before.isTrashed).toBe(true);
const before = await getAllAssets({}, { headers: asBearerAuth(admin.accessToken) });
expect(before).toStrictEqual([expect.objectContaining({ id: assetId, isTrashed: true })]);
const { status } = await request(app).post('/trash/restore').set('Authorization', `Bearer ${admin.accessToken}`);
expect(status).toBe(204);
const after = await utils.getAssetInfo(admin.accessToken, assetId);
expect(after.isTrashed).toBe(false);
const after = await getAllAssets({}, { headers: asBearerAuth(admin.accessToken) });
expect(after).toStrictEqual([expect.objectContaining({ id: assetId, isTrashed: false })]);
});
});

View File

@@ -51,7 +51,7 @@ describe(`immich upload`, () => {
expect.stringContaining('All assets were already uploaded, nothing to do'),
]),
);
expect(first.exitCode).toBe(0);
expect(second.exitCode).toBe(0);
});
it('should skip files that do not exist', async () => {
@@ -63,6 +63,44 @@ describe(`immich upload`, () => {
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(0);
});
it('should have accurate dry run', async () => {
const { stderr, stdout, exitCode } = await immichCli([
'upload',
`${testAssetDir}/albums/nature/silver_fir.jpg`,
'--dry-run',
]);
expect(stderr).toBe('');
expect(stdout.split('\n')).toEqual(
expect.arrayContaining([expect.stringContaining('Would have uploaded 1 asset')]),
);
expect(exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(0);
});
it('dry run should handle duplicates', async () => {
const first = await immichCli(['upload', `${testAssetDir}/albums/nature/silver_fir.jpg`]);
expect(first.stderr).toBe('');
expect(first.stdout.split('\n')).toEqual(
expect.arrayContaining([expect.stringContaining('Successfully uploaded 1 new asset')]),
);
expect(first.exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(1);
const second = await immichCli(['upload', `${testAssetDir}/albums/nature/`, '--dry-run']);
expect(second.stderr).toBe('');
expect(second.stdout.split('\n')).toEqual(
expect.arrayContaining([
expect.stringContaining('Found 8 new files and 1 duplicate'),
expect.stringContaining('Would have uploaded 8 assets'),
]),
);
expect(second.exitCode).toBe(0);
});
});
describe('immich upload --recursive', () => {
@@ -136,6 +174,31 @@ describe(`immich upload`, () => {
expect(albums2.length).toBe(1);
expect(albums2[0].albumName).toBe('nature');
});
it('should have accurate dry run', async () => {
const { stderr, stdout, exitCode } = await immichCli([
'upload',
`${testAssetDir}/albums/nature/`,
'--recursive',
'--album',
'--dry-run',
]);
expect(stdout.split('\n')).toEqual(
expect.arrayContaining([
expect.stringContaining('Would have uploaded 9 assets'),
expect.stringContaining('Would have created 1 new album'),
expect.stringContaining('Would have updated albums of 9 assets'),
]),
);
expect(stderr).toBe('');
expect(exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(0);
const albums = await getAllAlbums({}, { headers: asKeyAuth(key) });
expect(albums.length).toBe(0);
});
});
describe('immich upload --recursive --album-name=e2e', () => {
@@ -163,6 +226,31 @@ describe(`immich upload`, () => {
expect(albums.length).toBe(1);
expect(albums[0].albumName).toBe('e2e');
});
it('should have accurate dry run', async () => {
const { stderr, stdout, exitCode } = await immichCli([
'upload',
`${testAssetDir}/albums/nature/`,
'--recursive',
'--album-name=e2e',
'--dry-run',
]);
expect(stdout.split('\n')).toEqual(
expect.arrayContaining([
expect.stringContaining('Would have uploaded 9 assets'),
expect.stringContaining('Would have created 1 new album'),
expect.stringContaining('Would have updated albums of 9 assets'),
]),
);
expect(stderr).toBe('');
expect(exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(0);
const albums = await getAllAlbums({}, { headers: asKeyAuth(key) });
expect(albums.length).toBe(0);
});
});
describe('immich upload --delete', () => {
@@ -191,6 +279,32 @@ describe(`immich upload`, () => {
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(9);
});
it('should have accurate dry run', async () => {
await mkdir(`/tmp/albums/nature`, { recursive: true });
const filesToLink = await readdir(`${testAssetDir}/albums/nature`);
for (const file of filesToLink) {
await symlink(`${testAssetDir}/albums/nature/${file}`, `/tmp/albums/nature/${file}`);
}
const { stderr, stdout, exitCode } = await immichCli(['upload', `/tmp/albums/nature`, '--delete', '--dry-run']);
const files = await readdir(`/tmp/albums/nature`);
await rm(`/tmp/albums/nature`, { recursive: true });
expect(files.length).toBe(9);
expect(stdout.split('\n')).toEqual(
expect.arrayContaining([
expect.stringContaining('Would have uploaded 9 assets'),
expect.stringContaining('Would have deleted 9 local assets'),
]),
);
expect(stderr).toBe('');
expect(exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(0);
});
});
describe('immich upload --skip-hash', () => {
@@ -217,6 +331,22 @@ describe(`immich upload`, () => {
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(1);
});
it('should throw an error if attempting dry run', async () => {
const { stderr, stdout, exitCode } = await immichCli([
'upload',
`${testAssetDir}/albums/nature/`,
'--skip-hash',
'--dry-run',
]);
expect(stdout).toBe('');
expect(stderr).toEqual(`error: option '-n, --dry-run' cannot be used with option '-h, --skip-hash'`);
expect(exitCode).not.toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(0);
});
});
describe('immich upload --concurrency <number>', () => {
@@ -259,4 +389,70 @@ describe(`immich upload`, () => {
expect(exitCode).not.toBe(0);
});
});
describe('immich upload --ignore <pattern>', () => {
it('should work', async () => {
const { stderr, stdout, exitCode } = await immichCli([
'upload',
`${testAssetDir}/albums/nature/`,
'--ignore',
'silver_fir.jpg',
]);
expect(stderr).toBe('');
expect(stdout.split('\n')).toEqual(
expect.arrayContaining([
'Found 8 new files and 0 duplicates',
expect.stringContaining('Successfully uploaded 8 new assets'),
]),
);
expect(exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(8);
});
it('should ignore assets matching glob pattern', async () => {
const { stderr, stdout, exitCode } = await immichCli([
'upload',
`${testAssetDir}/albums/nature/`,
'--ignore',
'!(*_*_*).jpg',
]);
expect(stderr).toBe('');
expect(stdout.split('\n')).toEqual(
expect.arrayContaining([
'Found 1 new files and 0 duplicates',
expect.stringContaining('Successfully uploaded 1 new asset'),
]),
);
expect(exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(1);
});
it('should have accurate dry run', async () => {
const { stderr, stdout, exitCode } = await immichCli([
'upload',
`${testAssetDir}/albums/nature/`,
'--ignore',
'silver_fir.jpg',
'--dry-run',
]);
expect(stderr).toBe('');
expect(stdout.split('\n')).toEqual(
expect.arrayContaining([
'Found 8 new files and 0 duplicates',
expect.stringContaining('Would have uploaded 8 assets'),
]),
);
expect(exitCode).toBe(0);
const assets = await getAllAssets({}, { headers: asKeyAuth(key) });
expect(assets.length).toBe(0);
});
});
});

View File

@@ -51,11 +51,6 @@ export const errorDto = {
statusCode: 400,
message: 'The server already has an admin',
},
noDeleteUploadLibrary: {
error: 'Bad Request',
statusCode: 400,
message: 'Cannot delete the last upload library',
},
};
export const signupResponseDto = {

View File

@@ -17,7 +17,7 @@ const setup = async () => {
child.stdout.on('data', (data) => {
const input = data.toString();
console.log(input);
if (input.includes('Immich Microservices is listening')) {
if (input.includes('Immich Microservices is running')) {
_resolve();
}
});

View File

@@ -16,7 +16,6 @@ import {
createPerson,
createSharedLink,
createUser,
defaults,
deleteAssets,
getAllAssets,
getAllJobsStatus,
@@ -24,8 +23,10 @@ import {
getConfigDefaults,
login,
searchMetadata,
setAdminOnboarding,
setBaseUrl,
signUpAdmin,
updateAdminOnboarding,
updateAlbumUser,
updateConfig,
validate,
} from '@immich/sdk';
@@ -52,10 +53,11 @@ type AssetData = { bytes?: Buffer; filename: string };
const dbUrl = 'postgres://postgres:postgres@127.0.0.1:5433/immich';
const baseUrl = 'http://127.0.0.1:2283';
export const shareUrl = `${baseUrl}/share`;
export const app = `${baseUrl}/api`;
// TODO move test assets into e2e/assets
export const testAssetDir = path.resolve(`./../server/test/assets/`);
export const testAssetDirInternal = '/data/assets';
export const testAssetDir = path.resolve('./test-assets');
export const testAssetDirInternal = '/test-assets';
export const tempDir = tmpdir();
export const asBearerAuth = (accessToken: string) => ({ Authorization: `Bearer ${accessToken}` });
export const asKeyAuth = (key: string) => ({ 'x-api-key': key });
@@ -143,7 +145,6 @@ export const utils = {
'sessions',
'users',
'system_metadata',
'system_config',
];
const sql: string[] = [];
@@ -254,8 +255,8 @@ export const utils = {
});
},
setApiEndpoint: () => {
defaults.baseUrl = app;
initSdk: () => {
setBaseUrl(app);
},
adminSetup: async (options?: AdminSetupOptions) => {
@@ -264,7 +265,10 @@ export const utils = {
await signUpAdmin({ signUpDto: signupDto.admin });
const response = await login({ loginCredentialDto: loginDto.admin });
if (options.onboarding) {
await setAdminOnboarding({ headers: asBearerAuth(response.accessToken) });
await updateAdminOnboarding(
{ adminOnboardingUpdateDto: { isOnboarded: true } },
{ headers: asBearerAuth(response.accessToken) },
);
}
return response;
},
@@ -283,6 +287,9 @@ export const utils = {
createAlbum: (accessToken: string, dto: CreateAlbumDto) =>
createAlbum({ createAlbumDto: dto }, { headers: asBearerAuth(accessToken) }),
updateAlbumUser: (accessToken: string, args: Parameters<typeof updateAlbumUser>[0]) =>
updateAlbumUser(args, { headers: asBearerAuth(accessToken) }),
createAsset: async (
accessToken: string,
dto?: Partial<Omit<CreateAssetDto, 'assetData'>> & { assetData?: AssetData },
@@ -455,7 +462,7 @@ export const utils = {
},
};
utils.setApiEndpoint();
utils.initSdk();
if (!existsSync(`${testAssetDir}/albums`)) {
throw new Error(

View File

@@ -3,7 +3,7 @@ import { utils } from 'src/utils';
test.describe('Registration', () => {
test.beforeAll(() => {
utils.setApiEndpoint();
utils.initSdk();
});
test.beforeEach(async () => {

View File

@@ -17,7 +17,7 @@ test.describe('Shared Links', () => {
let sharedLinkPassword: SharedLinkResponseDto;
test.beforeAll(async () => {
utils.setApiEndpoint();
utils.initSdk();
await utils.resetDatabase();
admin = await utils.adminSetup();
asset = await utils.createAsset(admin.accessToken);

1
e2e/test-assets Submodule

Submodule e2e/test-assets added at 625ec3a5e9

View File

@@ -2,14 +2,15 @@
set -o nounset
set -o pipefail
create_immich_directory() { local -r Tgt='./immich-app'
create_immich_directory() {
local -r Tgt='./immich-app'
echo "Creating Immich directory..."
if [[ -e $Tgt ]]; then
echo "Found existing directory $Tgt, will overwrite YAML files"
else
mkdir "$Tgt" || return
fi
cd "$Tgt" || return
fi
cd "$Tgt" || return 1
}
download_docker_compose_file() {
@@ -22,6 +23,16 @@ download_dot_env_file() {
"${Curl[@]}" "$RepoUrl"/example.env -o ./.env
}
generate_random_password() {
echo "Generate random password for .env file..."
rand_pass=$(echo "$RANDOM$(date)$RANDOM" | sha256sum | base64 | head -c10)
if [ -z "$rand_pass" ]; then
sed -i -e "s/DB_PASSWORD=postgres/DB_PASSWORD=postgres${RANDOM}${RANDOM}/" ./.env
else
sed -i -e "s/DB_PASSWORD=postgres/DB_PASSWORD=${rand_pass}/" ./.env
fi
}
start_docker_compose() {
echo "Starting Immich's docker containers"
@@ -40,16 +51,16 @@ start_docker_compose() {
show_friendly_message() {
local ip_address
ip_address=$(hostname -I | awk '{print $1}')
cat << EOF
cat <<EOF
Successfully deployed Immich!
You can access the website at http://$ip_address:2283 and the server URL for the mobile app is http://$ip_address:2283/api
---------------------------------------------------
If you want to configure custom information of the server, including the database, Redis information, or the backup (or upload) location, etc.
1. First bring down the containers with the command 'docker compose down' in the immich-app directory,
2. Then change the information that fits your needs in the '.env' file,
If you want to configure custom information of the server, including the database, Redis information, or the backup (or upload) location, etc.
1. First bring down the containers with the command 'docker compose down' in the immich-app directory,
2. Then change the information that fits your needs in the '.env' file,
3. Finally, bring the containers back up with the command 'docker compose up --remove-orphans -d' in the immich-app directory
EOF
}
@@ -66,11 +77,25 @@ main() {
return 14
fi
create_immich_directory || { echo 'error creating Immich directory'; return 10; }
download_docker_compose_file || { echo 'error downloading Docker Compose file'; return 11; }
download_dot_env_file || { echo 'error downloading .env'; return 12; }
start_docker_compose || { echo 'error starting Docker'; return 13; }
return 0; }
create_immich_directory || {
echo 'error creating Immich directory'
return 10
}
download_docker_compose_file || {
echo 'error downloading Docker Compose file'
return 11
}
download_dot_env_file || {
echo 'error downloading .env'
return 12
}
generate_random_password
start_docker_compose || {
echo 'error starting Docker'
return 13
}
return 0
}
main
Exit=$?

View File

@@ -30,6 +30,8 @@ download:
locale_code: pl-PL
- file: mobile/assets/i18n/fi-FI.json
locale_code: fi-FI
- file: mobile/assets/i18n/pt-BR.json
locale_code: pt-BR
- file: mobile/assets/i18n/pt-PT.json
locale_code: pt-PT
- file: mobile/assets/i18n/pt-BR.json
@@ -74,6 +76,8 @@ download:
locale_code: lv-LV
- file: mobile/assets/i18n/zh-Hans.json
locale_code: zh-Hans
- file: mobile/assets/i18n/zh-TW.json
locale_code: zh-TW
- file: mobile/assets/i18n/th-TH.json
locale_code: th-TH
- file: mobile/assets/i18n/lt-LT.json

View File

@@ -1,6 +1,6 @@
ARG DEVICE=cpu
FROM python:3.11-bookworm@sha256:abe34d06fca0165d40375b0e840fd3296ad2d075954d3a400d4efefe0e9b3012 as builder-cpu
FROM python:3.11-bookworm@sha256:96de1ea4821d73fd2c1853d1fdc3cf794ccfe2fae4c3f08579e846de51760a61 as builder-cpu
FROM openvino/ubuntu22_runtime:2023.3.0@sha256:176646df619032ea6c10faf842867119c393e7497b7f88b5e307e932a0fd5aa8 as builder-openvino
USER root
@@ -36,10 +36,13 @@ RUN python3 -m venv /opt/venv
COPY poetry.lock pyproject.toml ./
RUN poetry install --sync --no-interaction --no-ansi --no-root --with ${DEVICE} --without dev
FROM python:3.11-slim-bookworm@sha256:dad770592ab3582ab2dabcf0e18a863df9d86bd9d23efcfa614110ce49ac20e4 as prod-cpu
FROM python:3.11-slim-bookworm@sha256:fc39d2e68b554c3f0a5cb8a776280c0b3d73b4c04b83dbade835e2a171ca27ef as prod-cpu
FROM openvino/ubuntu22_runtime:2023.3.0@sha256:176646df619032ea6c10faf842867119c393e7497b7f88b5e307e932a0fd5aa8 as prod-openvino
USER root
# TODO: remove this once the image has the fix for https://github.com/intel/compute-runtime/issues/710
ENV NEOReadDebugKeys=1 \
OverrideGpuAddressSpace=48
FROM nvidia/cuda:12.2.2-cudnn8-runtime-ubuntu22.04@sha256:2d913b09e6be8387e1a10976933642c73c840c0b735f0bf3c28d97fc9bc422e0 as prod-cuda
@@ -74,8 +77,7 @@ RUN apt-get update && \
rm -rf /var/lib/apt/lists/*
WORKDIR /usr/src/app
ENV NODE_ENV=production \
TRANSFORMERS_CACHE=/cache \
ENV TRANSFORMERS_CACHE=/cache \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PATH="/opt/venv/bin:$PATH" \

View File

@@ -41,7 +41,7 @@ class Settings(BaseSettings):
class LogSettings(BaseSettings):
log_level: str = "info"
immich_log_level: str = "info"
no_color: bool = False
class Config:
@@ -77,7 +77,7 @@ LOG_LEVELS: dict[str, int] = {
settings = Settings()
log_settings = LogSettings()
LOG_LEVEL = LOG_LEVELS.get(log_settings.log_level.lower(), logging.INFO)
LOG_LEVEL = LOG_LEVELS.get(log_settings.immich_log_level.lower(), logging.INFO)
class CustomRichHandler(RichHandler):

View File

@@ -1,7 +1,6 @@
FROM mambaorg/micromamba:bookworm-slim@sha256:4de614588f7b3d6598a6cd571297157b57ac85b1a8ff854cf10231bb0c50e3ac as builder
FROM mambaorg/micromamba:bookworm-slim@sha256:d5b82811074b396275ef69aadbf31098257dd8836e231371e9cdb393128e571c as builder
ENV NODE_ENV=production \
TRANSFORMERS_CACHE=/cache \
ENV TRANSFORMERS_CACHE=/cache \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PATH="/opt/venv/bin:$PATH" \

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "machine-learning"
version = "1.102.0"
version = "1.105.1"
description = ""
authors = ["Hau Tran <alex.tran1502@gmail.com>"]
readme = "README.md"
@@ -11,7 +11,7 @@ python = ">=3.10,<3.12"
insightface = ">=0.7.3,<1.0"
opencv-python-headless = ">=4.7.0.72,<5.0"
pillow = ">=9.5.0,<11.0"
fastapi = ">=0.95.2,<1.0"
fastapi-slim = ">=0.95.2,<1.0"
uvicorn = {extras = ["standard"], version = ">=0.22.0,<1.0"}
pydantic = "^1.10.8"
aiocache = ">=0.12.1,<1.0"

View File

@@ -2,20 +2,20 @@
lib_path="/usr/lib/$(arch)-linux-gnu/libmimalloc.so.2"
# mimalloc seems to increase memory usage dramatically with openvino, need to investigate
if ! [ "$DEVICE" = "openvino" ]; then
if ! [ "$DEVICE" = "openvino" ]; then
export LD_PRELOAD="$lib_path"
export LD_BIND_NOW=1
fi
: "${MACHINE_LEARNING_HOST:=[::]}"
: "${MACHINE_LEARNING_PORT:=3003}"
: "${IMMICH_HOST:=[::]}"
: "${IMMICH_PORT:=3003}"
: "${MACHINE_LEARNING_WORKERS:=1}"
: "${MACHINE_LEARNING_WORKER_TIMEOUT:=120}"
gunicorn app.main:app \
-k app.config.CustomUvicornWorker \
-b "$IMMICH_HOST":"$IMMICH_PORT" \
-w "$MACHINE_LEARNING_WORKERS" \
-b "$MACHINE_LEARNING_HOST":"$MACHINE_LEARNING_PORT" \
-t "$MACHINE_LEARNING_WORKER_TIMEOUT" \
--log-config-json log_conf.json \
--graceful-timeout 0

View File

@@ -4,31 +4,8 @@
As always, please consider supporting the project.
🎉 Cheer! 🎉
## Support
🎉 Cheers! 🎉
- - - -
And as always, bugs are fixed, and many other improvements also come with this release.
Please consider supporting the project.
## Support
<p align="center">
<img src="https://media.giphy.com/media/LStqgGESXW8XnuCv5y/giphy.gif" width="250" title="SUPPORT THE PROJECT!">
</p>
If you find the project helpful, you can support Immich via the following channels.
- Monthly donation via [GitHub Sponsors](https://github.com/sponsors/alextran1502)
- One-time donation via [GitHub Sponsors](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502)
- [Librepay](https://liberapay.com/alex.tran1502/)
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
- Bitcoin: 3QVAb9dCHutquVejeNXitPqZX26Yg5kxb7
It is a great way to let me know that you want me to continue developing and working on this project for years to come.

View File

@@ -1,4 +0,0 @@
{
"flutterSdkVersion": "3.19.3",
"flavors": {}
}

Some files were not shown because too many files have changed in this diff Show More