Merge branch 'main' of github.com:immich-app/immich into feat/mobile-delete-local-only
This commit is contained in:
commit
4643b4894e
122 changed files with 4535 additions and 491 deletions
2
.gitattributes
vendored
2
.gitattributes
vendored
|
@ -17,3 +17,5 @@ web/src/api/open-api/**/*.md -diff -merge
|
||||||
web/src/api/open-api/**/*.md linguist-generated=true
|
web/src/api/open-api/**/*.md linguist-generated=true
|
||||||
web/src/api/open-api/**/*.ts -diff -merge
|
web/src/api/open-api/**/*.ts -diff -merge
|
||||||
web/src/api/open-api/**/*.ts linguist-generated=true
|
web/src/api/open-api/**/*.ts linguist-generated=true
|
||||||
|
|
||||||
|
*.sh text eol=lf
|
||||||
|
|
2
.github/workflows/build-mobile.yml
vendored
2
.github/workflows/build-mobile.yml
vendored
|
@ -35,7 +35,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
ref: ${{ steps.get-ref.outputs.ref }}
|
ref: ${{ steps.get-ref.outputs.ref }}
|
||||||
|
|
||||||
- uses: actions/setup-java@v3
|
- uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: "zulu"
|
distribution: "zulu"
|
||||||
java-version: "12.x"
|
java-version: "12.x"
|
||||||
|
|
25
.github/workflows/test.yml
vendored
25
.github/workflows/test.yml
vendored
|
@ -209,7 +209,7 @@ jobs:
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
generated-typeorm-migrations-up-to-date:
|
generated-typeorm-migrations-up-to-date:
|
||||||
name: TypeORM Migrations
|
name: TypeORM Checks
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
|
@ -236,7 +236,7 @@ jobs:
|
||||||
- name: Install server dependencies
|
- name: Install server dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: Build the
|
- name: Build the app
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
- name: Run existing migrations
|
- name: Run existing migrations
|
||||||
|
@ -252,13 +252,30 @@ jobs:
|
||||||
with:
|
with:
|
||||||
files: |
|
files: |
|
||||||
server/src/infra/migrations/
|
server/src/infra/migrations/
|
||||||
- name: Verify files have not changed
|
- name: Verify migration files have not changed
|
||||||
if: steps.verify-changed-files.outputs.files_changed == 'true'
|
if: steps.verify-changed-files.outputs.files_changed == 'true'
|
||||||
run: |
|
run: |
|
||||||
echo "ERROR: Generated files not up to date!"
|
echo "ERROR: Generated migration files not up to date!"
|
||||||
echo "Changed files: ${{ steps.verify-changed-files.outputs.changed_files }}"
|
echo "Changed files: ${{ steps.verify-changed-files.outputs.changed_files }}"
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
|
- name: Run SQL generation
|
||||||
|
run: npm run sql:generate
|
||||||
|
|
||||||
|
- name: Find file changes
|
||||||
|
uses: tj-actions/verify-changed-files@v13.1
|
||||||
|
id: verify-changed-sql-files
|
||||||
|
with:
|
||||||
|
files: |
|
||||||
|
server/src/infra/sql
|
||||||
|
|
||||||
|
- name: Verify SQL files have not changed
|
||||||
|
if: steps.verify-changed-sql-files.outputs.files_changed == 'true'
|
||||||
|
run: |
|
||||||
|
echo "ERROR: Generated SQL files not up to date!"
|
||||||
|
echo "Changed files: ${{ steps.verify-changed-sql-files.outputs.changed_files }}"
|
||||||
|
exit 1
|
||||||
|
|
||||||
# mobile-integration-tests:
|
# mobile-integration-tests:
|
||||||
# name: Run mobile end-to-end integration tests
|
# name: Run mobile end-to-end integration tests
|
||||||
# runs-on: macos-latest
|
# runs-on: macos-latest
|
||||||
|
|
5
Makefile
5
Makefile
|
@ -26,7 +26,10 @@ prod-scale:
|
||||||
docker compose -f ./docker/docker-compose.prod.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans
|
docker compose -f ./docker/docker-compose.prod.yml up --build -V --scale immich-server=3 --scale immich-microservices=3 --remove-orphans
|
||||||
|
|
||||||
api:
|
api:
|
||||||
cd ./server && npm run api:generate
|
npm --prefix server run api:generate
|
||||||
|
|
||||||
|
sql:
|
||||||
|
npm --prefix server run sql:generate
|
||||||
|
|
||||||
attach-server:
|
attach-server:
|
||||||
docker exec -it docker_immich-server_1 sh
|
docker exec -it docker_immich-server_1 sh
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<img src="design/immich-logo.svg" width="150" title="Login mit eigener URL">
|
<img src="design/immich-logo.svg" width="150" title="Login mit eigener URL">
|
||||||
</p>
|
</p>
|
||||||
<h3 align="center">Immich - Hoch performante, selbst gehostete Backup Lösung für Fotos und Videos</h3>
|
<h3 align="center">Immich - Hoch performante, selbst gehostete Backup-Lösung für Fotos und Videos</h3>
|
||||||
<br/>
|
<br/>
|
||||||
<a href="https://immich.app">
|
<a href="https://immich.app">
|
||||||
<img src="design/immich-screenshots.png" title="Haupt-Screenshot">
|
<img src="design/immich-screenshots.png" title="Haupt-Screenshot">
|
||||||
|
@ -32,10 +32,10 @@
|
||||||
|
|
||||||
## Warnung
|
## Warnung
|
||||||
|
|
||||||
- ⚠️ Das Projekt befindet sich unter **sehr aktiver** Entwicklung.
|
- ⚠️ Das Projekt befindet sich in **sehr aktiver** Entwicklung.
|
||||||
- ⚠️ Erwarte Fehler und Änderungen mit Breaking-Changes.
|
- ⚠️ Erwarte Fehler und Änderungen mit Breaking-Changes.
|
||||||
- ⚠️ **Nutze die App auf keinen Fall als einziges Speichermedium für deine Fotos und Videos.**
|
- ⚠️ **Nutze die App auf keinen Fall als einziges Speichermedium für deine Fotos und Videos.**
|
||||||
- ⚠️ Befolge immer die [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) Backup Regel für deine wertvollen Fotos und Videos!
|
- ⚠️ Befolge immer die [3-2-1](https://www.backblaze.com/blog/the-3-2-1-backup-strategy/) Backup-Regel für deine wertvollen Fotos und Videos!
|
||||||
|
|
||||||
## Inhalt
|
## Inhalt
|
||||||
|
|
||||||
|
@ -54,9 +54,9 @@ Die Hauptdokumentation, inklusive Installationsanleitungen, ist unter https://im
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
Die Web-Demo kannst du unter https://demo.immich.app finden.
|
Die Web-Demo kannst Du unter https://demo.immich.app finden.
|
||||||
|
|
||||||
Für die Handy-App kannst du `https://demo.immich.app/api` als `Server Endpoint URL` angeben.
|
Für die Handy-App kannst Du `https://demo.immich.app/api` als `Server Endpoint URL` angeben.
|
||||||
|
|
||||||
```bash title="Demo Credential"
|
```bash title="Demo Credential"
|
||||||
Die Anmeldedaten
|
Die Anmeldedaten
|
||||||
|
@ -73,8 +73,8 @@ Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
|
||||||
| Funktionen | Mobil | Web |
|
| Funktionen | Mobil | Web |
|
||||||
| ---------------------------------------------------- | ------ | ----- |
|
| ---------------------------------------------------- | ------ | ----- |
|
||||||
| Fotos & Videos hochladen und ansehen | Ja | Ja |
|
| Fotos & Videos hochladen und ansehen | Ja | Ja |
|
||||||
| Automatisches Backup wenn die App offen ist | Ja | n. a. |
|
| Automatisches Backup wenn die App geöffnet ist | Ja | n. a. |
|
||||||
| Selektive Auswahl von Alben zum Backup | Ja | n. a. |
|
| Selektive Auswahl von Alben zum Sichern | Ja | n. a. |
|
||||||
| Fotos und Videos auf das Gerät herunterladen | Ja | Ja |
|
| Fotos und Videos auf das Gerät herunterladen | Ja | Ja |
|
||||||
| Unterstützt mehrere Benutzer | Ja | Ja |
|
| Unterstützt mehrere Benutzer | Ja | Ja |
|
||||||
| Album und geteilte Alben | Ja | Ja |
|
| Album und geteilte Alben | Ja | Ja |
|
||||||
|
@ -82,7 +82,7 @@ Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
|
||||||
| Unterstützt RAW Formate | Ja | Ja |
|
| Unterstützt RAW Formate | Ja | Ja |
|
||||||
| Metadaten anzeigen (EXIF, Karte) | Ja | Ja |
|
| Metadaten anzeigen (EXIF, Karte) | Ja | Ja |
|
||||||
| Suchen nach Metadaten, Objekten, Gesichtern und CLIP | Ja | Ja |
|
| Suchen nach Metadaten, Objekten, Gesichtern und CLIP | Ja | Ja |
|
||||||
| Administrative Funktionen (Nutzerverwaltung) | Nein | Ja |
|
| Administrative Funktionen (Benutzerverwaltung) | Nein | Ja |
|
||||||
| Backup im Hintergrund | Ja | n. a. |
|
| Backup im Hintergrund | Ja | n. a. |
|
||||||
| Virtuelles Scrollen | Ja | Ja |
|
| Virtuelles Scrollen | Ja | Ja |
|
||||||
| OAuth Unterstützung | Ja | Ja |
|
| OAuth Unterstützung | Ja | Ja |
|
||||||
|
@ -101,22 +101,22 @@ Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
|
||||||
|
|
||||||
## Unterstütze das Projekt
|
## Unterstütze das Projekt
|
||||||
|
|
||||||
Ich habe mich diesem Projekt verpflichtet und werde nicht aufgeben. Ich werde die Dokumentation weiter aktualisieren, neue Funktionen hinzufügen und Fehler beheben. Allerdings kann ich das nicht alleine schaffen. Daher brauche ich Eure Unterstützung mir zusätzliche Motivation zu geben um weiterzumachen.
|
Ich habe mich diesem Projekt verpflichtet und werde nicht aufgeben. Ich werde die Dokumentation weiter aktualisieren, neue Funktionen hinzufügen und Fehler beheben. Allerdings kann ich das nicht alleine schaffen. Daher brauche ich Eure Unterstützung, um mir zusätzliche Motivation zu geben, weiterzumachen.
|
||||||
|
|
||||||
Wie unsere Gastgeber in der [selfhosted.show - In der Episode 'The-organization-must-not-be-name is a Hostile Actor'](https://selfhosted.show/79?t=1418) gesagt haben, ist dies ein riesiges Unterfangen, welchem das Team und ich uns annehmen. In Zukunft würde ich liebend gerne Vollzeit an dem Projekt arbeiten und bitte daher um Eure Unterstützung.
|
Wie unsere Gastgeber in der [selfhosted.show - In der Episode 'The-organization-must-not-be-name is a Hostile Actor'](https://selfhosted.show/79?t=1418) gesagt haben, ist dies ein riesiges Unterfangen, welchem das Team und ich uns annehmen. In Zukunft würde ich liebend gerne Vollzeit an dem Projekt arbeiten und bitte daher um Eure Unterstützung.
|
||||||
|
|
||||||
Wenn Du denkst, dass dies die richtige Sache ist und dich selbst die App für eine längere Zeit nutzen siehst, dann denke bitte darüber nach das Projekt mit einer der unten aufgelisteten Optionen zu unterstützen.
|
Wenn Du denkst, dass dies die richtige Sache ist und dich selbst die App für eine längere Zeit nutzen siehst, dann denke bitte darüber nach, das Projekt mit einer der unten aufgelisteten Optionen zu unterstützen.
|
||||||
|
|
||||||
### Spenden
|
### Spenden
|
||||||
|
|
||||||
- [Monatliche Spende](https://github.com/sponsors/alextran1502) via GitHub Sponsors
|
- [Monatliche Spende](https://github.com/sponsors/immich-app) via GitHub Sponsors
|
||||||
- [Einmalige Spende](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502) via GitHub Sponsors
|
- [Einmalige Spende](https://github.com/sponsors/immich-app?frequency=one-time&sponsor=immich-app) via GitHub Sponsors
|
||||||
- [Librepay](https://liberapay.com/alex.tran1502/)
|
- [Librepay](https://liberapay.com/alex.tran1502/)
|
||||||
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
|
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
|
||||||
- Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX
|
- Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX
|
||||||
- ZCash: u1smm4wvqegcp46zss2jf5xptchgeczp4rx7a0wu3mermf2wxahm26yyz5w9mw3f2p4emwlljxjumg774kgs8rntt9yags0whnzane4n67z4c7gppq4yyvcj404ne3r769prwzd9j8ntvqp44fa6d67sf7rmcfjmds3gmeceff4u8e92rh38nd30cr96xw6vfhk6scu4ws90ldzupr3sz
|
- ZCash: u1smm4wvqegcp46zss2jf5xptchgeczp4rx7a0wu3mermf2wxahm26yyz5w9mw3f2p4emwlljxjumg774kgs8rntt9yags0whnzane4n67z4c7gppq4yyvcj404ne3r769prwzd9j8ntvqp44fa6d67sf7rmcfjmds3gmeceff4u8e92rh38nd30cr96xw6vfhk6scu4ws90ldzupr3sz
|
||||||
|
|
||||||
## Unterstützer
|
## Mitwirkende
|
||||||
<a href="https://github.com/alextran1502/immich/graphs/contributors">
|
<a href="https://github.com/alextran1502/immich/graphs/contributors">
|
||||||
<img src="https://contrib.rocks/image?repo=immich-app/immich" width="100%"/>
|
<img src="https://contrib.rocks/image?repo=immich-app/immich" width="100%"/>
|
||||||
</a>
|
</a>
|
||||||
|
|
12
cli/package-lock.json
generated
12
cli/package-lock.json
generated
|
@ -1604,9 +1604,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.10.0",
|
"version": "20.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.1.tgz",
|
||||||
"integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==",
|
"integrity": "sha512-T2qwhjWwGH81vUEx4EXmBKsTJRXFXNZTL4v0gi01+zyBmCwzE6TyHszqX01m+QHTEq+EZNo13NeJIdEqf+Myrg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.26.4"
|
||||||
|
@ -7990,9 +7990,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "20.10.0",
|
"version": "20.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.1.tgz",
|
||||||
"integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==",
|
"integrity": "sha512-T2qwhjWwGH81vUEx4EXmBKsTJRXFXNZTL4v0gi01+zyBmCwzE6TyHszqX01m+QHTEq+EZNo13NeJIdEqf+Myrg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.26.4"
|
||||||
|
|
36
cli/src/api/open-api/api.ts
generated
36
cli/src/api/open-api/api.ts
generated
|
@ -447,6 +447,12 @@ export interface AssetBulkDeleteDto {
|
||||||
* @interface AssetBulkUpdateDto
|
* @interface AssetBulkUpdateDto
|
||||||
*/
|
*/
|
||||||
export interface AssetBulkUpdateDto {
|
export interface AssetBulkUpdateDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof AssetBulkUpdateDto
|
||||||
|
*/
|
||||||
|
'dateTimeOriginal'?: string;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {Array<string>}
|
* @type {Array<string>}
|
||||||
|
@ -465,6 +471,18 @@ export interface AssetBulkUpdateDto {
|
||||||
* @memberof AssetBulkUpdateDto
|
* @memberof AssetBulkUpdateDto
|
||||||
*/
|
*/
|
||||||
'isFavorite'?: boolean;
|
'isFavorite'?: boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof AssetBulkUpdateDto
|
||||||
|
*/
|
||||||
|
'latitude'?: number;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof AssetBulkUpdateDto
|
||||||
|
*/
|
||||||
|
'longitude'?: number;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
|
@ -4137,6 +4155,12 @@ export interface UpdateAlbumDto {
|
||||||
* @interface UpdateAssetDto
|
* @interface UpdateAssetDto
|
||||||
*/
|
*/
|
||||||
export interface UpdateAssetDto {
|
export interface UpdateAssetDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof UpdateAssetDto
|
||||||
|
*/
|
||||||
|
'dateTimeOriginal'?: string;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
|
@ -4155,6 +4179,18 @@ export interface UpdateAssetDto {
|
||||||
* @memberof UpdateAssetDto
|
* @memberof UpdateAssetDto
|
||||||
*/
|
*/
|
||||||
'isFavorite'?: boolean;
|
'isFavorite'?: boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof UpdateAssetDto
|
||||||
|
*/
|
||||||
|
'latitude'?: number;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof UpdateAssetDto
|
||||||
|
*/
|
||||||
|
'longitude'?: number;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|
|
@ -108,11 +108,11 @@ services:
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: redis:6.2-alpine@sha256:80cc8518800438c684a53ed829c621c94afd1087aaeb59b0d4343ed3e7bcf6c5
|
image: redis:6.2-alpine@sha256:60e49e22fa5706cd8df7d5e0bc50ee9bab7c608039fa653c4d961014237cca46
|
||||||
|
|
||||||
database:
|
database:
|
||||||
container_name: immich_postgres
|
container_name: immich_postgres
|
||||||
image: postgres:14-alpine@sha256:50d9be76e9a90da4c781554955e0ffc79d9d5c4226838e64b36aacc97cbc35ad
|
image: postgres:14-alpine@sha256:54916702f83d9330d355e078f69a7ba42f2cf2e530c8fe63423d6680d8da45b0
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
|
|
|
@ -65,12 +65,12 @@ services:
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: redis:6.2-alpine@sha256:80cc8518800438c684a53ed829c621c94afd1087aaeb59b0d4343ed3e7bcf6c5
|
image: redis:6.2-alpine@sha256:60e49e22fa5706cd8df7d5e0bc50ee9bab7c608039fa653c4d961014237cca46
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
database:
|
database:
|
||||||
container_name: immich_postgres
|
container_name: immich_postgres
|
||||||
image: postgres:14-alpine@sha256:50d9be76e9a90da4c781554955e0ffc79d9d5c4226838e64b36aacc97cbc35ad
|
image: postgres:14-alpine@sha256:54916702f83d9330d355e078f69a7ba42f2cf2e530c8fe63423d6680d8da45b0
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
|
|
|
@ -23,7 +23,7 @@ services:
|
||||||
- database
|
- database
|
||||||
|
|
||||||
database:
|
database:
|
||||||
image: postgres:14-alpine@sha256:50d9be76e9a90da4c781554955e0ffc79d9d5c4226838e64b36aacc97cbc35ad
|
image: postgres:14-alpine@sha256:54916702f83d9330d355e078f69a7ba42f2cf2e530c8fe63423d6680d8da45b0
|
||||||
command: -c fsync=off
|
command: -c fsync=off
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_PASSWORD: postgres
|
POSTGRES_PASSWORD: postgres
|
||||||
|
|
|
@ -69,12 +69,12 @@ services:
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
container_name: immich_redis
|
container_name: immich_redis
|
||||||
image: redis:6.2-alpine@sha256:80cc8518800438c684a53ed829c621c94afd1087aaeb59b0d4343ed3e7bcf6c5
|
image: redis:6.2-alpine@sha256:60e49e22fa5706cd8df7d5e0bc50ee9bab7c608039fa653c4d961014237cca46
|
||||||
restart: always
|
restart: always
|
||||||
|
|
||||||
database:
|
database:
|
||||||
container_name: immich_postgres
|
container_name: immich_postgres
|
||||||
image: postgres:14-alpine@sha256:50d9be76e9a90da4c781554955e0ffc79d9d5c4226838e64b36aacc97cbc35ad
|
image: postgres:14-alpine@sha256:54916702f83d9330d355e078f69a7ba42f2cf2e530c8fe63423d6680d8da45b0
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
environment:
|
environment:
|
||||||
|
|
|
@ -144,7 +144,7 @@ PUBLIC_LOGIN_PAGE_MESSAGE="My Family Photos and Videos Backup Server"
|
||||||
From the directory you created in Step 1, (which should now contain your customized `docker-compose.yml` and `.env` files) run `docker-compose up -d`.
|
From the directory you created in Step 1, (which should now contain your customized `docker-compose.yml` and `.env` files) run `docker-compose up -d`.
|
||||||
|
|
||||||
```bash title="Start the containers using docker compose command"
|
```bash title="Start the containers using docker compose command"
|
||||||
docker-compose up -d # or `docker compose up -d` based on your docker-compose version
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
:::tip
|
:::tip
|
||||||
|
@ -162,7 +162,7 @@ If `IMMICH_VERSION` is set, it will need to be updated to the latest or desired
|
||||||
When a new version of Immich is [released](https://github.com/immich-app/immich/releases), the application can be upgraded with the following commands, run in the directory with the `docker-compose.yml` file:
|
When a new version of Immich is [released](https://github.com/immich-app/immich/releases), the application can be upgraded with the following commands, run in the directory with the `docker-compose.yml` file:
|
||||||
|
|
||||||
```bash title="Upgrade Immich"
|
```bash title="Upgrade Immich"
|
||||||
docker-compose pull && docker-compose up -d # Or `docker compose up -d`
|
docker compose pull && docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
:::caution Automatic Updates
|
:::caution Automatic Updates
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
FROM python:3.11-bookworm@sha256:e5a1b0a194a5fbf94f6e350b31c9a508723f9eeb2f9e9e32c3b65df8520a40cc as builder
|
FROM python:3.11-bookworm@sha256:47c1829f72432c33609b3095259843a88c7ffc42cc9dbb55c43f2e7bbe46ca58 as builder
|
||||||
|
|
||||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
PYTHONUNBUFFERED=1 \
|
PYTHONUNBUFFERED=1 \
|
||||||
|
@ -13,7 +13,7 @@ ENV VIRTUAL_ENV="/opt/venv" PATH="/opt/venv/bin:${PATH}"
|
||||||
COPY poetry.lock pyproject.toml ./
|
COPY poetry.lock pyproject.toml ./
|
||||||
RUN poetry install --sync --no-interaction --no-ansi --no-root --only main
|
RUN poetry install --sync --no-interaction --no-ansi --no-root --only main
|
||||||
|
|
||||||
FROM python:3.11-slim-bookworm@sha256:1bc6a3e9356d64ea632791653bc71a56340e8741dab66434ab2739ebf6aed29d
|
FROM python:3.11-slim-bookworm@sha256:8f82989e563d0dbad057a874a96438a360978c148e34f36c1db8d2d61b5fd6f0
|
||||||
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends tini libmimalloc2.0 && rm -rf /var/lib/apt/lists/*
|
RUN apt-get update && apt-get install -y --no-install-recommends tini libmimalloc2.0 && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
|
3
mobile/openapi/doc/AssetBulkUpdateDto.md
generated
3
mobile/openapi/doc/AssetBulkUpdateDto.md
generated
|
@ -8,9 +8,12 @@ import 'package:openapi/api.dart';
|
||||||
## Properties
|
## Properties
|
||||||
Name | Type | Description | Notes
|
Name | Type | Description | Notes
|
||||||
------------ | ------------- | ------------- | -------------
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**dateTimeOriginal** | **String** | | [optional]
|
||||||
**ids** | **List<String>** | | [default to const []]
|
**ids** | **List<String>** | | [default to const []]
|
||||||
**isArchived** | **bool** | | [optional]
|
**isArchived** | **bool** | | [optional]
|
||||||
**isFavorite** | **bool** | | [optional]
|
**isFavorite** | **bool** | | [optional]
|
||||||
|
**latitude** | **num** | | [optional]
|
||||||
|
**longitude** | **num** | | [optional]
|
||||||
**removeParent** | **bool** | | [optional]
|
**removeParent** | **bool** | | [optional]
|
||||||
**stackParentId** | **String** | | [optional]
|
**stackParentId** | **String** | | [optional]
|
||||||
|
|
||||||
|
|
3
mobile/openapi/doc/UpdateAssetDto.md
generated
3
mobile/openapi/doc/UpdateAssetDto.md
generated
|
@ -8,9 +8,12 @@ import 'package:openapi/api.dart';
|
||||||
## Properties
|
## Properties
|
||||||
Name | Type | Description | Notes
|
Name | Type | Description | Notes
|
||||||
------------ | ------------- | ------------- | -------------
|
------------ | ------------- | ------------- | -------------
|
||||||
|
**dateTimeOriginal** | **String** | | [optional]
|
||||||
**description** | **String** | | [optional]
|
**description** | **String** | | [optional]
|
||||||
**isArchived** | **bool** | | [optional]
|
**isArchived** | **bool** | | [optional]
|
||||||
**isFavorite** | **bool** | | [optional]
|
**isFavorite** | **bool** | | [optional]
|
||||||
|
**latitude** | **num** | | [optional]
|
||||||
|
**longitude** | **num** | | [optional]
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
57
mobile/openapi/lib/model/asset_bulk_update_dto.dart
generated
57
mobile/openapi/lib/model/asset_bulk_update_dto.dart
generated
|
@ -13,13 +13,24 @@ part of openapi.api;
|
||||||
class AssetBulkUpdateDto {
|
class AssetBulkUpdateDto {
|
||||||
/// Returns a new [AssetBulkUpdateDto] instance.
|
/// Returns a new [AssetBulkUpdateDto] instance.
|
||||||
AssetBulkUpdateDto({
|
AssetBulkUpdateDto({
|
||||||
|
this.dateTimeOriginal,
|
||||||
this.ids = const [],
|
this.ids = const [],
|
||||||
this.isArchived,
|
this.isArchived,
|
||||||
this.isFavorite,
|
this.isFavorite,
|
||||||
|
this.latitude,
|
||||||
|
this.longitude,
|
||||||
this.removeParent,
|
this.removeParent,
|
||||||
this.stackParentId,
|
this.stackParentId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
|
/// source code must fall back to having a nullable type.
|
||||||
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
|
///
|
||||||
|
String? dateTimeOriginal;
|
||||||
|
|
||||||
List<String> ids;
|
List<String> ids;
|
||||||
|
|
||||||
///
|
///
|
||||||
|
@ -38,6 +49,22 @@ class AssetBulkUpdateDto {
|
||||||
///
|
///
|
||||||
bool? isFavorite;
|
bool? isFavorite;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
|
/// source code must fall back to having a nullable type.
|
||||||
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
|
///
|
||||||
|
num? latitude;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
|
/// source code must fall back to having a nullable type.
|
||||||
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
|
///
|
||||||
|
num? longitude;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
/// does not include a default value (using the "default:" property), however, the generated
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
|
@ -56,26 +83,37 @@ class AssetBulkUpdateDto {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is AssetBulkUpdateDto &&
|
bool operator ==(Object other) => identical(this, other) || other is AssetBulkUpdateDto &&
|
||||||
|
other.dateTimeOriginal == dateTimeOriginal &&
|
||||||
other.ids == ids &&
|
other.ids == ids &&
|
||||||
other.isArchived == isArchived &&
|
other.isArchived == isArchived &&
|
||||||
other.isFavorite == isFavorite &&
|
other.isFavorite == isFavorite &&
|
||||||
|
other.latitude == latitude &&
|
||||||
|
other.longitude == longitude &&
|
||||||
other.removeParent == removeParent &&
|
other.removeParent == removeParent &&
|
||||||
other.stackParentId == stackParentId;
|
other.stackParentId == stackParentId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
|
(dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) +
|
||||||
(ids.hashCode) +
|
(ids.hashCode) +
|
||||||
(isArchived == null ? 0 : isArchived!.hashCode) +
|
(isArchived == null ? 0 : isArchived!.hashCode) +
|
||||||
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
||||||
|
(latitude == null ? 0 : latitude!.hashCode) +
|
||||||
|
(longitude == null ? 0 : longitude!.hashCode) +
|
||||||
(removeParent == null ? 0 : removeParent!.hashCode) +
|
(removeParent == null ? 0 : removeParent!.hashCode) +
|
||||||
(stackParentId == null ? 0 : stackParentId!.hashCode);
|
(stackParentId == null ? 0 : stackParentId!.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'AssetBulkUpdateDto[ids=$ids, isArchived=$isArchived, isFavorite=$isFavorite, removeParent=$removeParent, stackParentId=$stackParentId]';
|
String toString() => 'AssetBulkUpdateDto[dateTimeOriginal=$dateTimeOriginal, ids=$ids, isArchived=$isArchived, isFavorite=$isFavorite, latitude=$latitude, longitude=$longitude, removeParent=$removeParent, stackParentId=$stackParentId]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
|
if (this.dateTimeOriginal != null) {
|
||||||
|
json[r'dateTimeOriginal'] = this.dateTimeOriginal;
|
||||||
|
} else {
|
||||||
|
// json[r'dateTimeOriginal'] = null;
|
||||||
|
}
|
||||||
json[r'ids'] = this.ids;
|
json[r'ids'] = this.ids;
|
||||||
if (this.isArchived != null) {
|
if (this.isArchived != null) {
|
||||||
json[r'isArchived'] = this.isArchived;
|
json[r'isArchived'] = this.isArchived;
|
||||||
|
@ -87,6 +125,16 @@ class AssetBulkUpdateDto {
|
||||||
} else {
|
} else {
|
||||||
// json[r'isFavorite'] = null;
|
// json[r'isFavorite'] = null;
|
||||||
}
|
}
|
||||||
|
if (this.latitude != null) {
|
||||||
|
json[r'latitude'] = this.latitude;
|
||||||
|
} else {
|
||||||
|
// json[r'latitude'] = null;
|
||||||
|
}
|
||||||
|
if (this.longitude != null) {
|
||||||
|
json[r'longitude'] = this.longitude;
|
||||||
|
} else {
|
||||||
|
// json[r'longitude'] = null;
|
||||||
|
}
|
||||||
if (this.removeParent != null) {
|
if (this.removeParent != null) {
|
||||||
json[r'removeParent'] = this.removeParent;
|
json[r'removeParent'] = this.removeParent;
|
||||||
} else {
|
} else {
|
||||||
|
@ -108,11 +156,18 @@ class AssetBulkUpdateDto {
|
||||||
final json = value.cast<String, dynamic>();
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
return AssetBulkUpdateDto(
|
return AssetBulkUpdateDto(
|
||||||
|
dateTimeOriginal: mapValueOfType<String>(json, r'dateTimeOriginal'),
|
||||||
ids: json[r'ids'] is List
|
ids: json[r'ids'] is List
|
||||||
? (json[r'ids'] as List).cast<String>()
|
? (json[r'ids'] as List).cast<String>()
|
||||||
: const [],
|
: const [],
|
||||||
isArchived: mapValueOfType<bool>(json, r'isArchived'),
|
isArchived: mapValueOfType<bool>(json, r'isArchived'),
|
||||||
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
|
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
|
||||||
|
latitude: json[r'latitude'] == null
|
||||||
|
? null
|
||||||
|
: num.parse(json[r'latitude'].toString()),
|
||||||
|
longitude: json[r'longitude'] == null
|
||||||
|
? null
|
||||||
|
: num.parse(json[r'longitude'].toString()),
|
||||||
removeParent: mapValueOfType<bool>(json, r'removeParent'),
|
removeParent: mapValueOfType<bool>(json, r'removeParent'),
|
||||||
stackParentId: mapValueOfType<String>(json, r'stackParentId'),
|
stackParentId: mapValueOfType<String>(json, r'stackParentId'),
|
||||||
);
|
);
|
||||||
|
|
61
mobile/openapi/lib/model/update_asset_dto.dart
generated
61
mobile/openapi/lib/model/update_asset_dto.dart
generated
|
@ -13,11 +13,22 @@ part of openapi.api;
|
||||||
class UpdateAssetDto {
|
class UpdateAssetDto {
|
||||||
/// Returns a new [UpdateAssetDto] instance.
|
/// Returns a new [UpdateAssetDto] instance.
|
||||||
UpdateAssetDto({
|
UpdateAssetDto({
|
||||||
|
this.dateTimeOriginal,
|
||||||
this.description,
|
this.description,
|
||||||
this.isArchived,
|
this.isArchived,
|
||||||
this.isFavorite,
|
this.isFavorite,
|
||||||
|
this.latitude,
|
||||||
|
this.longitude,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
|
/// source code must fall back to having a nullable type.
|
||||||
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
|
///
|
||||||
|
String? dateTimeOriginal;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
/// does not include a default value (using the "default:" property), however, the generated
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
|
@ -42,24 +53,51 @@ class UpdateAssetDto {
|
||||||
///
|
///
|
||||||
bool? isFavorite;
|
bool? isFavorite;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
|
/// source code must fall back to having a nullable type.
|
||||||
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
|
///
|
||||||
|
num? latitude;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
|
/// source code must fall back to having a nullable type.
|
||||||
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
|
///
|
||||||
|
num? longitude;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is UpdateAssetDto &&
|
bool operator ==(Object other) => identical(this, other) || other is UpdateAssetDto &&
|
||||||
|
other.dateTimeOriginal == dateTimeOriginal &&
|
||||||
other.description == description &&
|
other.description == description &&
|
||||||
other.isArchived == isArchived &&
|
other.isArchived == isArchived &&
|
||||||
other.isFavorite == isFavorite;
|
other.isFavorite == isFavorite &&
|
||||||
|
other.latitude == latitude &&
|
||||||
|
other.longitude == longitude;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
|
(dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) +
|
||||||
(description == null ? 0 : description!.hashCode) +
|
(description == null ? 0 : description!.hashCode) +
|
||||||
(isArchived == null ? 0 : isArchived!.hashCode) +
|
(isArchived == null ? 0 : isArchived!.hashCode) +
|
||||||
(isFavorite == null ? 0 : isFavorite!.hashCode);
|
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
||||||
|
(latitude == null ? 0 : latitude!.hashCode) +
|
||||||
|
(longitude == null ? 0 : longitude!.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'UpdateAssetDto[description=$description, isArchived=$isArchived, isFavorite=$isFavorite]';
|
String toString() => 'UpdateAssetDto[dateTimeOriginal=$dateTimeOriginal, description=$description, isArchived=$isArchived, isFavorite=$isFavorite, latitude=$latitude, longitude=$longitude]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
|
if (this.dateTimeOriginal != null) {
|
||||||
|
json[r'dateTimeOriginal'] = this.dateTimeOriginal;
|
||||||
|
} else {
|
||||||
|
// json[r'dateTimeOriginal'] = null;
|
||||||
|
}
|
||||||
if (this.description != null) {
|
if (this.description != null) {
|
||||||
json[r'description'] = this.description;
|
json[r'description'] = this.description;
|
||||||
} else {
|
} else {
|
||||||
|
@ -75,6 +113,16 @@ class UpdateAssetDto {
|
||||||
} else {
|
} else {
|
||||||
// json[r'isFavorite'] = null;
|
// json[r'isFavorite'] = null;
|
||||||
}
|
}
|
||||||
|
if (this.latitude != null) {
|
||||||
|
json[r'latitude'] = this.latitude;
|
||||||
|
} else {
|
||||||
|
// json[r'latitude'] = null;
|
||||||
|
}
|
||||||
|
if (this.longitude != null) {
|
||||||
|
json[r'longitude'] = this.longitude;
|
||||||
|
} else {
|
||||||
|
// json[r'longitude'] = null;
|
||||||
|
}
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,9 +134,16 @@ class UpdateAssetDto {
|
||||||
final json = value.cast<String, dynamic>();
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
return UpdateAssetDto(
|
return UpdateAssetDto(
|
||||||
|
dateTimeOriginal: mapValueOfType<String>(json, r'dateTimeOriginal'),
|
||||||
description: mapValueOfType<String>(json, r'description'),
|
description: mapValueOfType<String>(json, r'description'),
|
||||||
isArchived: mapValueOfType<bool>(json, r'isArchived'),
|
isArchived: mapValueOfType<bool>(json, r'isArchived'),
|
||||||
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
|
isFavorite: mapValueOfType<bool>(json, r'isFavorite'),
|
||||||
|
latitude: json[r'latitude'] == null
|
||||||
|
? null
|
||||||
|
: num.parse(json[r'latitude'].toString()),
|
||||||
|
longitude: json[r'longitude'] == null
|
||||||
|
? null
|
||||||
|
: num.parse(json[r'longitude'].toString()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
15
mobile/openapi/test/asset_bulk_update_dto_test.dart
generated
15
mobile/openapi/test/asset_bulk_update_dto_test.dart
generated
|
@ -16,6 +16,11 @@ void main() {
|
||||||
// final instance = AssetBulkUpdateDto();
|
// final instance = AssetBulkUpdateDto();
|
||||||
|
|
||||||
group('test AssetBulkUpdateDto', () {
|
group('test AssetBulkUpdateDto', () {
|
||||||
|
// String dateTimeOriginal
|
||||||
|
test('to test the property `dateTimeOriginal`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
// List<String> ids (default value: const [])
|
// List<String> ids (default value: const [])
|
||||||
test('to test the property `ids`', () async {
|
test('to test the property `ids`', () async {
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -31,6 +36,16 @@ void main() {
|
||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// num latitude
|
||||||
|
test('to test the property `latitude`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// num longitude
|
||||||
|
test('to test the property `longitude`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
// bool removeParent
|
// bool removeParent
|
||||||
test('to test the property `removeParent`', () async {
|
test('to test the property `removeParent`', () async {
|
||||||
// TODO
|
// TODO
|
||||||
|
|
15
mobile/openapi/test/update_asset_dto_test.dart
generated
15
mobile/openapi/test/update_asset_dto_test.dart
generated
|
@ -16,6 +16,11 @@ void main() {
|
||||||
// final instance = UpdateAssetDto();
|
// final instance = UpdateAssetDto();
|
||||||
|
|
||||||
group('test UpdateAssetDto', () {
|
group('test UpdateAssetDto', () {
|
||||||
|
// String dateTimeOriginal
|
||||||
|
test('to test the property `dateTimeOriginal`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
// String description
|
// String description
|
||||||
test('to test the property `description`', () async {
|
test('to test the property `description`', () async {
|
||||||
// TODO
|
// TODO
|
||||||
|
@ -31,6 +36,16 @@ void main() {
|
||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// num latitude
|
||||||
|
test('to test the property `latitude`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
// num longitude
|
||||||
|
test('to test the property `longitude`', () async {
|
||||||
|
// TODO
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,8 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"groupName": "base-image",
|
"groupName": "base-image",
|
||||||
"matchPackagePrefixes": ["ghcr.io/immich-app/base-server"]
|
"matchPackagePrefixes": ["ghcr.io/immich-app/base-server"],
|
||||||
|
"minimumReleaseAge": "0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"matchDatasources": ["docker"],
|
"matchDatasources": ["docker"],
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# dev build
|
# dev build
|
||||||
FROM ghcr.io/immich-app/base-server-dev:20231125@sha256:f33b6eaf384e76ef3705a6e2cc76d276144ad6d3366b82f9b45b07d6a19285e2 as dev
|
FROM ghcr.io/immich-app/base-server-dev:20231130@sha256:2f3b4bc0b50a0710e4a0867b4842ebde3a709d18fd19b095b8bfb884082cfa18 as dev
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
COPY server/package.json server/package-lock.json ./
|
COPY server/package.json server/package-lock.json ./
|
||||||
|
@ -23,7 +23,7 @@ RUN npm run build
|
||||||
|
|
||||||
|
|
||||||
# prod build
|
# prod build
|
||||||
FROM ghcr.io/immich-app/base-server-prod:20231125@sha256:a0e15f5bf87a97a79a399a5adffb5fe5befc18fb212e8341e744d958fe41e32a
|
FROM ghcr.io/immich-app/base-server-prod:20231130@sha256:dd91bfac4090357605a862823a99b50cf01cbc519723198f7aebb6b0517fab1d
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
@ -37,6 +37,7 @@ COPY server/start*.sh ./
|
||||||
RUN npm link && npm cache clean --force
|
RUN npm link && npm cache clean --force
|
||||||
COPY LICENSE /licenses/LICENSE.txt
|
COPY LICENSE /licenses/LICENSE.txt
|
||||||
COPY LICENSE /LICENSE
|
COPY LICENSE /LICENSE
|
||||||
|
ENV PATH="${PATH}:/usr/src/app/bin"
|
||||||
VOLUME /usr/src/app/upload
|
VOLUME /usr/src/app/upload
|
||||||
EXPOSE 3001
|
EXPOSE 3001
|
||||||
ENTRYPOINT ["tini", "--", "/bin/sh"]
|
ENTRYPOINT ["tini", "--", "/bin/sh"]
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/usr/bin/env sh
|
|
||||||
./start.sh admin-cli $1
|
|
|
@ -1,2 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
npx immich "$@"
|
|
2
server/bin/immich
Executable file
2
server/bin/immich
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
node /usr/src/app/node_modules/.bin/immich "$@"
|
2
server/bin/immich-admin
Executable file
2
server/bin/immich-admin
Executable file
|
@ -0,0 +1,2 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
/usr/src/app/start.sh immich-admin $1
|
|
@ -6449,6 +6449,9 @@
|
||||||
},
|
},
|
||||||
"AssetBulkUpdateDto": {
|
"AssetBulkUpdateDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"dateTimeOriginal": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"ids": {
|
"ids": {
|
||||||
"items": {
|
"items": {
|
||||||
"format": "uuid",
|
"format": "uuid",
|
||||||
|
@ -6462,6 +6465,12 @@
|
||||||
"isFavorite": {
|
"isFavorite": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"latitude": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"longitude": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
"removeParent": {
|
"removeParent": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
@ -9343,6 +9352,9 @@
|
||||||
},
|
},
|
||||||
"UpdateAssetDto": {
|
"UpdateAssetDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"dateTimeOriginal": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
@ -9351,6 +9363,12 @@
|
||||||
},
|
},
|
||||||
"isFavorite": {
|
"isFavorite": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"latitude": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"longitude": {
|
||||||
|
"type": "number"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"type": "object"
|
"type": "object"
|
||||||
|
|
170
server/package-lock.json
generated
170
server/package-lock.json
generated
|
@ -53,10 +53,6 @@
|
||||||
"typesense": "^1.7.1",
|
"typesense": "^1.7.1",
|
||||||
"ua-parser-js": "^1.0.35"
|
"ua-parser-js": "^1.0.35"
|
||||||
},
|
},
|
||||||
"bin": {
|
|
||||||
"immich": "bin/cli.sh",
|
|
||||||
"immich-admin": "bin/admin-cli.sh"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nestjs/cli": "^10.1.16",
|
"@nestjs/cli": "^10.1.16",
|
||||||
"@nestjs/schematics": "^10.0.2",
|
"@nestjs/schematics": "^10.0.2",
|
||||||
|
@ -93,6 +89,7 @@
|
||||||
"prettier-plugin-organize-imports": "^3.2.3",
|
"prettier-plugin-organize-imports": "^3.2.3",
|
||||||
"rimraf": "^5.0.1",
|
"rimraf": "^5.0.1",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
|
"sql-formatter": "^14.0.0",
|
||||||
"supertest": "^6.3.3",
|
"supertest": "^6.3.3",
|
||||||
"testcontainers": "^10.2.1",
|
"testcontainers": "^10.2.1",
|
||||||
"ts-jest": "^29.1.1",
|
"ts-jest": "^29.1.1",
|
||||||
|
@ -5504,6 +5501,12 @@
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/discontinuous-range": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/docker-compose": {
|
"node_modules/docker-compose": {
|
||||||
"version": "0.24.2",
|
"version": "0.24.2",
|
||||||
"resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.2.tgz",
|
"resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.2.tgz",
|
||||||
|
@ -6740,6 +6743,18 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/get-stdin": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/get-stream": {
|
"node_modules/get-stream": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||||
|
@ -8723,6 +8738,12 @@
|
||||||
"node": ">=12.0.0"
|
"node": ">=12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/moo": {
|
||||||
|
"version": "0.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz",
|
||||||
|
"integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/ms": {
|
"node_modules/ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
@ -8895,6 +8916,34 @@
|
||||||
"ncp": "bin/ncp"
|
"ncp": "bin/ncp"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nearley": {
|
||||||
|
"version": "2.20.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz",
|
||||||
|
"integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"commander": "^2.19.0",
|
||||||
|
"moo": "^0.5.0",
|
||||||
|
"railroad-diagrams": "^1.0.0",
|
||||||
|
"randexp": "0.4.6"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"nearley-railroad": "bin/nearley-railroad.js",
|
||||||
|
"nearley-test": "bin/nearley-test.js",
|
||||||
|
"nearley-unparse": "bin/nearley-unparse.js",
|
||||||
|
"nearleyc": "bin/nearleyc.js"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://nearley.js.org/#give-to-nearley"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/nearley/node_modules/commander": {
|
||||||
|
"version": "2.20.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||||
|
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/negotiator": {
|
"node_modules/negotiator": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||||
|
@ -9964,6 +10013,25 @@
|
||||||
"resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
|
||||||
"integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag=="
|
"integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag=="
|
||||||
},
|
},
|
||||||
|
"node_modules/railroad-diagrams": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/randexp": {
|
||||||
|
"version": "0.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz",
|
||||||
|
"integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"discontinuous-range": "1.0.0",
|
||||||
|
"ret": "~0.1.10"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/randombytes": {
|
"node_modules/randombytes": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||||
|
@ -10222,6 +10290,15 @@
|
||||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
|
||||||
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
|
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/ret": {
|
||||||
|
"version": "0.1.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
|
||||||
|
"integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
|
||||||
|
"dev": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/retry": {
|
"node_modules/retry": {
|
||||||
"version": "0.12.0",
|
"version": "0.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
|
||||||
|
@ -10806,6 +10883,20 @@
|
||||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/sql-formatter": {
|
||||||
|
"version": "14.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-14.0.0.tgz",
|
||||||
|
"integrity": "sha512-VcHYMRvZqg3RNjjxNB/puT9O1hR5QLXTvgTaBtxXcvmRQwSnH9M+oW2Ti+uFuVVU8HoNlOjU2uKHv8c0FQNsdQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"argparse": "^2.0.1",
|
||||||
|
"get-stdin": "=8.0.0",
|
||||||
|
"nearley": "^2.20.1"
|
||||||
|
},
|
||||||
|
"bin": {
|
||||||
|
"sql-formatter": "bin/sql-formatter-cli.cjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ssh-remote-port-forward": {
|
"node_modules/ssh-remote-port-forward": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/ssh-remote-port-forward/-/ssh-remote-port-forward-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/ssh-remote-port-forward/-/ssh-remote-port-forward-1.0.4.tgz",
|
||||||
|
@ -16768,6 +16859,12 @@
|
||||||
"path-type": "^4.0.0"
|
"path-type": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"discontinuous-range": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"docker-compose": {
|
"docker-compose": {
|
||||||
"version": "0.24.2",
|
"version": "0.24.2",
|
||||||
"resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.2.tgz",
|
"resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.2.tgz",
|
||||||
|
@ -17718,6 +17815,12 @@
|
||||||
"integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==",
|
"integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"get-stdin": {
|
||||||
|
"version": "8.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz",
|
||||||
|
"integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"get-stream": {
|
"get-stream": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||||
|
@ -19188,6 +19291,12 @@
|
||||||
"integrity": "sha512-2dF2R6YMSZbpip1V1WHKGLNjr/k48uQClqMVb5H3MOvwc9qhYis3/IWbj02qIg/Y8MDXKFF4c5v0rxx2o6xTZw==",
|
"integrity": "sha512-2dF2R6YMSZbpip1V1WHKGLNjr/k48uQClqMVb5H3MOvwc9qhYis3/IWbj02qIg/Y8MDXKFF4c5v0rxx2o6xTZw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"moo": {
|
||||||
|
"version": "0.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz",
|
||||||
|
"integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"ms": {
|
"ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
@ -19340,6 +19449,26 @@
|
||||||
"resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
|
||||||
"integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA=="
|
"integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA=="
|
||||||
},
|
},
|
||||||
|
"nearley": {
|
||||||
|
"version": "2.20.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz",
|
||||||
|
"integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"commander": "^2.19.0",
|
||||||
|
"moo": "^0.5.0",
|
||||||
|
"railroad-diagrams": "^1.0.0",
|
||||||
|
"randexp": "0.4.6"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"commander": {
|
||||||
|
"version": "2.20.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||||
|
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"negotiator": {
|
"negotiator": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
|
||||||
|
@ -20107,6 +20236,22 @@
|
||||||
"resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
|
||||||
"integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag=="
|
"integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag=="
|
||||||
},
|
},
|
||||||
|
"railroad-diagrams": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"randexp": {
|
||||||
|
"version": "0.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz",
|
||||||
|
"integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"discontinuous-range": "1.0.0",
|
||||||
|
"ret": "~0.1.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"randombytes": {
|
"randombytes": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||||
|
@ -20313,6 +20458,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ret": {
|
||||||
|
"version": "0.1.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
|
||||||
|
"integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"retry": {
|
"retry": {
|
||||||
"version": "0.12.0",
|
"version": "0.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
|
||||||
|
@ -20747,6 +20898,17 @@
|
||||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"sql-formatter": {
|
||||||
|
"version": "14.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-14.0.0.tgz",
|
||||||
|
"integrity": "sha512-VcHYMRvZqg3RNjjxNB/puT9O1hR5QLXTvgTaBtxXcvmRQwSnH9M+oW2Ti+uFuVVU8HoNlOjU2uKHv8c0FQNsdQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"argparse": "^2.0.1",
|
||||||
|
"get-stdin": "=8.0.0",
|
||||||
|
"nearley": "^2.20.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ssh-remote-port-forward": {
|
"ssh-remote-port-forward": {
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/ssh-remote-port-forward/-/ssh-remote-port-forward-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/ssh-remote-port-forward/-/ssh-remote-port-forward-1.0.4.tgz",
|
||||||
|
|
|
@ -5,10 +5,6 @@
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"bin": {
|
|
||||||
"immich": "./bin/cli.sh",
|
|
||||||
"immich-admin": "./bin/admin-cli.sh"
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nest build",
|
"build": "nest build",
|
||||||
"format": "prettier --check .",
|
"format": "prettier --check .",
|
||||||
|
@ -36,7 +32,8 @@
|
||||||
"typeorm:schema:reset": "npm run typeorm:schema:drop && npm run typeorm:migrations:run",
|
"typeorm:schema:reset": "npm run typeorm:schema:drop && npm run typeorm:migrations:run",
|
||||||
"api:typescript": "bash ./bin/generate-open-api.sh web",
|
"api:typescript": "bash ./bin/generate-open-api.sh web",
|
||||||
"api:dart": "bash ./bin/generate-open-api.sh mobile",
|
"api:dart": "bash ./bin/generate-open-api.sh mobile",
|
||||||
"api:generate": "node ./bin/sync-spec-version.js && bash ./bin/generate-open-api.sh"
|
"api:generate": "node ./bin/sync-spec-version.js && bash ./bin/generate-open-api.sh",
|
||||||
|
"sql:generate": "node ./dist/infra/sql-generator/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.22.11",
|
"@babel/runtime": "^7.22.11",
|
||||||
|
@ -119,6 +116,7 @@
|
||||||
"prettier-plugin-organize-imports": "^3.2.3",
|
"prettier-plugin-organize-imports": "^3.2.3",
|
||||||
"rimraf": "^5.0.1",
|
"rimraf": "^5.0.1",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
|
"sql-formatter": "^14.0.0",
|
||||||
"supertest": "^6.3.3",
|
"supertest": "^6.3.3",
|
||||||
"testcontainers": "^10.2.1",
|
"testcontainers": "^10.2.1",
|
||||||
"ts-jest": "^29.1.1",
|
"ts-jest": "^29.1.1",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
||||||
import { AuthUserDto } from '../auth';
|
import { AuthUserDto } from '../auth';
|
||||||
import { setDifference, setUnion } from '../domain.util';
|
import { setDifference, setIsEqual, setUnion } from '../domain.util';
|
||||||
import { IAccessRepository } from '../repositories';
|
import { IAccessRepository } from '../repositories';
|
||||||
|
|
||||||
export enum Permission {
|
export enum Permission {
|
||||||
|
@ -76,7 +76,7 @@ export class AccessCore {
|
||||||
async requirePermission(authUser: AuthUserDto, permission: Permission, ids: string[] | string) {
|
async requirePermission(authUser: AuthUserDto, permission: Permission, ids: string[] | string) {
|
||||||
ids = Array.isArray(ids) ? ids : [ids];
|
ids = Array.isArray(ids) ? ids : [ids];
|
||||||
const allowedIds = await this.checkAccess(authUser, permission, ids);
|
const allowedIds = await this.checkAccess(authUser, permission, ids);
|
||||||
if (new Set(ids).size !== allowedIds.size) {
|
if (!setIsEqual(new Set(ids), allowedIds)) {
|
||||||
throw new BadRequestException(`Not found or no ${permission} access`);
|
throw new BadRequestException(`Not found or no ${permission} access`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,9 +106,24 @@ export class AccessCore {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (permission) {
|
switch (permission) {
|
||||||
|
case Permission.ASSET_READ:
|
||||||
|
return await this.repository.asset.checkSharedLinkAccess(sharedLinkId, ids);
|
||||||
|
|
||||||
|
case Permission.ASSET_VIEW:
|
||||||
|
return await this.repository.asset.checkSharedLinkAccess(sharedLinkId, ids);
|
||||||
|
|
||||||
|
case Permission.ASSET_DOWNLOAD:
|
||||||
|
return !!authUser.isAllowDownload
|
||||||
|
? await this.repository.asset.checkSharedLinkAccess(sharedLinkId, ids)
|
||||||
|
: new Set();
|
||||||
|
|
||||||
case Permission.ASSET_UPLOAD:
|
case Permission.ASSET_UPLOAD:
|
||||||
return authUser.isAllowUpload ? ids : new Set();
|
return authUser.isAllowUpload ? ids : new Set();
|
||||||
|
|
||||||
|
case Permission.ASSET_SHARE:
|
||||||
|
// TODO: fix this to not use authUser.id for shared link access control
|
||||||
|
return await this.repository.asset.checkOwnerAccess(authUser.id, ids);
|
||||||
|
|
||||||
case Permission.ALBUM_READ:
|
case Permission.ALBUM_READ:
|
||||||
return await this.repository.album.checkSharedLinkAccess(sharedLinkId, ids);
|
return await this.repository.album.checkSharedLinkAccess(sharedLinkId, ids);
|
||||||
|
|
||||||
|
@ -116,46 +131,59 @@ export class AccessCore {
|
||||||
return !!authUser.isAllowDownload
|
return !!authUser.isAllowDownload
|
||||||
? await this.repository.album.checkSharedLinkAccess(sharedLinkId, ids)
|
? await this.repository.album.checkSharedLinkAccess(sharedLinkId, ids)
|
||||||
: new Set();
|
: new Set();
|
||||||
}
|
|
||||||
|
|
||||||
const allowedIds = new Set();
|
|
||||||
for (const id of ids) {
|
|
||||||
const hasAccess = await this.hasSharedLinkAccess(authUser, permission, id);
|
|
||||||
if (hasAccess) {
|
|
||||||
allowedIds.add(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return allowedIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Migrate logic to checkAccessSharedLink to evaluate permissions in bulk.
|
|
||||||
private async hasSharedLinkAccess(authUser: AuthUserDto, permission: Permission, id: string) {
|
|
||||||
const sharedLinkId = authUser.sharedLinkId;
|
|
||||||
if (!sharedLinkId) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (permission) {
|
|
||||||
case Permission.ASSET_READ:
|
|
||||||
return this.repository.asset.hasSharedLinkAccess(sharedLinkId, id);
|
|
||||||
|
|
||||||
case Permission.ASSET_VIEW:
|
|
||||||
return await this.repository.asset.hasSharedLinkAccess(sharedLinkId, id);
|
|
||||||
|
|
||||||
case Permission.ASSET_DOWNLOAD:
|
|
||||||
return !!authUser.isAllowDownload && (await this.repository.asset.hasSharedLinkAccess(sharedLinkId, id));
|
|
||||||
|
|
||||||
case Permission.ASSET_SHARE:
|
|
||||||
// TODO: fix this to not use authUser.id for shared link access control
|
|
||||||
return this.repository.asset.hasOwnerAccess(authUser.id, id);
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return false;
|
return new Set();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async checkAccessOther(authUser: AuthUserDto, permission: Permission, ids: Set<string>) {
|
private async checkAccessOther(authUser: AuthUserDto, permission: Permission, ids: Set<string>) {
|
||||||
switch (permission) {
|
switch (permission) {
|
||||||
|
case Permission.ASSET_READ: {
|
||||||
|
const isOwner = await this.repository.asset.checkOwnerAccess(authUser.id, ids);
|
||||||
|
const isAlbum = await this.repository.asset.checkAlbumAccess(authUser.id, setDifference(ids, isOwner));
|
||||||
|
const isPartner = await this.repository.asset.checkPartnerAccess(
|
||||||
|
authUser.id,
|
||||||
|
setDifference(ids, isOwner, isAlbum),
|
||||||
|
);
|
||||||
|
return setUnion(isOwner, isAlbum, isPartner);
|
||||||
|
}
|
||||||
|
|
||||||
|
case Permission.ASSET_SHARE: {
|
||||||
|
const isOwner = await this.repository.asset.checkOwnerAccess(authUser.id, ids);
|
||||||
|
const isPartner = await this.repository.asset.checkPartnerAccess(authUser.id, setDifference(ids, isOwner));
|
||||||
|
return setUnion(isOwner, isPartner);
|
||||||
|
}
|
||||||
|
|
||||||
|
case Permission.ASSET_VIEW: {
|
||||||
|
const isOwner = await this.repository.asset.checkOwnerAccess(authUser.id, ids);
|
||||||
|
const isAlbum = await this.repository.asset.checkAlbumAccess(authUser.id, setDifference(ids, isOwner));
|
||||||
|
const isPartner = await this.repository.asset.checkPartnerAccess(
|
||||||
|
authUser.id,
|
||||||
|
setDifference(ids, isOwner, isAlbum),
|
||||||
|
);
|
||||||
|
return setUnion(isOwner, isAlbum, isPartner);
|
||||||
|
}
|
||||||
|
|
||||||
|
case Permission.ASSET_DOWNLOAD: {
|
||||||
|
const isOwner = await this.repository.asset.checkOwnerAccess(authUser.id, ids);
|
||||||
|
const isAlbum = await this.repository.asset.checkAlbumAccess(authUser.id, setDifference(ids, isOwner));
|
||||||
|
const isPartner = await this.repository.asset.checkPartnerAccess(
|
||||||
|
authUser.id,
|
||||||
|
setDifference(ids, isOwner, isAlbum),
|
||||||
|
);
|
||||||
|
return setUnion(isOwner, isAlbum, isPartner);
|
||||||
|
}
|
||||||
|
|
||||||
|
case Permission.ASSET_UPDATE:
|
||||||
|
return await this.repository.asset.checkOwnerAccess(authUser.id, ids);
|
||||||
|
|
||||||
|
case Permission.ASSET_DELETE:
|
||||||
|
return await this.repository.asset.checkOwnerAccess(authUser.id, ids);
|
||||||
|
|
||||||
|
case Permission.ASSET_RESTORE:
|
||||||
|
return await this.repository.asset.checkOwnerAccess(authUser.id, ids);
|
||||||
|
|
||||||
case Permission.ALBUM_READ: {
|
case Permission.ALBUM_READ: {
|
||||||
const isOwner = await this.repository.album.checkOwnerAccess(authUser.id, ids);
|
const isOwner = await this.repository.album.checkOwnerAccess(authUser.id, ids);
|
||||||
const isShared = await this.repository.album.checkSharedAlbumAccess(authUser.id, setDifference(ids, isOwner));
|
const isShared = await this.repository.album.checkSharedAlbumAccess(authUser.id, setDifference(ids, isOwner));
|
||||||
|
@ -163,13 +191,13 @@ export class AccessCore {
|
||||||
}
|
}
|
||||||
|
|
||||||
case Permission.ALBUM_UPDATE:
|
case Permission.ALBUM_UPDATE:
|
||||||
return this.repository.album.checkOwnerAccess(authUser.id, ids);
|
return await this.repository.album.checkOwnerAccess(authUser.id, ids);
|
||||||
|
|
||||||
case Permission.ALBUM_DELETE:
|
case Permission.ALBUM_DELETE:
|
||||||
return this.repository.album.checkOwnerAccess(authUser.id, ids);
|
return await this.repository.album.checkOwnerAccess(authUser.id, ids);
|
||||||
|
|
||||||
case Permission.ALBUM_SHARE:
|
case Permission.ALBUM_SHARE:
|
||||||
return this.repository.album.checkOwnerAccess(authUser.id, ids);
|
return await this.repository.album.checkOwnerAccess(authUser.id, ids);
|
||||||
|
|
||||||
case Permission.ALBUM_DOWNLOAD: {
|
case Permission.ALBUM_DOWNLOAD: {
|
||||||
const isOwner = await this.repository.album.checkOwnerAccess(authUser.id, ids);
|
const isOwner = await this.repository.album.checkOwnerAccess(authUser.id, ids);
|
||||||
|
@ -178,16 +206,16 @@ export class AccessCore {
|
||||||
}
|
}
|
||||||
|
|
||||||
case Permission.ALBUM_REMOVE_ASSET:
|
case Permission.ALBUM_REMOVE_ASSET:
|
||||||
return this.repository.album.checkOwnerAccess(authUser.id, ids);
|
return await this.repository.album.checkOwnerAccess(authUser.id, ids);
|
||||||
|
|
||||||
case Permission.ASSET_UPLOAD:
|
case Permission.ASSET_UPLOAD:
|
||||||
return this.repository.library.checkOwnerAccess(authUser.id, ids);
|
return await this.repository.library.checkOwnerAccess(authUser.id, ids);
|
||||||
|
|
||||||
case Permission.ARCHIVE_READ:
|
case Permission.ARCHIVE_READ:
|
||||||
return ids.has(authUser.id) ? new Set([authUser.id]) : new Set();
|
return ids.has(authUser.id) ? new Set([authUser.id]) : new Set();
|
||||||
|
|
||||||
case Permission.AUTH_DEVICE_DELETE:
|
case Permission.AUTH_DEVICE_DELETE:
|
||||||
return this.repository.authDevice.checkOwnerAccess(authUser.id, ids);
|
return await this.repository.authDevice.checkOwnerAccess(authUser.id, ids);
|
||||||
|
|
||||||
case Permission.TIMELINE_READ: {
|
case Permission.TIMELINE_READ: {
|
||||||
const isOwner = ids.has(authUser.id) ? new Set([authUser.id]) : new Set<string>();
|
const isOwner = ids.has(authUser.id) ? new Set([authUser.id]) : new Set<string>();
|
||||||
|
@ -205,22 +233,22 @@ export class AccessCore {
|
||||||
}
|
}
|
||||||
|
|
||||||
case Permission.LIBRARY_UPDATE:
|
case Permission.LIBRARY_UPDATE:
|
||||||
return this.repository.library.checkOwnerAccess(authUser.id, ids);
|
return await this.repository.library.checkOwnerAccess(authUser.id, ids);
|
||||||
|
|
||||||
case Permission.LIBRARY_DELETE:
|
case Permission.LIBRARY_DELETE:
|
||||||
return this.repository.library.checkOwnerAccess(authUser.id, ids);
|
return await this.repository.library.checkOwnerAccess(authUser.id, ids);
|
||||||
|
|
||||||
case Permission.PERSON_READ:
|
case Permission.PERSON_READ:
|
||||||
return this.repository.person.checkOwnerAccess(authUser.id, ids);
|
return await this.repository.person.checkOwnerAccess(authUser.id, ids);
|
||||||
|
|
||||||
case Permission.PERSON_WRITE:
|
case Permission.PERSON_WRITE:
|
||||||
return this.repository.person.checkOwnerAccess(authUser.id, ids);
|
return await this.repository.person.checkOwnerAccess(authUser.id, ids);
|
||||||
|
|
||||||
case Permission.PERSON_MERGE:
|
case Permission.PERSON_MERGE:
|
||||||
return this.repository.person.checkOwnerAccess(authUser.id, ids);
|
return await this.repository.person.checkOwnerAccess(authUser.id, ids);
|
||||||
|
|
||||||
case Permission.PARTNER_UPDATE:
|
case Permission.PARTNER_UPDATE:
|
||||||
return this.repository.partner.checkUpdateAccess(authUser.id, ids);
|
return await this.repository.partner.checkUpdateAccess(authUser.id, ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
const allowedIds = new Set();
|
const allowedIds = new Set();
|
||||||
|
@ -247,41 +275,6 @@ export class AccessCore {
|
||||||
(await this.repository.activity.hasAlbumOwnerAccess(authUser.id, id))
|
(await this.repository.activity.hasAlbumOwnerAccess(authUser.id, id))
|
||||||
);
|
);
|
||||||
|
|
||||||
case Permission.ASSET_READ:
|
|
||||||
return (
|
|
||||||
(await this.repository.asset.hasOwnerAccess(authUser.id, id)) ||
|
|
||||||
(await this.repository.asset.hasAlbumAccess(authUser.id, id)) ||
|
|
||||||
(await this.repository.asset.hasPartnerAccess(authUser.id, id))
|
|
||||||
);
|
|
||||||
case Permission.ASSET_UPDATE:
|
|
||||||
return this.repository.asset.hasOwnerAccess(authUser.id, id);
|
|
||||||
|
|
||||||
case Permission.ASSET_DELETE:
|
|
||||||
return this.repository.asset.hasOwnerAccess(authUser.id, id);
|
|
||||||
|
|
||||||
case Permission.ASSET_RESTORE:
|
|
||||||
return this.repository.asset.hasOwnerAccess(authUser.id, id);
|
|
||||||
|
|
||||||
case Permission.ASSET_SHARE:
|
|
||||||
return (
|
|
||||||
(await this.repository.asset.hasOwnerAccess(authUser.id, id)) ||
|
|
||||||
(await this.repository.asset.hasPartnerAccess(authUser.id, id))
|
|
||||||
);
|
|
||||||
|
|
||||||
case Permission.ASSET_VIEW:
|
|
||||||
return (
|
|
||||||
(await this.repository.asset.hasOwnerAccess(authUser.id, id)) ||
|
|
||||||
(await this.repository.asset.hasAlbumAccess(authUser.id, id)) ||
|
|
||||||
(await this.repository.asset.hasPartnerAccess(authUser.id, id))
|
|
||||||
);
|
|
||||||
|
|
||||||
case Permission.ASSET_DOWNLOAD:
|
|
||||||
return (
|
|
||||||
(await this.repository.asset.hasOwnerAccess(authUser.id, id)) ||
|
|
||||||
(await this.repository.asset.hasAlbumAccess(authUser.id, id)) ||
|
|
||||||
(await this.repository.asset.hasPartnerAccess(authUser.id, id))
|
|
||||||
);
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -509,7 +509,7 @@ describe(AlbumService.name, () => {
|
||||||
describe('addAssets', () => {
|
describe('addAssets', () => {
|
||||||
it('should allow the owner to add assets', async () => {
|
it('should allow the owner to add assets', async () => {
|
||||||
accessMock.album.checkOwnerAccess.mockResolvedValue(new Set(['album-123']));
|
accessMock.album.checkOwnerAccess.mockResolvedValue(new Set(['album-123']));
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3']));
|
||||||
albumMock.getById.mockResolvedValue(_.cloneDeep(albumStub.oneAsset));
|
albumMock.getById.mockResolvedValue(_.cloneDeep(albumStub.oneAsset));
|
||||||
albumMock.getAssetIds.mockResolvedValueOnce(new Set());
|
albumMock.getAssetIds.mockResolvedValueOnce(new Set());
|
||||||
|
|
||||||
|
@ -534,7 +534,7 @@ describe(AlbumService.name, () => {
|
||||||
|
|
||||||
it('should not set the thumbnail if the album has one already', async () => {
|
it('should not set the thumbnail if the album has one already', async () => {
|
||||||
accessMock.album.checkOwnerAccess.mockResolvedValue(new Set(['album-123']));
|
accessMock.album.checkOwnerAccess.mockResolvedValue(new Set(['album-123']));
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||||
albumMock.getById.mockResolvedValue(_.cloneDeep({ ...albumStub.empty, albumThumbnailAssetId: 'asset-id' }));
|
albumMock.getById.mockResolvedValue(_.cloneDeep({ ...albumStub.empty, albumThumbnailAssetId: 'asset-id' }));
|
||||||
albumMock.getAssetIds.mockResolvedValueOnce(new Set());
|
albumMock.getAssetIds.mockResolvedValueOnce(new Set());
|
||||||
|
|
||||||
|
@ -552,7 +552,7 @@ describe(AlbumService.name, () => {
|
||||||
|
|
||||||
it('should allow a shared user to add assets', async () => {
|
it('should allow a shared user to add assets', async () => {
|
||||||
accessMock.album.checkSharedAlbumAccess.mockResolvedValue(new Set(['album-123']));
|
accessMock.album.checkSharedAlbumAccess.mockResolvedValue(new Set(['album-123']));
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3']));
|
||||||
albumMock.getById.mockResolvedValue(_.cloneDeep(albumStub.sharedWithUser));
|
albumMock.getById.mockResolvedValue(_.cloneDeep(albumStub.sharedWithUser));
|
||||||
albumMock.getAssetIds.mockResolvedValueOnce(new Set());
|
albumMock.getAssetIds.mockResolvedValueOnce(new Set());
|
||||||
|
|
||||||
|
@ -577,7 +577,7 @@ describe(AlbumService.name, () => {
|
||||||
|
|
||||||
it('should allow a shared link user to add assets', async () => {
|
it('should allow a shared link user to add assets', async () => {
|
||||||
accessMock.album.checkSharedLinkAccess.mockResolvedValue(new Set(['album-123']));
|
accessMock.album.checkSharedLinkAccess.mockResolvedValue(new Set(['album-123']));
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2', 'asset-3']));
|
||||||
albumMock.getById.mockResolvedValue(_.cloneDeep(albumStub.oneAsset));
|
albumMock.getById.mockResolvedValue(_.cloneDeep(albumStub.oneAsset));
|
||||||
albumMock.getAssetIds.mockResolvedValueOnce(new Set());
|
albumMock.getAssetIds.mockResolvedValueOnce(new Set());
|
||||||
|
|
||||||
|
@ -607,8 +607,7 @@ describe(AlbumService.name, () => {
|
||||||
|
|
||||||
it('should allow adding assets shared via partner sharing', async () => {
|
it('should allow adding assets shared via partner sharing', async () => {
|
||||||
accessMock.album.checkOwnerAccess.mockResolvedValue(new Set(['album-123']));
|
accessMock.album.checkOwnerAccess.mockResolvedValue(new Set(['album-123']));
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
accessMock.asset.checkPartnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||||
accessMock.asset.hasPartnerAccess.mockResolvedValue(true);
|
|
||||||
albumMock.getById.mockResolvedValue(_.cloneDeep(albumStub.oneAsset));
|
albumMock.getById.mockResolvedValue(_.cloneDeep(albumStub.oneAsset));
|
||||||
albumMock.getAssetIds.mockResolvedValueOnce(new Set());
|
albumMock.getAssetIds.mockResolvedValueOnce(new Set());
|
||||||
|
|
||||||
|
@ -621,12 +620,12 @@ describe(AlbumService.name, () => {
|
||||||
updatedAt: expect.any(Date),
|
updatedAt: expect.any(Date),
|
||||||
albumThumbnailAssetId: 'asset-1',
|
albumThumbnailAssetId: 'asset-1',
|
||||||
});
|
});
|
||||||
expect(accessMock.asset.hasPartnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'asset-1');
|
expect(accessMock.asset.checkPartnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['asset-1']));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should skip duplicate assets', async () => {
|
it('should skip duplicate assets', async () => {
|
||||||
accessMock.album.checkOwnerAccess.mockResolvedValue(new Set(['album-123']));
|
accessMock.album.checkOwnerAccess.mockResolvedValue(new Set(['album-123']));
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-id']));
|
||||||
albumMock.getById.mockResolvedValue(_.cloneDeep(albumStub.oneAsset));
|
albumMock.getById.mockResolvedValue(_.cloneDeep(albumStub.oneAsset));
|
||||||
albumMock.getAssetIds.mockResolvedValueOnce(new Set(['asset-id']));
|
albumMock.getAssetIds.mockResolvedValueOnce(new Set(['asset-id']));
|
||||||
|
|
||||||
|
@ -639,8 +638,6 @@ describe(AlbumService.name, () => {
|
||||||
|
|
||||||
it('should skip assets not shared with user', async () => {
|
it('should skip assets not shared with user', async () => {
|
||||||
accessMock.album.checkOwnerAccess.mockResolvedValue(new Set(['album-123']));
|
accessMock.album.checkOwnerAccess.mockResolvedValue(new Set(['album-123']));
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
|
||||||
accessMock.asset.hasPartnerAccess.mockResolvedValue(false);
|
|
||||||
albumMock.getById.mockResolvedValue(albumStub.oneAsset);
|
albumMock.getById.mockResolvedValue(albumStub.oneAsset);
|
||||||
albumMock.getAssetIds.mockResolvedValueOnce(new Set());
|
albumMock.getAssetIds.mockResolvedValueOnce(new Set());
|
||||||
|
|
||||||
|
@ -648,8 +645,8 @@ describe(AlbumService.name, () => {
|
||||||
{ success: false, id: 'asset-1', error: BulkIdErrorReason.NO_PERMISSION },
|
{ success: false, id: 'asset-1', error: BulkIdErrorReason.NO_PERMISSION },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(accessMock.asset.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'asset-1');
|
expect(accessMock.asset.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['asset-1']));
|
||||||
expect(accessMock.asset.hasPartnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'asset-1');
|
expect(accessMock.asset.checkPartnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['asset-1']));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not allow unauthorized access to the album', async () => {
|
it('should not allow unauthorized access to the album', async () => {
|
||||||
|
|
|
@ -457,19 +457,15 @@ describe(AssetService.name, () => {
|
||||||
|
|
||||||
describe('downloadFile', () => {
|
describe('downloadFile', () => {
|
||||||
it('should require the asset.download permission', async () => {
|
it('should require the asset.download permission', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
|
||||||
accessMock.asset.hasAlbumAccess.mockResolvedValue(false);
|
|
||||||
accessMock.asset.hasPartnerAccess.mockResolvedValue(false);
|
|
||||||
|
|
||||||
await expect(sut.downloadFile(authStub.admin, 'asset-1')).rejects.toBeInstanceOf(BadRequestException);
|
await expect(sut.downloadFile(authStub.admin, 'asset-1')).rejects.toBeInstanceOf(BadRequestException);
|
||||||
|
|
||||||
expect(accessMock.asset.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'asset-1');
|
expect(accessMock.asset.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['asset-1']));
|
||||||
expect(accessMock.asset.hasAlbumAccess).toHaveBeenCalledWith(authStub.admin.id, 'asset-1');
|
expect(accessMock.asset.checkAlbumAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['asset-1']));
|
||||||
expect(accessMock.asset.hasPartnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'asset-1');
|
expect(accessMock.asset.checkPartnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['asset-1']));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if the asset is not found', async () => {
|
it('should throw an error if the asset is not found', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||||
assetMock.getByIds.mockResolvedValue([]);
|
assetMock.getByIds.mockResolvedValue([]);
|
||||||
|
|
||||||
await expect(sut.downloadFile(authStub.admin, 'asset-1')).rejects.toBeInstanceOf(BadRequestException);
|
await expect(sut.downloadFile(authStub.admin, 'asset-1')).rejects.toBeInstanceOf(BadRequestException);
|
||||||
|
@ -480,7 +476,7 @@ describe(AssetService.name, () => {
|
||||||
it('should download a file', async () => {
|
it('should download a file', async () => {
|
||||||
const stream = new Readable();
|
const stream = new Readable();
|
||||||
|
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||||
storageMock.createReadStream.mockResolvedValue({ stream });
|
storageMock.createReadStream.mockResolvedValue({ stream });
|
||||||
|
|
||||||
|
@ -496,7 +492,7 @@ describe(AssetService.name, () => {
|
||||||
stream: new Readable(),
|
stream: new Readable(),
|
||||||
};
|
};
|
||||||
|
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2']));
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.noResizePath, assetStub.noWebpPath]);
|
assetMock.getByIds.mockResolvedValue([assetStub.noResizePath, assetStub.noWebpPath]);
|
||||||
storageMock.createZipStream.mockReturnValue(archiveMock);
|
storageMock.createZipStream.mockReturnValue(archiveMock);
|
||||||
|
|
||||||
|
@ -516,7 +512,7 @@ describe(AssetService.name, () => {
|
||||||
stream: new Readable(),
|
stream: new Readable(),
|
||||||
};
|
};
|
||||||
|
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2']));
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.noResizePath, assetStub.noResizePath]);
|
assetMock.getByIds.mockResolvedValue([assetStub.noResizePath, assetStub.noResizePath]);
|
||||||
storageMock.createZipStream.mockReturnValue(archiveMock);
|
storageMock.createZipStream.mockReturnValue(archiveMock);
|
||||||
|
|
||||||
|
@ -536,7 +532,7 @@ describe(AssetService.name, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return a list of archives (assetIds)', async () => {
|
it('should return a list of archives (assetIds)', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2']));
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.image, assetStub.video]);
|
assetMock.getByIds.mockResolvedValue([assetStub.image, assetStub.video]);
|
||||||
|
|
||||||
const assetIds = ['asset-1', 'asset-2'];
|
const assetIds = ['asset-1', 'asset-2'];
|
||||||
|
@ -602,7 +598,9 @@ describe(AssetService.name, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should include the video portion of a live photo', async () => {
|
it('should include the video portion of a live photo', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
const assetIds = [assetStub.livePhotoStillAsset.id];
|
||||||
|
|
||||||
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(assetIds));
|
||||||
when(assetMock.getByIds)
|
when(assetMock.getByIds)
|
||||||
.calledWith([assetStub.livePhotoStillAsset.id])
|
.calledWith([assetStub.livePhotoStillAsset.id])
|
||||||
.mockResolvedValue([assetStub.livePhotoStillAsset]);
|
.mockResolvedValue([assetStub.livePhotoStillAsset]);
|
||||||
|
@ -610,7 +608,6 @@ describe(AssetService.name, () => {
|
||||||
.calledWith([assetStub.livePhotoMotionAsset.id])
|
.calledWith([assetStub.livePhotoMotionAsset.id])
|
||||||
.mockResolvedValue([assetStub.livePhotoMotionAsset]);
|
.mockResolvedValue([assetStub.livePhotoMotionAsset]);
|
||||||
|
|
||||||
const assetIds = [assetStub.livePhotoStillAsset.id];
|
|
||||||
await expect(sut.getDownloadInfo(authStub.admin, { assetIds })).resolves.toEqual({
|
await expect(sut.getDownloadInfo(authStub.admin, { assetIds })).resolves.toEqual({
|
||||||
totalSize: 125_000,
|
totalSize: 125_000,
|
||||||
archives: [
|
archives: [
|
||||||
|
@ -651,7 +648,6 @@ describe(AssetService.name, () => {
|
||||||
|
|
||||||
describe('update', () => {
|
describe('update', () => {
|
||||||
it('should require asset write access for the id', async () => {
|
it('should require asset write access for the id', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
|
||||||
await expect(sut.update(authStub.admin, 'asset-1', { isArchived: false })).rejects.toBeInstanceOf(
|
await expect(sut.update(authStub.admin, 'asset-1', { isArchived: false })).rejects.toBeInstanceOf(
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
);
|
);
|
||||||
|
@ -659,14 +655,14 @@ describe(AssetService.name, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update the asset', async () => {
|
it('should update the asset', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||||
assetMock.save.mockResolvedValue(assetStub.image);
|
assetMock.save.mockResolvedValue(assetStub.image);
|
||||||
await sut.update(authStub.admin, 'asset-1', { isFavorite: true });
|
await sut.update(authStub.admin, 'asset-1', { isFavorite: true });
|
||||||
expect(assetMock.save).toHaveBeenCalledWith({ id: 'asset-1', isFavorite: true });
|
expect(assetMock.save).toHaveBeenCalledWith({ id: 'asset-1', isFavorite: true });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update the exif description', async () => {
|
it('should update the exif description', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||||
assetMock.save.mockResolvedValue(assetStub.image);
|
assetMock.save.mockResolvedValue(assetStub.image);
|
||||||
await sut.update(authStub.admin, 'asset-1', { description: 'Test description' });
|
await sut.update(authStub.admin, 'asset-1', { description: 'Test description' });
|
||||||
expect(assetMock.upsertExif).toHaveBeenCalledWith({ assetId: 'asset-1', description: 'Test description' });
|
expect(assetMock.upsertExif).toHaveBeenCalledWith({ assetId: 'asset-1', description: 'Test description' });
|
||||||
|
@ -675,7 +671,6 @@ describe(AssetService.name, () => {
|
||||||
|
|
||||||
describe('updateAll', () => {
|
describe('updateAll', () => {
|
||||||
it('should require asset write access for all ids', async () => {
|
it('should require asset write access for all ids', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.updateAll(authStub.admin, {
|
sut.updateAll(authStub.admin, {
|
||||||
ids: ['asset-1'],
|
ids: ['asset-1'],
|
||||||
|
@ -685,7 +680,7 @@ describe(AssetService.name, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update all assets', async () => {
|
it('should update all assets', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1', 'asset-2']));
|
||||||
await sut.updateAll(authStub.admin, { ids: ['asset-1', 'asset-2'], isArchived: true });
|
await sut.updateAll(authStub.admin, { ids: ['asset-1', 'asset-2'], isArchived: true });
|
||||||
expect(assetMock.updateAll).toHaveBeenCalledWith(['asset-1', 'asset-2'], { isArchived: true });
|
expect(assetMock.updateAll).toHaveBeenCalledWith(['asset-1', 'asset-2'], { isArchived: true });
|
||||||
});
|
});
|
||||||
|
@ -693,8 +688,7 @@ describe(AssetService.name, () => {
|
||||||
/// Stack related
|
/// Stack related
|
||||||
|
|
||||||
it('should require asset update access for parent', async () => {
|
it('should require asset update access for parent', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||||
when(accessMock.asset.hasOwnerAccess).calledWith(authStub.user1.id, 'parent').mockResolvedValue(false);
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.updateAll(authStub.user1, {
|
sut.updateAll(authStub.user1, {
|
||||||
ids: ['asset-1'],
|
ids: ['asset-1'],
|
||||||
|
@ -704,7 +698,7 @@ describe(AssetService.name, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update parent asset when children are added', async () => {
|
it('should update parent asset when children are added', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['parent']));
|
||||||
await sut.updateAll(authStub.user1, {
|
await sut.updateAll(authStub.user1, {
|
||||||
ids: [],
|
ids: [],
|
||||||
stackParentId: 'parent',
|
stackParentId: 'parent',
|
||||||
|
@ -713,7 +707,7 @@ describe(AssetService.name, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update parent asset when children are removed', async () => {
|
it('should update parent asset when children are removed', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['child-1']));
|
||||||
assetMock.getByIds.mockResolvedValue([{ id: 'child-1', stackParentId: 'parent' } as AssetEntity]);
|
assetMock.getByIds.mockResolvedValue([{ id: 'child-1', stackParentId: 'parent' } as AssetEntity]);
|
||||||
|
|
||||||
await sut.updateAll(authStub.user1, {
|
await sut.updateAll(authStub.user1, {
|
||||||
|
@ -724,7 +718,8 @@ describe(AssetService.name, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('update parentId for new children', async () => {
|
it('update parentId for new children', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['child-1', 'child-2']));
|
||||||
|
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['parent']));
|
||||||
await sut.updateAll(authStub.user1, {
|
await sut.updateAll(authStub.user1, {
|
||||||
stackParentId: 'parent',
|
stackParentId: 'parent',
|
||||||
ids: ['child-1', 'child-2'],
|
ids: ['child-1', 'child-2'],
|
||||||
|
@ -734,7 +729,7 @@ describe(AssetService.name, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('nullify parentId for remove children', async () => {
|
it('nullify parentId for remove children', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['child-1', 'child-2']));
|
||||||
await sut.updateAll(authStub.user1, {
|
await sut.updateAll(authStub.user1, {
|
||||||
removeParent: true,
|
removeParent: true,
|
||||||
ids: ['child-1', 'child-2'],
|
ids: ['child-1', 'child-2'],
|
||||||
|
@ -744,7 +739,8 @@ describe(AssetService.name, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('merge stacks if new child has children', async () => {
|
it('merge stacks if new child has children', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['child-1']));
|
||||||
|
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['parent']));
|
||||||
assetMock.getByIds.mockResolvedValue([
|
assetMock.getByIds.mockResolvedValue([
|
||||||
{ id: 'child-1', stack: [{ id: 'child-2' } as AssetEntity] } as AssetEntity,
|
{ id: 'child-1', stack: [{ id: 'child-2' } as AssetEntity] } as AssetEntity,
|
||||||
]);
|
]);
|
||||||
|
@ -758,7 +754,9 @@ describe(AssetService.name, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should send ws asset update event', async () => {
|
it('should send ws asset update event', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['asset-1']));
|
||||||
|
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['parent']));
|
||||||
|
|
||||||
await sut.updateAll(authStub.user1, {
|
await sut.updateAll(authStub.user1, {
|
||||||
ids: ['asset-1'],
|
ids: ['asset-1'],
|
||||||
stackParentId: 'parent',
|
stackParentId: 'parent',
|
||||||
|
@ -772,7 +770,6 @@ describe(AssetService.name, () => {
|
||||||
|
|
||||||
describe('deleteAll', () => {
|
describe('deleteAll', () => {
|
||||||
it('should require asset delete access for all ids', async () => {
|
it('should require asset delete access for all ids', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.deleteAll(authStub.user1, {
|
sut.deleteAll(authStub.user1, {
|
||||||
ids: ['asset-1'],
|
ids: ['asset-1'],
|
||||||
|
@ -781,7 +778,7 @@ describe(AssetService.name, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should force delete a batch of assets', async () => {
|
it('should force delete a batch of assets', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset1', 'asset2']));
|
||||||
|
|
||||||
await sut.deleteAll(authStub.user1, { ids: ['asset1', 'asset2'], force: true });
|
await sut.deleteAll(authStub.user1, { ids: ['asset1', 'asset2'], force: true });
|
||||||
|
|
||||||
|
@ -792,7 +789,7 @@ describe(AssetService.name, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should soft delete a batch of assets', async () => {
|
it('should soft delete a batch of assets', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset1', 'asset2']));
|
||||||
|
|
||||||
await sut.deleteAll(authStub.user1, { ids: ['asset1', 'asset2'], force: false });
|
await sut.deleteAll(authStub.user1, { ids: ['asset1', 'asset2'], force: false });
|
||||||
|
|
||||||
|
@ -810,7 +807,6 @@ describe(AssetService.name, () => {
|
||||||
|
|
||||||
describe('restoreAll', () => {
|
describe('restoreAll', () => {
|
||||||
it('should require asset restore access for all ids', async () => {
|
it('should require asset restore access for all ids', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.deleteAll(authStub.user1, {
|
sut.deleteAll(authStub.user1, {
|
||||||
ids: ['asset-1'],
|
ids: ['asset-1'],
|
||||||
|
@ -819,7 +815,7 @@ describe(AssetService.name, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should restore a batch of assets', async () => {
|
it('should restore a batch of assets', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset1', 'asset2']));
|
||||||
|
|
||||||
await sut.restoreAll(authStub.user1, { ids: ['asset1', 'asset2'] });
|
await sut.restoreAll(authStub.user1, { ids: ['asset1', 'asset2'] });
|
||||||
|
|
||||||
|
@ -984,19 +980,19 @@ describe(AssetService.name, () => {
|
||||||
|
|
||||||
describe('run', () => {
|
describe('run', () => {
|
||||||
it('should run the refresh metadata job', async () => {
|
it('should run the refresh metadata job', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||||
await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REFRESH_METADATA }),
|
await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REFRESH_METADATA }),
|
||||||
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.METADATA_EXTRACTION, data: { id: 'asset-1' } });
|
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.METADATA_EXTRACTION, data: { id: 'asset-1' } });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should run the refresh thumbnails job', async () => {
|
it('should run the refresh thumbnails job', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||||
await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REGENERATE_THUMBNAIL }),
|
await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.REGENERATE_THUMBNAIL }),
|
||||||
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id: 'asset-1' } });
|
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id: 'asset-1' } });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should run the transcode video', async () => {
|
it('should run the transcode video', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||||
await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.TRANSCODE_VIDEO }),
|
await sut.run(authStub.admin, { assetIds: ['asset-1'], name: AssetJobName.TRANSCODE_VIDEO }),
|
||||||
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.VIDEO_CONVERSION, data: { id: 'asset-1' } });
|
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.VIDEO_CONVERSION, data: { id: 'asset-1' } });
|
||||||
});
|
});
|
||||||
|
@ -1004,9 +1000,7 @@ describe(AssetService.name, () => {
|
||||||
|
|
||||||
describe('updateStackParent', () => {
|
describe('updateStackParent', () => {
|
||||||
it('should require asset update access for new parent', async () => {
|
it('should require asset update access for new parent', async () => {
|
||||||
when(accessMock.asset.hasOwnerAccess).calledWith(authStub.user1.id, 'old').mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['old']));
|
||||||
when(accessMock.asset.hasOwnerAccess).calledWith(authStub.user1.id, 'new').mockResolvedValue(false);
|
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.updateStackParent(authStub.user1, {
|
sut.updateStackParent(authStub.user1, {
|
||||||
oldParentId: 'old',
|
oldParentId: 'old',
|
||||||
|
@ -1016,8 +1010,7 @@ describe(AssetService.name, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require asset read access for old parent', async () => {
|
it('should require asset read access for old parent', async () => {
|
||||||
when(accessMock.asset.hasOwnerAccess).calledWith(authStub.user1.id, 'old').mockResolvedValue(false);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['new']));
|
||||||
when(accessMock.asset.hasOwnerAccess).calledWith(authStub.user1.id, 'new').mockResolvedValue(true);
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.updateStackParent(authStub.user1, {
|
sut.updateStackParent(authStub.user1, {
|
||||||
oldParentId: 'old',
|
oldParentId: 'old',
|
||||||
|
@ -1027,7 +1020,9 @@ describe(AssetService.name, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('make old parent the child of new parent', async () => {
|
it('make old parent the child of new parent', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set([assetStub.image.id]));
|
||||||
|
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['new']));
|
||||||
|
|
||||||
when(assetMock.getById)
|
when(assetMock.getById)
|
||||||
.calledWith(assetStub.image.id)
|
.calledWith(assetStub.image.id)
|
||||||
.mockResolvedValue(assetStub.image as AssetEntity);
|
.mockResolvedValue(assetStub.image as AssetEntity);
|
||||||
|
@ -1041,7 +1036,9 @@ describe(AssetService.name, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('remove stackParentId of new parent', async () => {
|
it('remove stackParentId of new parent', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set([assetStub.primaryImage.id]));
|
||||||
|
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['new']));
|
||||||
|
|
||||||
await sut.updateStackParent(authStub.user1, {
|
await sut.updateStackParent(authStub.user1, {
|
||||||
oldParentId: assetStub.primaryImage.id,
|
oldParentId: assetStub.primaryImage.id,
|
||||||
newParentId: 'new',
|
newParentId: 'new',
|
||||||
|
@ -1051,7 +1048,8 @@ describe(AssetService.name, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('update stackParentId of old parents children to new parent', async () => {
|
it('update stackParentId of old parents children to new parent', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set([assetStub.primaryImage.id]));
|
||||||
|
accessMock.asset.checkOwnerAccess.mockResolvedValueOnce(new Set(['new']));
|
||||||
when(assetMock.getById)
|
when(assetMock.getById)
|
||||||
.calledWith(assetStub.primaryImage.id)
|
.calledWith(assetStub.primaryImage.id)
|
||||||
.mockResolvedValue(assetStub.primaryImage as AssetEntity);
|
.mockResolvedValue(assetStub.primaryImage as AssetEntity);
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { AccessCore, Permission } from '../access';
|
||||||
import { AuthUserDto } from '../auth';
|
import { AuthUserDto } from '../auth';
|
||||||
import { mimeTypes } from '../domain.constant';
|
import { mimeTypes } from '../domain.constant';
|
||||||
import { HumanReadableSize, usePagination } from '../domain.util';
|
import { HumanReadableSize, usePagination } from '../domain.util';
|
||||||
import { IAssetDeletionJob, JOBS_ASSET_PAGINATION_SIZE, JobName } from '../job';
|
import { IAssetDeletionJob, ISidecarWriteJob, JOBS_ASSET_PAGINATION_SIZE, JobName } from '../job';
|
||||||
import {
|
import {
|
||||||
CommunicationEvent,
|
CommunicationEvent,
|
||||||
IAccessRepository,
|
IAccessRepository,
|
||||||
|
@ -393,10 +393,8 @@ export class AssetService {
|
||||||
async update(authUser: AuthUserDto, id: string, dto: UpdateAssetDto): Promise<AssetResponseDto> {
|
async update(authUser: AuthUserDto, id: string, dto: UpdateAssetDto): Promise<AssetResponseDto> {
|
||||||
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, id);
|
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, id);
|
||||||
|
|
||||||
const { description, ...rest } = dto;
|
const { description, dateTimeOriginal, latitude, longitude, ...rest } = dto;
|
||||||
if (description !== undefined) {
|
await this.updateMetadata({ id, description, dateTimeOriginal, latitude, longitude });
|
||||||
await this.assetRepository.upsertExif({ assetId: id, description });
|
|
||||||
}
|
|
||||||
|
|
||||||
const asset = await this.assetRepository.save({ id, ...rest });
|
const asset = await this.assetRepository.save({ id, ...rest });
|
||||||
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSET, data: { ids: [id] } });
|
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSET, data: { ids: [id] } });
|
||||||
|
@ -404,7 +402,7 @@ export class AssetService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateAll(authUser: AuthUserDto, dto: AssetBulkUpdateDto): Promise<void> {
|
async updateAll(authUser: AuthUserDto, dto: AssetBulkUpdateDto): Promise<void> {
|
||||||
const { ids, removeParent, ...options } = dto;
|
const { ids, removeParent, dateTimeOriginal, latitude, longitude, ...options } = dto;
|
||||||
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, ids);
|
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, ids);
|
||||||
|
|
||||||
if (removeParent) {
|
if (removeParent) {
|
||||||
|
@ -424,6 +422,10 @@ export class AssetService {
|
||||||
await this.assetRepository.updateAll([options.stackParentId], { stackParentId: null });
|
await this.assetRepository.updateAll([options.stackParentId], { stackParentId: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const id of ids) {
|
||||||
|
await this.updateMetadata({ id, dateTimeOriginal, latitude, longitude });
|
||||||
|
}
|
||||||
|
|
||||||
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSET, data: { ids } });
|
await this.jobRepository.queue({ name: JobName.SEARCH_INDEX_ASSET, data: { ids } });
|
||||||
await this.assetRepository.updateAll(ids, options);
|
await this.assetRepository.updateAll(ids, options);
|
||||||
this.communicationRepository.send(CommunicationEvent.ASSET_UPDATE, authUser.id, ids);
|
this.communicationRepository.send(CommunicationEvent.ASSET_UPDATE, authUser.id, ids);
|
||||||
|
@ -587,4 +589,13 @@ export class AssetService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async updateMetadata(dto: ISidecarWriteJob) {
|
||||||
|
const { id, description, dateTimeOriginal, latitude, longitude } = dto;
|
||||||
|
const writes = _.omitBy({ description, dateTimeOriginal, latitude, longitude }, _.isUndefined);
|
||||||
|
if (Object.keys(writes).length > 0) {
|
||||||
|
await this.assetRepository.upsertExif({ assetId: id, ...writes });
|
||||||
|
await this.jobRepository.queue({ name: JobName.SIDECAR_WRITE, data: { id, ...writes } });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,19 @@
|
||||||
import { AssetType } from '@app/infra/entities';
|
import { AssetType } from '@app/infra/entities';
|
||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import { IsBoolean, IsEnum, IsInt, IsPositive, IsString, Min } from 'class-validator';
|
import {
|
||||||
|
IsBoolean,
|
||||||
|
IsDateString,
|
||||||
|
IsEnum,
|
||||||
|
IsInt,
|
||||||
|
IsLatitude,
|
||||||
|
IsLongitude,
|
||||||
|
IsNotEmpty,
|
||||||
|
IsPositive,
|
||||||
|
IsString,
|
||||||
|
Min,
|
||||||
|
ValidateIf,
|
||||||
|
} from 'class-validator';
|
||||||
import { Optional, QueryBoolean, QueryDate, ValidateUUID } from '../../domain.util';
|
import { Optional, QueryBoolean, QueryDate, ValidateUUID } from '../../domain.util';
|
||||||
import { BulkIdsDto } from '../response-dto';
|
import { BulkIdsDto } from '../response-dto';
|
||||||
|
|
||||||
|
@ -10,6 +22,10 @@ export enum AssetOrder {
|
||||||
DESC = 'desc',
|
DESC = 'desc',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasGPS = (o: { latitude: undefined; longitude: undefined }) =>
|
||||||
|
o.latitude !== undefined || o.longitude !== undefined;
|
||||||
|
const ValidateGPS = () => ValidateIf(hasGPS);
|
||||||
|
|
||||||
export class AssetSearchDto {
|
export class AssetSearchDto {
|
||||||
@ValidateUUID({ optional: true })
|
@ValidateUUID({ optional: true })
|
||||||
id?: string;
|
id?: string;
|
||||||
|
@ -172,6 +188,20 @@ export class AssetBulkUpdateDto extends BulkIdsDto {
|
||||||
@Optional()
|
@Optional()
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
removeParent?: boolean;
|
removeParent?: boolean;
|
||||||
|
|
||||||
|
@Optional()
|
||||||
|
@IsDateString()
|
||||||
|
dateTimeOriginal?: string;
|
||||||
|
|
||||||
|
@ValidateGPS()
|
||||||
|
@IsLatitude()
|
||||||
|
@IsNotEmpty()
|
||||||
|
latitude?: number;
|
||||||
|
|
||||||
|
@ValidateGPS()
|
||||||
|
@IsLongitude()
|
||||||
|
@IsNotEmpty()
|
||||||
|
longitude?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UpdateAssetDto {
|
export class UpdateAssetDto {
|
||||||
|
@ -186,6 +216,20 @@ export class UpdateAssetDto {
|
||||||
@Optional()
|
@Optional()
|
||||||
@IsString()
|
@IsString()
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|
||||||
|
@Optional()
|
||||||
|
@IsDateString()
|
||||||
|
dateTimeOriginal?: string;
|
||||||
|
|
||||||
|
@ValidateGPS()
|
||||||
|
@IsLatitude()
|
||||||
|
@IsNotEmpty()
|
||||||
|
latitude?: number;
|
||||||
|
|
||||||
|
@ValidateGPS()
|
||||||
|
@IsLongitude()
|
||||||
|
@IsNotEmpty()
|
||||||
|
longitude?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RandomAssetsDto {
|
export class RandomAssetsDto {
|
||||||
|
|
|
@ -98,7 +98,7 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As
|
||||||
tags: entity.tags?.map(mapTag),
|
tags: entity.tags?.map(mapTag),
|
||||||
people: entity.faces
|
people: entity.faces
|
||||||
?.map(mapFace)
|
?.map(mapFace)
|
||||||
.filter((person): person is PersonResponseDto => person !== null && !person.isHidden)
|
.filter((person): person is PersonResponseDto => person !== null)
|
||||||
.reduce((people, person) => {
|
.reduce((people, person) => {
|
||||||
const existingPerson = people.find((p) => p.id === person.id);
|
const existingPerson = people.find((p) => p.id === person.id);
|
||||||
if (!existingPerson) {
|
if (!existingPerson) {
|
||||||
|
|
|
@ -155,18 +155,35 @@ export function Optional({ nullable, ...validationOptions }: OptionalOptions = {
|
||||||
// They should be replaced with native Set operations, when they are added to the language.
|
// They should be replaced with native Set operations, when they are added to the language.
|
||||||
// Proposal reference: https://github.com/tc39/proposal-set-methods
|
// Proposal reference: https://github.com/tc39/proposal-set-methods
|
||||||
|
|
||||||
export const setUnion = <T>(setA: Set<T>, setB: Set<T>): Set<T> => {
|
export const setUnion = <T>(...sets: Set<T>[]): Set<T> => {
|
||||||
const union = new Set(setA);
|
const union = new Set(sets[0]);
|
||||||
for (const elem of setB) {
|
for (const set of sets.slice(1)) {
|
||||||
union.add(elem);
|
for (const elem of set) {
|
||||||
|
union.add(elem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return union;
|
return union;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const setDifference = <T>(setA: Set<T>, setB: Set<T>): Set<T> => {
|
export const setDifference = <T>(setA: Set<T>, ...sets: Set<T>[]): Set<T> => {
|
||||||
const difference = new Set(setA);
|
const difference = new Set(setA);
|
||||||
for (const elem of setB) {
|
for (const set of sets) {
|
||||||
difference.delete(elem);
|
for (const elem of set) {
|
||||||
|
difference.delete(elem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return difference;
|
return difference;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const setIsSuperset = <T>(set: Set<T>, subset: Set<T>): boolean => {
|
||||||
|
for (const elem of subset) {
|
||||||
|
if (!set.has(elem)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setIsEqual = <T>(setA: Set<T>, setB: Set<T>): boolean => {
|
||||||
|
return setA.size === setB.size && setIsSuperset(setA, setB);
|
||||||
|
};
|
||||||
|
|
|
@ -96,6 +96,7 @@ export enum JobName {
|
||||||
QUEUE_SIDECAR = 'queue-sidecar',
|
QUEUE_SIDECAR = 'queue-sidecar',
|
||||||
SIDECAR_DISCOVERY = 'sidecar-discovery',
|
SIDECAR_DISCOVERY = 'sidecar-discovery',
|
||||||
SIDECAR_SYNC = 'sidecar-sync',
|
SIDECAR_SYNC = 'sidecar-sync',
|
||||||
|
SIDECAR_WRITE = 'sidecar-write',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const JOBS_ASSET_PAGINATION_SIZE = 1000;
|
export const JOBS_ASSET_PAGINATION_SIZE = 1000;
|
||||||
|
@ -168,6 +169,7 @@ export const JOBS_TO_QUEUE: Record<JobName, QueueName> = {
|
||||||
[JobName.QUEUE_SIDECAR]: QueueName.SIDECAR,
|
[JobName.QUEUE_SIDECAR]: QueueName.SIDECAR,
|
||||||
[JobName.SIDECAR_DISCOVERY]: QueueName.SIDECAR,
|
[JobName.SIDECAR_DISCOVERY]: QueueName.SIDECAR,
|
||||||
[JobName.SIDECAR_SYNC]: QueueName.SIDECAR,
|
[JobName.SIDECAR_SYNC]: QueueName.SIDECAR,
|
||||||
|
[JobName.SIDECAR_WRITE]: QueueName.SIDECAR,
|
||||||
|
|
||||||
// Library management
|
// Library management
|
||||||
[JobName.LIBRARY_SCAN_ASSET]: QueueName.LIBRARY,
|
[JobName.LIBRARY_SCAN_ASSET]: QueueName.LIBRARY,
|
||||||
|
|
|
@ -9,7 +9,7 @@ export interface IAssetFaceJob extends IBaseJob {
|
||||||
|
|
||||||
export interface IEntityJob extends IBaseJob {
|
export interface IEntityJob extends IBaseJob {
|
||||||
id: string;
|
id: string;
|
||||||
source?: 'upload';
|
source?: 'upload' | 'sidecar-write';
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAssetDeletionJob extends IEntityJob {
|
export interface IAssetDeletionJob extends IEntityJob {
|
||||||
|
@ -33,3 +33,10 @@ export interface IBulkEntityJob extends IBaseJob {
|
||||||
export interface IDeleteFilesJob extends IBaseJob {
|
export interface IDeleteFilesJob extends IBaseJob {
|
||||||
files: Array<string | null | undefined>;
|
files: Array<string | null | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ISidecarWriteJob extends IEntityJob {
|
||||||
|
description?: string;
|
||||||
|
dateTimeOriginal?: string;
|
||||||
|
latitude?: number;
|
||||||
|
longitude?: number;
|
||||||
|
}
|
||||||
|
|
|
@ -165,7 +165,19 @@ export class JobService {
|
||||||
await this.jobRepository.queue({ name: JobName.METADATA_EXTRACTION, data: item.data });
|
await this.jobRepository.queue({ name: JobName.METADATA_EXTRACTION, data: item.data });
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case JobName.SIDECAR_WRITE:
|
||||||
|
await this.jobRepository.queue({
|
||||||
|
name: JobName.METADATA_EXTRACTION,
|
||||||
|
data: { id: item.data.id, source: 'sidecar-write' },
|
||||||
|
});
|
||||||
|
|
||||||
case JobName.METADATA_EXTRACTION:
|
case JobName.METADATA_EXTRACTION:
|
||||||
|
if (item.data.source === 'sidecar-write') {
|
||||||
|
const [asset] = await this.assetRepository.getByIds([item.data.id]);
|
||||||
|
if (asset) {
|
||||||
|
this.communicationRepository.send(CommunicationEvent.ASSET_UPDATE, asset.ownerId, mapAsset(asset));
|
||||||
|
}
|
||||||
|
}
|
||||||
await this.jobRepository.queue({ name: JobName.LINK_LIVE_PHOTOS, data: item.data });
|
await this.jobRepository.queue({ name: JobName.LINK_LIVE_PHOTOS, data: item.data });
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,8 @@ describe(LibraryService.name, () => {
|
||||||
ctime: new Date('2023-01-01'),
|
ctime: new Date('2023-01-01'),
|
||||||
} as Stats);
|
} as Stats);
|
||||||
|
|
||||||
accessMock.library.checkOwnerAccess.mockResolvedValue(new Set([authStub.admin.id]));
|
// Always validate owner access for library.
|
||||||
|
accessMock.library.checkOwnerAccess.mockImplementation(async (_, libraryIds) => libraryIds);
|
||||||
|
|
||||||
sut = new LibraryService(
|
sut = new LibraryService(
|
||||||
accessMock,
|
accessMock,
|
||||||
|
|
|
@ -218,11 +218,11 @@ describe(MetadataService.name, () => {
|
||||||
const originalDate = new Date('2023-11-21T16:13:17.517Z');
|
const originalDate = new Date('2023-11-21T16:13:17.517Z');
|
||||||
const sidecarDate = new Date('2022-01-01T00:00:00.000Z');
|
const sidecarDate = new Date('2022-01-01T00:00:00.000Z');
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.sidecar]);
|
assetMock.getByIds.mockResolvedValue([assetStub.sidecar]);
|
||||||
when(metadataMock.getExifTags)
|
when(metadataMock.readTags)
|
||||||
.calledWith(assetStub.sidecar.originalPath)
|
.calledWith(assetStub.sidecar.originalPath)
|
||||||
// higher priority tag
|
// higher priority tag
|
||||||
.mockResolvedValue({ CreationDate: originalDate.toISOString() });
|
.mockResolvedValue({ CreationDate: originalDate.toISOString() });
|
||||||
when(metadataMock.getExifTags)
|
when(metadataMock.readTags)
|
||||||
.calledWith(assetStub.sidecar.sidecarPath as string)
|
.calledWith(assetStub.sidecar.sidecarPath as string)
|
||||||
// lower priority tag, but in sidecar
|
// lower priority tag, but in sidecar
|
||||||
.mockResolvedValue({ CreateDate: sidecarDate.toISOString() });
|
.mockResolvedValue({ CreateDate: sidecarDate.toISOString() });
|
||||||
|
@ -240,7 +240,7 @@ describe(MetadataService.name, () => {
|
||||||
|
|
||||||
it('should handle lists of numbers', async () => {
|
it('should handle lists of numbers', async () => {
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||||
metadataMock.getExifTags.mockResolvedValue({ ISO: [160] as any });
|
metadataMock.readTags.mockResolvedValue({ ISO: [160] as any });
|
||||||
|
|
||||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||||
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id]);
|
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id]);
|
||||||
|
@ -257,7 +257,7 @@ describe(MetadataService.name, () => {
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.withLocation]);
|
assetMock.getByIds.mockResolvedValue([assetStub.withLocation]);
|
||||||
configMock.load.mockResolvedValue([{ key: SystemConfigKey.REVERSE_GEOCODING_ENABLED, value: true }]);
|
configMock.load.mockResolvedValue([{ key: SystemConfigKey.REVERSE_GEOCODING_ENABLED, value: true }]);
|
||||||
metadataMock.reverseGeocode.mockResolvedValue({ city: 'City', state: 'State', country: 'Country' });
|
metadataMock.reverseGeocode.mockResolvedValue({ city: 'City', state: 'State', country: 'Country' });
|
||||||
metadataMock.getExifTags.mockResolvedValue({
|
metadataMock.readTags.mockResolvedValue({
|
||||||
GPSLatitude: assetStub.withLocation.exifInfo!.latitude!,
|
GPSLatitude: assetStub.withLocation.exifInfo!.latitude!,
|
||||||
GPSLongitude: assetStub.withLocation.exifInfo!.longitude!,
|
GPSLongitude: assetStub.withLocation.exifInfo!.longitude!,
|
||||||
});
|
});
|
||||||
|
@ -289,7 +289,7 @@ describe(MetadataService.name, () => {
|
||||||
|
|
||||||
it('should apply motion photos', async () => {
|
it('should apply motion photos', async () => {
|
||||||
assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoStillAsset, livePhotoVideoId: null }]);
|
assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoStillAsset, livePhotoVideoId: null }]);
|
||||||
metadataMock.getExifTags.mockResolvedValue({
|
metadataMock.readTags.mockResolvedValue({
|
||||||
Directory: 'foo/bar/',
|
Directory: 'foo/bar/',
|
||||||
MotionPhoto: 1,
|
MotionPhoto: 1,
|
||||||
MicroVideo: 1,
|
MicroVideo: 1,
|
||||||
|
@ -310,7 +310,7 @@ describe(MetadataService.name, () => {
|
||||||
|
|
||||||
it('should create new motion asset if not found and link it with the photo', async () => {
|
it('should create new motion asset if not found and link it with the photo', async () => {
|
||||||
assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoStillAsset, livePhotoVideoId: null }]);
|
assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoStillAsset, livePhotoVideoId: null }]);
|
||||||
metadataMock.getExifTags.mockResolvedValue({
|
metadataMock.readTags.mockResolvedValue({
|
||||||
Directory: 'foo/bar/',
|
Directory: 'foo/bar/',
|
||||||
MotionPhoto: 1,
|
MotionPhoto: 1,
|
||||||
MicroVideo: 1,
|
MicroVideo: 1,
|
||||||
|
@ -367,7 +367,7 @@ describe(MetadataService.name, () => {
|
||||||
tz: '+02:00',
|
tz: '+02:00',
|
||||||
};
|
};
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||||
metadataMock.getExifTags.mockResolvedValue(tags);
|
metadataMock.readTags.mockResolvedValue(tags);
|
||||||
|
|
||||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||||
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id]);
|
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id]);
|
||||||
|
@ -406,7 +406,7 @@ describe(MetadataService.name, () => {
|
||||||
|
|
||||||
it('should handle duration', async () => {
|
it('should handle duration', async () => {
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||||
metadataMock.getExifTags.mockResolvedValue({ Duration: 6.21 });
|
metadataMock.readTags.mockResolvedValue({ Duration: 6.21 });
|
||||||
|
|
||||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||||
|
|
||||||
|
@ -422,7 +422,7 @@ describe(MetadataService.name, () => {
|
||||||
|
|
||||||
it('should handle duration as an object without Scale', async () => {
|
it('should handle duration as an object without Scale', async () => {
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||||
metadataMock.getExifTags.mockResolvedValue({ Duration: { Value: 6.2 } });
|
metadataMock.readTags.mockResolvedValue({ Duration: { Value: 6.2 } });
|
||||||
|
|
||||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||||
|
|
||||||
|
@ -438,7 +438,7 @@ describe(MetadataService.name, () => {
|
||||||
|
|
||||||
it('should handle duration with scale', async () => {
|
it('should handle duration with scale', async () => {
|
||||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||||
metadataMock.getExifTags.mockResolvedValue({ Duration: { Scale: 1.11111111111111e-5, Value: 558720 } });
|
metadataMock.readTags.mockResolvedValue({ Duration: { Scale: 1.11111111111111e-5, Value: 558720 } });
|
||||||
|
|
||||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||||
|
|
||||||
|
@ -531,4 +531,41 @@ describe(MetadataService.name, () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('handleSidecarWrite', () => {
|
||||||
|
it('should skip assets that do not exist anymore', async () => {
|
||||||
|
assetMock.getByIds.mockResolvedValue([]);
|
||||||
|
await expect(sut.handleSidecarWrite({ id: 'asset-123' })).resolves.toBe(false);
|
||||||
|
expect(metadataMock.writeTags).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should skip jobs with not metadata', async () => {
|
||||||
|
assetMock.getByIds.mockResolvedValue([assetStub.sidecar]);
|
||||||
|
await expect(sut.handleSidecarWrite({ id: assetStub.sidecar.id })).resolves.toBe(true);
|
||||||
|
expect(metadataMock.writeTags).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should write tags', async () => {
|
||||||
|
const description = 'this is a description';
|
||||||
|
const gps = 12;
|
||||||
|
const date = '2023-11-22T04:56:12.196Z';
|
||||||
|
|
||||||
|
assetMock.getByIds.mockResolvedValue([assetStub.sidecar]);
|
||||||
|
await expect(
|
||||||
|
sut.handleSidecarWrite({
|
||||||
|
id: assetStub.sidecar.id,
|
||||||
|
description,
|
||||||
|
latitude: gps,
|
||||||
|
longitude: gps,
|
||||||
|
dateTimeOriginal: date,
|
||||||
|
}),
|
||||||
|
).resolves.toBe(true);
|
||||||
|
expect(metadataMock.writeTags).toHaveBeenCalledWith(assetStub.sidecar.sidecarPath, {
|
||||||
|
ImageDescription: description,
|
||||||
|
CreationDate: date,
|
||||||
|
GPSLatitude: gps,
|
||||||
|
GPSLongitude: gps,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,10 +3,11 @@ import { Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
import { ExifDateTime, Tags } from 'exiftool-vendored';
|
import { ExifDateTime, Tags } from 'exiftool-vendored';
|
||||||
import { firstDateTime } from 'exiftool-vendored/dist/FirstDateTime';
|
import { firstDateTime } from 'exiftool-vendored/dist/FirstDateTime';
|
||||||
import { constants } from 'fs/promises';
|
import { constants } from 'fs/promises';
|
||||||
|
import _ from 'lodash';
|
||||||
import { Duration } from 'luxon';
|
import { Duration } from 'luxon';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { usePagination } from '../domain.util';
|
import { usePagination } from '../domain.util';
|
||||||
import { IBaseJob, IEntityJob, JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from '../job';
|
import { IBaseJob, IEntityJob, ISidecarWriteJob, JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from '../job';
|
||||||
import {
|
import {
|
||||||
ExifDuration,
|
ExifDuration,
|
||||||
IAlbumRepository,
|
IAlbumRepository,
|
||||||
|
@ -79,7 +80,6 @@ export class MetadataService {
|
||||||
private logger = new Logger(MetadataService.name);
|
private logger = new Logger(MetadataService.name);
|
||||||
private storageCore: StorageCore;
|
private storageCore: StorageCore;
|
||||||
private configCore: SystemConfigCore;
|
private configCore: SystemConfigCore;
|
||||||
private oldCities?: string;
|
|
||||||
private subscription: Subscription | null = null;
|
private subscription: Subscription | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -244,6 +244,37 @@ export class MetadataService {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleSidecarWrite(job: ISidecarWriteJob) {
|
||||||
|
const { id, description, dateTimeOriginal, latitude, longitude } = job;
|
||||||
|
const [asset] = await this.assetRepository.getByIds([id]);
|
||||||
|
if (!asset) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sidecarPath = asset.sidecarPath || `${asset.originalPath}.xmp`;
|
||||||
|
const exif = _.omitBy<Tags>(
|
||||||
|
{
|
||||||
|
ImageDescription: description,
|
||||||
|
CreationDate: dateTimeOriginal,
|
||||||
|
GPSLatitude: latitude,
|
||||||
|
GPSLongitude: longitude,
|
||||||
|
},
|
||||||
|
_.isUndefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Object.keys(exif).length === 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.repository.writeTags(sidecarPath, exif);
|
||||||
|
|
||||||
|
if (!asset.sidecarPath) {
|
||||||
|
await this.assetRepository.save({ id, sidecarPath });
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private async applyReverseGeocoding(asset: AssetEntity, exifData: ExifEntityWithoutGeocodeAndTypeOrm) {
|
private async applyReverseGeocoding(asset: AssetEntity, exifData: ExifEntityWithoutGeocodeAndTypeOrm) {
|
||||||
const { latitude, longitude } = exifData;
|
const { latitude, longitude } = exifData;
|
||||||
if (!(await this.configCore.hasFeature(FeatureFlag.REVERSE_GEOCODING)) || !longitude || !latitude) {
|
if (!(await this.configCore.hasFeature(FeatureFlag.REVERSE_GEOCODING)) || !longitude || !latitude) {
|
||||||
|
@ -346,8 +377,8 @@ export class MetadataService {
|
||||||
asset: AssetEntity,
|
asset: AssetEntity,
|
||||||
): Promise<{ exifData: ExifEntityWithoutGeocodeAndTypeOrm; tags: ImmichTags }> {
|
): Promise<{ exifData: ExifEntityWithoutGeocodeAndTypeOrm; tags: ImmichTags }> {
|
||||||
const stats = await this.storageRepository.stat(asset.originalPath);
|
const stats = await this.storageRepository.stat(asset.originalPath);
|
||||||
const mediaTags = await this.repository.getExifTags(asset.originalPath);
|
const mediaTags = await this.repository.readTags(asset.originalPath);
|
||||||
const sidecarTags = asset.sidecarPath ? await this.repository.getExifTags(asset.sidecarPath) : null;
|
const sidecarTags = asset.sidecarPath ? await this.repository.readTags(asset.sidecarPath) : null;
|
||||||
|
|
||||||
// ensure date from sidecar is used if present
|
// ensure date from sidecar is used if present
|
||||||
const hasDateOverride = !!this.getDateTimeOriginal(sidecarTags);
|
const hasDateOverride = !!this.getDateTimeOriginal(sidecarTags);
|
||||||
|
|
|
@ -331,7 +331,7 @@ describe(PersonService.name, () => {
|
||||||
personMock.getById.mockResolvedValue(personStub.withName);
|
personMock.getById.mockResolvedValue(personStub.withName);
|
||||||
personMock.update.mockResolvedValue(personStub.withName);
|
personMock.update.mockResolvedValue(personStub.withName);
|
||||||
personMock.getFacesByIds.mockResolvedValue([faceStub.face1]);
|
personMock.getFacesByIds.mockResolvedValue([faceStub.face1]);
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id]));
|
||||||
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
|
accessMock.person.checkOwnerAccess.mockResolvedValue(new Set(['person-1']));
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
|
|
|
@ -6,11 +6,12 @@ export interface IAccessRepository {
|
||||||
hasAlbumOwnerAccess(userId: string, activityId: string): Promise<boolean>;
|
hasAlbumOwnerAccess(userId: string, activityId: string): Promise<boolean>;
|
||||||
hasCreateAccess(userId: string, albumId: string): Promise<boolean>;
|
hasCreateAccess(userId: string, albumId: string): Promise<boolean>;
|
||||||
};
|
};
|
||||||
|
|
||||||
asset: {
|
asset: {
|
||||||
hasOwnerAccess(userId: string, assetId: string): Promise<boolean>;
|
checkOwnerAccess(userId: string, assetIds: Set<string>): Promise<Set<string>>;
|
||||||
hasAlbumAccess(userId: string, assetId: string): Promise<boolean>;
|
checkAlbumAccess(userId: string, assetIds: Set<string>): Promise<Set<string>>;
|
||||||
hasPartnerAccess(userId: string, assetId: string): Promise<boolean>;
|
checkPartnerAccess(userId: string, assetIds: Set<string>): Promise<Set<string>>;
|
||||||
hasSharedLinkAccess(sharedLinkId: string, assetId: string): Promise<boolean>;
|
checkSharedLinkAccess(sharedLinkId: string, assetIds: Set<string>): Promise<Set<string>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
authDevice: {
|
authDevice: {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
IEntityJob,
|
IEntityJob,
|
||||||
ILibraryFileJob,
|
ILibraryFileJob,
|
||||||
ILibraryRefreshJob,
|
ILibraryRefreshJob,
|
||||||
|
ISidecarWriteJob,
|
||||||
} from '../job/job.interface';
|
} from '../job/job.interface';
|
||||||
|
|
||||||
export interface JobCounts {
|
export interface JobCounts {
|
||||||
|
@ -54,11 +55,11 @@ export type JobItem =
|
||||||
| { name: JobName.QUEUE_METADATA_EXTRACTION; data: IBaseJob }
|
| { name: JobName.QUEUE_METADATA_EXTRACTION; data: IBaseJob }
|
||||||
| { name: JobName.METADATA_EXTRACTION; data: IEntityJob }
|
| { name: JobName.METADATA_EXTRACTION; data: IEntityJob }
|
||||||
| { name: JobName.LINK_LIVE_PHOTOS; data: IEntityJob }
|
| { name: JobName.LINK_LIVE_PHOTOS; data: IEntityJob }
|
||||||
|
|
||||||
// Sidecar Scanning
|
// Sidecar Scanning
|
||||||
| { name: JobName.QUEUE_SIDECAR; data: IBaseJob }
|
| { name: JobName.QUEUE_SIDECAR; data: IBaseJob }
|
||||||
| { name: JobName.SIDECAR_DISCOVERY; data: IEntityJob }
|
| { name: JobName.SIDECAR_DISCOVERY; data: IEntityJob }
|
||||||
| { name: JobName.SIDECAR_SYNC; data: IEntityJob }
|
| { name: JobName.SIDECAR_SYNC; data: IEntityJob }
|
||||||
|
| { name: JobName.SIDECAR_WRITE; data: ISidecarWriteJob }
|
||||||
|
|
||||||
// Object Tagging
|
// Object Tagging
|
||||||
| { name: JobName.QUEUE_OBJECT_TAGGING; data: IBaseJob }
|
| { name: JobName.QUEUE_OBJECT_TAGGING; data: IBaseJob }
|
||||||
|
|
|
@ -33,5 +33,6 @@ export interface IMetadataRepository {
|
||||||
init(): Promise<void>;
|
init(): Promise<void>;
|
||||||
teardown(): Promise<void>;
|
teardown(): Promise<void>;
|
||||||
reverseGeocode(point: GeoPoint): Promise<ReverseGeocodeResult | null>;
|
reverseGeocode(point: GeoPoint): Promise<ReverseGeocodeResult | null>;
|
||||||
getExifTags(path: string): Promise<ImmichTags | null>;
|
readTags(path: string): Promise<ImmichTags | null>;
|
||||||
|
writeTags(path: string, tags: Partial<Tags>): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import {
|
||||||
sharedLinkResponseStub,
|
sharedLinkResponseStub,
|
||||||
sharedLinkStub,
|
sharedLinkStub,
|
||||||
} from '@test';
|
} from '@test';
|
||||||
import { when } from 'jest-when';
|
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { AssetIdErrorReason } from '../asset';
|
import { AssetIdErrorReason } from '../asset';
|
||||||
import { ICryptoRepository, ISharedLinkRepository } from '../repositories';
|
import { ICryptoRepository, ISharedLinkRepository } from '../repositories';
|
||||||
|
@ -109,7 +108,6 @@ describe(SharedLinkService.name, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should require asset ownership to make an individual shared link', async () => {
|
it('should require asset ownership to make an individual shared link', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.create(authStub.admin, { type: SharedLinkType.INDIVIDUAL, assetIds: ['asset-1'] }),
|
sut.create(authStub.admin, { type: SharedLinkType.INDIVIDUAL, assetIds: ['asset-1'] }),
|
||||||
).rejects.toBeInstanceOf(BadRequestException);
|
).rejects.toBeInstanceOf(BadRequestException);
|
||||||
|
@ -140,7 +138,7 @@ describe(SharedLinkService.name, () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create an individual shared link', async () => {
|
it('should create an individual shared link', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id]));
|
||||||
shareMock.create.mockResolvedValue(sharedLinkStub.individual);
|
shareMock.create.mockResolvedValue(sharedLinkStub.individual);
|
||||||
|
|
||||||
await sut.create(authStub.admin, {
|
await sut.create(authStub.admin, {
|
||||||
|
@ -151,7 +149,7 @@ describe(SharedLinkService.name, () => {
|
||||||
allowUpload: true,
|
allowUpload: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(accessMock.asset.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, assetStub.image.id);
|
expect(accessMock.asset.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set([assetStub.image.id]));
|
||||||
expect(shareMock.create).toHaveBeenCalledWith({
|
expect(shareMock.create).toHaveBeenCalledWith({
|
||||||
type: SharedLinkType.INDIVIDUAL,
|
type: SharedLinkType.INDIVIDUAL,
|
||||||
userId: authStub.admin.id,
|
userId: authStub.admin.id,
|
||||||
|
@ -215,9 +213,7 @@ describe(SharedLinkService.name, () => {
|
||||||
it('should add assets to a shared link', async () => {
|
it('should add assets to a shared link', async () => {
|
||||||
shareMock.get.mockResolvedValue(_.cloneDeep(sharedLinkStub.individual));
|
shareMock.get.mockResolvedValue(_.cloneDeep(sharedLinkStub.individual));
|
||||||
shareMock.create.mockResolvedValue(sharedLinkStub.individual);
|
shareMock.create.mockResolvedValue(sharedLinkStub.individual);
|
||||||
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-3']));
|
||||||
when(accessMock.asset.hasOwnerAccess).calledWith(authStub.admin.id, 'asset-2').mockResolvedValue(false);
|
|
||||||
when(accessMock.asset.hasOwnerAccess).calledWith(authStub.admin.id, 'asset-3').mockResolvedValue(true);
|
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.addAssets(authStub.admin, 'link-1', { assetIds: [assetStub.image.id, 'asset-2', 'asset-3'] }),
|
sut.addAssets(authStub.admin, 'link-1', { assetIds: [assetStub.image.id, 'asset-2', 'asset-3'] }),
|
||||||
|
@ -227,7 +223,7 @@ describe(SharedLinkService.name, () => {
|
||||||
{ assetId: 'asset-3', success: true },
|
{ assetId: 'asset-3', success: true },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
expect(accessMock.asset.hasOwnerAccess).toHaveBeenCalledTimes(2);
|
expect(accessMock.asset.checkOwnerAccess).toHaveBeenCalledTimes(1);
|
||||||
expect(shareMock.update).toHaveBeenCalledWith({
|
expect(shareMock.update).toHaveBeenCalledWith({
|
||||||
...sharedLinkStub.individual,
|
...sharedLinkStub.individual,
|
||||||
assets: [assetStub.image, { id: 'asset-3' }],
|
assets: [assetStub.image, { id: 'asset-3' }],
|
||||||
|
|
|
@ -232,54 +232,49 @@ describe('AssetService', () => {
|
||||||
|
|
||||||
describe('getAssetById', () => {
|
describe('getAssetById', () => {
|
||||||
it('should allow owner access', async () => {
|
it('should allow owner access', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set([assetStub.image.id]));
|
||||||
assetRepositoryMock.getById.mockResolvedValue(assetStub.image);
|
assetRepositoryMock.getById.mockResolvedValue(assetStub.image);
|
||||||
await sut.getAssetById(authStub.admin, assetStub.image.id);
|
await sut.getAssetById(authStub.admin, assetStub.image.id);
|
||||||
expect(accessMock.asset.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, assetStub.image.id);
|
expect(accessMock.asset.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set([assetStub.image.id]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow shared link access', async () => {
|
it('should allow shared link access', async () => {
|
||||||
accessMock.asset.hasSharedLinkAccess.mockResolvedValue(true);
|
accessMock.asset.checkSharedLinkAccess.mockResolvedValue(new Set([assetStub.image.id]));
|
||||||
assetRepositoryMock.getById.mockResolvedValue(assetStub.image);
|
assetRepositoryMock.getById.mockResolvedValue(assetStub.image);
|
||||||
await sut.getAssetById(authStub.adminSharedLink, assetStub.image.id);
|
await sut.getAssetById(authStub.adminSharedLink, assetStub.image.id);
|
||||||
expect(accessMock.asset.hasSharedLinkAccess).toHaveBeenCalledWith(
|
expect(accessMock.asset.checkSharedLinkAccess).toHaveBeenCalledWith(
|
||||||
authStub.adminSharedLink.sharedLinkId,
|
authStub.adminSharedLink.sharedLinkId,
|
||||||
assetStub.image.id,
|
new Set([assetStub.image.id]),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow partner sharing access', async () => {
|
it('should allow partner sharing access', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
accessMock.asset.checkPartnerAccess.mockResolvedValue(new Set([assetStub.image.id]));
|
||||||
accessMock.asset.hasPartnerAccess.mockResolvedValue(true);
|
|
||||||
assetRepositoryMock.getById.mockResolvedValue(assetStub.image);
|
assetRepositoryMock.getById.mockResolvedValue(assetStub.image);
|
||||||
await sut.getAssetById(authStub.admin, assetStub.image.id);
|
await sut.getAssetById(authStub.admin, assetStub.image.id);
|
||||||
expect(accessMock.asset.hasPartnerAccess).toHaveBeenCalledWith(authStub.admin.id, assetStub.image.id);
|
expect(accessMock.asset.checkPartnerAccess).toHaveBeenCalledWith(
|
||||||
|
authStub.admin.id,
|
||||||
|
new Set([assetStub.image.id]),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow shared album access', async () => {
|
it('should allow shared album access', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
accessMock.asset.checkAlbumAccess.mockResolvedValue(new Set([assetStub.image.id]));
|
||||||
accessMock.asset.hasPartnerAccess.mockResolvedValue(false);
|
|
||||||
accessMock.asset.hasAlbumAccess.mockResolvedValue(true);
|
|
||||||
assetRepositoryMock.getById.mockResolvedValue(assetStub.image);
|
assetRepositoryMock.getById.mockResolvedValue(assetStub.image);
|
||||||
await sut.getAssetById(authStub.admin, assetStub.image.id);
|
await sut.getAssetById(authStub.admin, assetStub.image.id);
|
||||||
expect(accessMock.asset.hasAlbumAccess).toHaveBeenCalledWith(authStub.admin.id, assetStub.image.id);
|
expect(accessMock.asset.checkAlbumAccess).toHaveBeenCalledWith(authStub.admin.id, new Set([assetStub.image.id]));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error for no access', async () => {
|
it('should throw an error for no access', async () => {
|
||||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
|
||||||
accessMock.asset.hasPartnerAccess.mockResolvedValue(false);
|
|
||||||
accessMock.asset.hasSharedLinkAccess.mockResolvedValue(false);
|
|
||||||
accessMock.asset.hasAlbumAccess.mockResolvedValue(false);
|
|
||||||
await expect(sut.getAssetById(authStub.admin, assetStub.image.id)).rejects.toBeInstanceOf(BadRequestException);
|
await expect(sut.getAssetById(authStub.admin, assetStub.image.id)).rejects.toBeInstanceOf(BadRequestException);
|
||||||
expect(assetRepositoryMock.getById).not.toHaveBeenCalled();
|
expect(assetRepositoryMock.getById).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error for an invalid shared link', async () => {
|
it('should throw an error for an invalid shared link', async () => {
|
||||||
accessMock.asset.hasSharedLinkAccess.mockResolvedValue(false);
|
|
||||||
await expect(sut.getAssetById(authStub.adminSharedLink, assetStub.image.id)).rejects.toBeInstanceOf(
|
await expect(sut.getAssetById(authStub.adminSharedLink, assetStub.image.id)).rejects.toBeInstanceOf(
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
);
|
);
|
||||||
expect(accessMock.asset.hasOwnerAccess).not.toHaveBeenCalled();
|
expect(accessMock.asset.checkOwnerAccess).not.toHaveBeenCalled();
|
||||||
expect(assetRepositoryMock.getById).not.toHaveBeenCalled();
|
expect(assetRepositoryMock.getById).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -36,10 +36,10 @@ import { databaseConfig } from './database.config';
|
||||||
import { databaseEntities } from './entities';
|
import { databaseEntities } from './entities';
|
||||||
import { bullConfig, bullQueues } from './infra.config';
|
import { bullConfig, bullQueues } from './infra.config';
|
||||||
import {
|
import {
|
||||||
APIKeyRepository,
|
|
||||||
AccessRepository,
|
AccessRepository,
|
||||||
ActivityRepository,
|
ActivityRepository,
|
||||||
AlbumRepository,
|
AlbumRepository,
|
||||||
|
ApiKeyRepository,
|
||||||
AssetRepository,
|
AssetRepository,
|
||||||
AuditRepository,
|
AuditRepository,
|
||||||
CommunicationRepository,
|
CommunicationRepository,
|
||||||
|
@ -74,7 +74,7 @@ const providers: Provider[] = [
|
||||||
{ provide: ICryptoRepository, useClass: CryptoRepository },
|
{ provide: ICryptoRepository, useClass: CryptoRepository },
|
||||||
{ provide: IJobRepository, useClass: JobRepository },
|
{ provide: IJobRepository, useClass: JobRepository },
|
||||||
{ provide: ILibraryRepository, useClass: LibraryRepository },
|
{ provide: ILibraryRepository, useClass: LibraryRepository },
|
||||||
{ provide: IKeyRepository, useClass: APIKeyRepository },
|
{ provide: IKeyRepository, useClass: ApiKeyRepository },
|
||||||
{ provide: IMachineLearningRepository, useClass: MachineLearningRepository },
|
{ provide: IMachineLearningRepository, useClass: MachineLearningRepository },
|
||||||
{ provide: IMetadataRepository, useClass: MetadataRepository },
|
{ provide: IMetadataRepository, useClass: MetadataRepository },
|
||||||
{ provide: IMoveRepository, useClass: MoveRepository },
|
{ provide: IMoveRepository, useClass: MoveRepository },
|
||||||
|
|
20
server/src/infra/infra.util.ts
Normal file
20
server/src/infra/infra.util.ts
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { SetMetadata } from '@nestjs/common';
|
||||||
|
|
||||||
|
export const GENERATE_SQL_KEY = 'generate-sql-key';
|
||||||
|
|
||||||
|
export interface GenerateSqlQueries {
|
||||||
|
name?: string;
|
||||||
|
params?: any[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Decorator to enable versioning/tracking of generated Sql */
|
||||||
|
export const GenerateSql = (...options: GenerateSqlQueries[]) => SetMetadata(GENERATE_SQL_KEY, options);
|
||||||
|
|
||||||
|
export const DummyValue = {
|
||||||
|
UUID: '00000000-0000-4000-a000-000000000000',
|
||||||
|
PAGINATION: { take: 10, skip: 0 },
|
||||||
|
EMAIL: 'user@immich.app',
|
||||||
|
STRING: 'abcdefghi',
|
||||||
|
BUFFER: Buffer.from('abcdefghi'),
|
||||||
|
DATE: new Date(),
|
||||||
|
};
|
|
@ -1,6 +1,6 @@
|
||||||
import { IAccessRepository } from '@app/domain';
|
import { IAccessRepository } from '@app/domain';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { In, Repository } from 'typeorm';
|
import { Brackets, In, Repository } from 'typeorm';
|
||||||
import {
|
import {
|
||||||
ActivityEntity,
|
ActivityEntity,
|
||||||
AlbumEntity,
|
AlbumEntity,
|
||||||
|
@ -112,107 +112,120 @@ export class AccessRepository implements IAccessRepository {
|
||||||
};
|
};
|
||||||
|
|
||||||
asset = {
|
asset = {
|
||||||
hasAlbumAccess: (userId: string, assetId: string): Promise<boolean> => {
|
checkAlbumAccess: async (userId: string, assetIds: Set<string>): Promise<Set<string>> => {
|
||||||
return this.albumRepository.exist({
|
if (assetIds.size === 0) {
|
||||||
where: [
|
return new Set();
|
||||||
{
|
}
|
||||||
|
|
||||||
|
return this.albumRepository
|
||||||
|
.createQueryBuilder('album')
|
||||||
|
.innerJoin('album.assets', 'asset')
|
||||||
|
.leftJoin('album.sharedUsers', 'sharedUsers')
|
||||||
|
.select('asset.id', 'assetId')
|
||||||
|
.addSelect('asset.livePhotoVideoId', 'livePhotoVideoId')
|
||||||
|
.where(
|
||||||
|
new Brackets((qb) => {
|
||||||
|
qb.where('album.ownerId = :userId', { userId }).orWhere('sharedUsers.id = :userId', { userId });
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.andWhere(
|
||||||
|
new Brackets((qb) => {
|
||||||
|
qb.where('asset.id IN (:...assetIds)', { assetIds: [...assetIds] })
|
||||||
|
// still part of a live photo is in an album
|
||||||
|
.orWhere('asset.livePhotoVideoId IN (:...assetIds)', { assetIds: [...assetIds] });
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.getRawMany()
|
||||||
|
.then((rows) => {
|
||||||
|
const allowedIds = new Set<string>();
|
||||||
|
for (const row of rows) {
|
||||||
|
if (row.assetId && assetIds.has(row.assetId)) {
|
||||||
|
allowedIds.add(row.assetId);
|
||||||
|
}
|
||||||
|
if (row.livePhotoVideoId && assetIds.has(row.livePhotoVideoId)) {
|
||||||
|
allowedIds.add(row.livePhotoVideoId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allowedIds;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
checkOwnerAccess: async (userId: string, assetIds: Set<string>): Promise<Set<string>> => {
|
||||||
|
if (assetIds.size === 0) {
|
||||||
|
return new Set();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.assetRepository
|
||||||
|
.find({
|
||||||
|
select: { id: true },
|
||||||
|
where: {
|
||||||
|
id: In([...assetIds]),
|
||||||
ownerId: userId,
|
ownerId: userId,
|
||||||
assets: {
|
|
||||||
id: assetId,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
withDeleted: true,
|
||||||
sharedUsers: {
|
})
|
||||||
id: userId,
|
.then((assets) => new Set(assets.map((asset) => asset.id)));
|
||||||
},
|
|
||||||
assets: {
|
|
||||||
id: assetId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// still part of a live photo is in an album
|
|
||||||
{
|
|
||||||
ownerId: userId,
|
|
||||||
assets: {
|
|
||||||
livePhotoVideoId: assetId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
sharedUsers: {
|
|
||||||
id: userId,
|
|
||||||
},
|
|
||||||
assets: {
|
|
||||||
livePhotoVideoId: assetId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
|
|
||||||
hasOwnerAccess: (userId: string, assetId: string): Promise<boolean> => {
|
checkPartnerAccess: async (userId: string, assetIds: Set<string>): Promise<Set<string>> => {
|
||||||
return this.assetRepository.exist({
|
if (assetIds.size === 0) {
|
||||||
where: {
|
return new Set();
|
||||||
id: assetId,
|
}
|
||||||
ownerId: userId,
|
|
||||||
},
|
return this.partnerRepository
|
||||||
withDeleted: true,
|
.createQueryBuilder('partner')
|
||||||
});
|
.innerJoin('partner.sharedBy', 'sharedBy')
|
||||||
|
.innerJoin('sharedBy.assets', 'asset')
|
||||||
|
.select('asset.id', 'assetId')
|
||||||
|
.where('partner.sharedWithId = :userId', { userId })
|
||||||
|
.andWhere('asset.id IN (:...assetIds)', { assetIds: [...assetIds] })
|
||||||
|
.getRawMany()
|
||||||
|
.then((rows) => new Set(rows.map((row) => row.assetId)));
|
||||||
},
|
},
|
||||||
|
|
||||||
hasPartnerAccess: (userId: string, assetId: string): Promise<boolean> => {
|
checkSharedLinkAccess: async (sharedLinkId: string, assetIds: Set<string>): Promise<Set<string>> => {
|
||||||
return this.partnerRepository.exist({
|
if (assetIds.size === 0) {
|
||||||
where: {
|
return new Set();
|
||||||
sharedWith: {
|
}
|
||||||
id: userId,
|
|
||||||
},
|
|
||||||
sharedBy: {
|
|
||||||
assets: {
|
|
||||||
id: assetId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
relations: {
|
|
||||||
sharedWith: true,
|
|
||||||
sharedBy: {
|
|
||||||
assets: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
hasSharedLinkAccess: async (sharedLinkId: string, assetId: string): Promise<boolean> => {
|
return this.sharedLinkRepository
|
||||||
return this.sharedLinkRepository.exist({
|
.createQueryBuilder('sharedLink')
|
||||||
where: [
|
.leftJoin('sharedLink.album', 'album')
|
||||||
{
|
.leftJoin('sharedLink.assets', 'assets')
|
||||||
id: sharedLinkId,
|
.leftJoin('album.assets', 'albumAssets')
|
||||||
album: {
|
.select('assets.id', 'assetId')
|
||||||
assets: {
|
.addSelect('albumAssets.id', 'albumAssetId')
|
||||||
id: assetId,
|
.addSelect('assets.livePhotoVideoId', 'assetLivePhotoVideoId')
|
||||||
},
|
.addSelect('albumAssets.livePhotoVideoId', 'albumAssetLivePhotoVideoId')
|
||||||
},
|
.where('sharedLink.id = :sharedLinkId', { sharedLinkId })
|
||||||
},
|
.andWhere(
|
||||||
{
|
new Brackets((qb) => {
|
||||||
id: sharedLinkId,
|
qb.where('assets.id IN (:...assetIds)', { assetIds: [...assetIds] })
|
||||||
assets: {
|
.orWhere('albumAssets.id IN (:...assetIds)', { assetIds: [...assetIds] })
|
||||||
id: assetId,
|
// still part of a live photo is in a shared link
|
||||||
},
|
.orWhere('assets.livePhotoVideoId IN (:...assetIds)', { assetIds: [...assetIds] })
|
||||||
},
|
.orWhere('albumAssets.livePhotoVideoId IN (:...assetIds)', { assetIds: [...assetIds] });
|
||||||
// still part of a live photo is in a shared link
|
}),
|
||||||
{
|
)
|
||||||
id: sharedLinkId,
|
.getRawMany()
|
||||||
album: {
|
.then((rows) => {
|
||||||
assets: {
|
const allowedIds = new Set<string>();
|
||||||
livePhotoVideoId: assetId,
|
for (const row of rows) {
|
||||||
},
|
if (row.assetId && assetIds.has(row.assetId)) {
|
||||||
},
|
allowedIds.add(row.assetId);
|
||||||
},
|
}
|
||||||
{
|
if (row.assetLivePhotoVideoId && assetIds.has(row.assetLivePhotoVideoId)) {
|
||||||
id: sharedLinkId,
|
allowedIds.add(row.assetLivePhotoVideoId);
|
||||||
assets: {
|
}
|
||||||
livePhotoVideoId: assetId,
|
if (row.albumAssetId && assetIds.has(row.albumAssetId)) {
|
||||||
},
|
allowedIds.add(row.albumAssetId);
|
||||||
},
|
}
|
||||||
],
|
if (row.albumAssetLivePhotoVideoId && assetIds.has(row.albumAssetLivePhotoVideoId)) {
|
||||||
});
|
allowedIds.add(row.albumAssetLivePhotoVideoId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return allowedIds;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { IsNull, Repository } from 'typeorm';
|
import { IsNull, Repository } from 'typeorm';
|
||||||
import { ActivityEntity } from '../entities/activity.entity';
|
import { ActivityEntity } from '../entities/activity.entity';
|
||||||
|
import { DummyValue, GenerateSql } from '../infra.util';
|
||||||
|
|
||||||
export interface ActivitySearch {
|
export interface ActivitySearch {
|
||||||
albumId?: string;
|
albumId?: string;
|
||||||
|
@ -15,6 +16,7 @@ export interface ActivitySearch {
|
||||||
export class ActivityRepository implements IActivityRepository {
|
export class ActivityRepository implements IActivityRepository {
|
||||||
constructor(@InjectRepository(ActivityEntity) private repository: Repository<ActivityEntity>) {}
|
constructor(@InjectRepository(ActivityEntity) private repository: Repository<ActivityEntity>) {}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [{ albumId: DummyValue.UUID }] })
|
||||||
search(options: ActivitySearch): Promise<ActivityEntity[]> {
|
search(options: ActivitySearch): Promise<ActivityEntity[]> {
|
||||||
const { userId, assetId, albumId, isLiked } = options;
|
const { userId, assetId, albumId, isLiked } = options;
|
||||||
return this.repository.find({
|
return this.repository.find({
|
||||||
|
@ -41,6 +43,7 @@ export class ActivityRepository implements IActivityRepository {
|
||||||
await this.repository.delete(id);
|
await this.repository.delete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
|
||||||
getStatistics(assetId: string, albumId: string): Promise<number> {
|
getStatistics(assetId: string, albumId: string): Promise<number> {
|
||||||
return this.repository.count({
|
return this.repository.count({
|
||||||
where: { assetId, albumId, isLiked: false },
|
where: { assetId, albumId, isLiked: false },
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||||
import { DataSource, FindOptionsOrder, FindOptionsRelations, In, IsNull, Not, Repository } from 'typeorm';
|
import { DataSource, FindOptionsOrder, FindOptionsRelations, In, IsNull, Not, Repository } from 'typeorm';
|
||||||
import { dataSource } from '../database.config';
|
import { dataSource } from '../database.config';
|
||||||
import { AlbumEntity, AssetEntity } from '../entities';
|
import { AlbumEntity, AssetEntity } from '../entities';
|
||||||
|
import { DummyValue, GenerateSql } from '../infra.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AlbumRepository implements IAlbumRepository {
|
export class AlbumRepository implements IAlbumRepository {
|
||||||
|
@ -13,6 +14,7 @@ export class AlbumRepository implements IAlbumRepository {
|
||||||
@InjectDataSource() private dataSource: DataSource,
|
@InjectDataSource() private dataSource: DataSource,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID, {}] })
|
||||||
getById(id: string, options: AlbumInfoOptions): Promise<AlbumEntity | null> {
|
getById(id: string, options: AlbumInfoOptions): Promise<AlbumEntity | null> {
|
||||||
const relations: FindOptionsRelations<AlbumEntity> = {
|
const relations: FindOptionsRelations<AlbumEntity> = {
|
||||||
owner: true,
|
owner: true,
|
||||||
|
@ -36,6 +38,7 @@ export class AlbumRepository implements IAlbumRepository {
|
||||||
return this.repository.findOne({ where: { id }, relations, order });
|
return this.repository.findOne({ where: { id }, relations, order });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [[DummyValue.UUID]] })
|
||||||
getByIds(ids: string[]): Promise<AlbumEntity[]> {
|
getByIds(ids: string[]): Promise<AlbumEntity[]> {
|
||||||
return this.repository.find({
|
return this.repository.find({
|
||||||
where: {
|
where: {
|
||||||
|
@ -48,6 +51,7 @@ export class AlbumRepository implements IAlbumRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
|
||||||
getByAssetId(ownerId: string, assetId: string): Promise<AlbumEntity[]> {
|
getByAssetId(ownerId: string, assetId: string): Promise<AlbumEntity[]> {
|
||||||
return this.repository.find({
|
return this.repository.find({
|
||||||
where: [
|
where: [
|
||||||
|
@ -59,6 +63,7 @@ export class AlbumRepository implements IAlbumRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [[DummyValue.UUID]] })
|
||||||
async getMetadataForIds(ids: string[]): Promise<AlbumAssetCount[]> {
|
async getMetadataForIds(ids: string[]): Promise<AlbumAssetCount[]> {
|
||||||
// Guard against running invalid query when ids list is empty.
|
// Guard against running invalid query when ids list is empty.
|
||||||
if (!ids.length) {
|
if (!ids.length) {
|
||||||
|
@ -91,6 +96,7 @@ export class AlbumRepository implements IAlbumRepository {
|
||||||
* - Thumbnail references an asset outside the album
|
* - Thumbnail references an asset outside the album
|
||||||
* - Empty album still has a thumbnail set
|
* - Empty album still has a thumbnail set
|
||||||
*/
|
*/
|
||||||
|
@GenerateSql()
|
||||||
async getInvalidThumbnail(): Promise<string[]> {
|
async getInvalidThumbnail(): Promise<string[]> {
|
||||||
// Using dataSource, because there is no direct access to albums_assets_assets.
|
// Using dataSource, because there is no direct access to albums_assets_assets.
|
||||||
const albumHasAssets = this.dataSource
|
const albumHasAssets = this.dataSource
|
||||||
|
@ -113,6 +119,7 @@ export class AlbumRepository implements IAlbumRepository {
|
||||||
return albums.map((album) => album.id);
|
return albums.map((album) => album.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
getOwned(ownerId: string): Promise<AlbumEntity[]> {
|
getOwned(ownerId: string): Promise<AlbumEntity[]> {
|
||||||
return this.repository.find({
|
return this.repository.find({
|
||||||
relations: { sharedUsers: true, sharedLinks: true, owner: true },
|
relations: { sharedUsers: true, sharedLinks: true, owner: true },
|
||||||
|
@ -124,6 +131,7 @@ export class AlbumRepository implements IAlbumRepository {
|
||||||
/**
|
/**
|
||||||
* Get albums shared with and shared by owner.
|
* Get albums shared with and shared by owner.
|
||||||
*/
|
*/
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
getShared(ownerId: string): Promise<AlbumEntity[]> {
|
getShared(ownerId: string): Promise<AlbumEntity[]> {
|
||||||
return this.repository.find({
|
return this.repository.find({
|
||||||
relations: { sharedUsers: true, sharedLinks: true, owner: true },
|
relations: { sharedUsers: true, sharedLinks: true, owner: true },
|
||||||
|
@ -139,6 +147,7 @@ export class AlbumRepository implements IAlbumRepository {
|
||||||
/**
|
/**
|
||||||
* Get albums of owner that are _not_ shared
|
* Get albums of owner that are _not_ shared
|
||||||
*/
|
*/
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
getNotShared(ownerId: string): Promise<AlbumEntity[]> {
|
getNotShared(ownerId: string): Promise<AlbumEntity[]> {
|
||||||
return this.repository.find({
|
return this.repository.find({
|
||||||
relations: { sharedUsers: true, sharedLinks: true, owner: true },
|
relations: { sharedUsers: true, sharedLinks: true, owner: true },
|
||||||
|
@ -159,6 +168,7 @@ export class AlbumRepository implements IAlbumRepository {
|
||||||
await this.repository.delete({ ownerId: userId });
|
await this.repository.delete({ ownerId: userId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql()
|
||||||
getAll(): Promise<AlbumEntity[]> {
|
getAll(): Promise<AlbumEntity[]> {
|
||||||
return this.repository.find({
|
return this.repository.find({
|
||||||
relations: {
|
relations: {
|
||||||
|
@ -167,6 +177,7 @@ export class AlbumRepository implements IAlbumRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @GenerateSql({ params: [DummyValue.UUID] })
|
||||||
async removeAsset(assetId: string): Promise<void> {
|
async removeAsset(assetId: string): Promise<void> {
|
||||||
// Using dataSource, because there is no direct access to albums_assets_assets.
|
// Using dataSource, because there is no direct access to albums_assets_assets.
|
||||||
await this.dataSource
|
await this.dataSource
|
||||||
|
@ -176,6 +187,7 @@ export class AlbumRepository implements IAlbumRepository {
|
||||||
.where('"albums_assets_assets"."assetsId" = :assetId', { assetId });
|
.where('"albums_assets_assets"."assetsId" = :assetId', { assetId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [{ albumId: DummyValue.UUID, assetIds: [DummyValue.UUID] }] })
|
||||||
async removeAssets(asset: AlbumAssets): Promise<void> {
|
async removeAssets(asset: AlbumAssets): Promise<void> {
|
||||||
await this.dataSource
|
await this.dataSource
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
|
@ -195,6 +207,7 @@ export class AlbumRepository implements IAlbumRepository {
|
||||||
* @param assetIds Optional list of asset IDs to filter on.
|
* @param assetIds Optional list of asset IDs to filter on.
|
||||||
* @returns Set of Asset IDs for the given album ID.
|
* @returns Set of Asset IDs for the given album ID.
|
||||||
*/
|
*/
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID]] }, { name: 'no assets', params: [DummyValue.UUID] })
|
||||||
async getAssetIds(albumId: string, assetIds?: string[]): Promise<Set<string>> {
|
async getAssetIds(albumId: string, assetIds?: string[]): Promise<Set<string>> {
|
||||||
const query = this.dataSource
|
const query = this.dataSource
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
|
@ -210,6 +223,7 @@ export class AlbumRepository implements IAlbumRepository {
|
||||||
return new Set(result.map((row) => row['assetId']));
|
return new Set(result.map((row) => row['assetId']));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [{ albumId: DummyValue.UUID, assetId: DummyValue.UUID }] })
|
||||||
hasAsset(asset: AlbumAsset): Promise<boolean> {
|
hasAsset(asset: AlbumAsset): Promise<boolean> {
|
||||||
return this.repository.exist({
|
return this.repository.exist({
|
||||||
where: {
|
where: {
|
||||||
|
@ -224,6 +238,7 @@ export class AlbumRepository implements IAlbumRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [{ albumId: DummyValue.UUID, assetIds: [DummyValue.UUID] }] })
|
||||||
async addAssets({ albumId, assetIds }: AlbumAssets): Promise<void> {
|
async addAssets({ albumId, assetIds }: AlbumAssets): Promise<void> {
|
||||||
await this.dataSource
|
await this.dataSource
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
|
@ -266,6 +281,7 @@ export class AlbumRepository implements IAlbumRepository {
|
||||||
*
|
*
|
||||||
* @returns Amount of updated album thumbnails or undefined when unknown
|
* @returns Amount of updated album thumbnails or undefined when unknown
|
||||||
*/
|
*/
|
||||||
|
@GenerateSql()
|
||||||
async updateThumbnails(): Promise<number | undefined> {
|
async updateThumbnails(): Promise<number | undefined> {
|
||||||
// Subquery for getting a new thumbnail.
|
// Subquery for getting a new thumbnail.
|
||||||
const newThumbnail = this.assetRepository
|
const newThumbnail = this.assetRepository
|
||||||
|
|
|
@ -3,9 +3,10 @@ import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { APIKeyEntity } from '../entities';
|
import { APIKeyEntity } from '../entities';
|
||||||
|
import { DummyValue, GenerateSql } from '../infra.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class APIKeyRepository implements IKeyRepository {
|
export class ApiKeyRepository implements IKeyRepository {
|
||||||
constructor(@InjectRepository(APIKeyEntity) private repository: Repository<APIKeyEntity>) {}
|
constructor(@InjectRepository(APIKeyEntity) private repository: Repository<APIKeyEntity>) {}
|
||||||
|
|
||||||
async create(dto: Partial<APIKeyEntity>): Promise<APIKeyEntity> {
|
async create(dto: Partial<APIKeyEntity>): Promise<APIKeyEntity> {
|
||||||
|
@ -21,6 +22,7 @@ export class APIKeyRepository implements IKeyRepository {
|
||||||
await this.repository.delete({ userId, id });
|
await this.repository.delete({ userId, id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.STRING] })
|
||||||
getKey(hashedToken: string): Promise<APIKeyEntity | null> {
|
getKey(hashedToken: string): Promise<APIKeyEntity | null> {
|
||||||
return this.repository.findOne({
|
return this.repository.findOne({
|
||||||
select: {
|
select: {
|
||||||
|
@ -35,10 +37,12 @@ export class APIKeyRepository implements IKeyRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
|
||||||
getById(userId: string, id: string): Promise<APIKeyEntity | null> {
|
getById(userId: string, id: string): Promise<APIKeyEntity | null> {
|
||||||
return this.repository.findOne({ where: { userId, id } });
|
return this.repository.findOne({ where: { userId, id } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.STRING] })
|
||||||
getByUserId(userId: string): Promise<APIKeyEntity[]> {
|
getByUserId(userId: string): Promise<APIKeyEntity[]> {
|
||||||
return this.repository.find({ where: { userId }, order: { createdAt: 'DESC' } });
|
return this.repository.find({ where: { userId }, order: { createdAt: 'DESC' } });
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import _ from 'lodash';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { And, FindOptionsRelations, FindOptionsWhere, In, IsNull, LessThan, Not, Repository } from 'typeorm';
|
import { And, FindOptionsRelations, FindOptionsWhere, In, IsNull, LessThan, Not, Repository } from 'typeorm';
|
||||||
import { AssetEntity, AssetJobStatusEntity, AssetType, ExifEntity } from '../entities';
|
import { AssetEntity, AssetJobStatusEntity, AssetType, ExifEntity } from '../entities';
|
||||||
|
import { DummyValue, GenerateSql } from '../infra.util';
|
||||||
import OptionalBetween from '../utils/optional-between.util';
|
import OptionalBetween from '../utils/optional-between.util';
|
||||||
import { paginate } from '../utils/pagination.util';
|
import { paginate } from '../utils/pagination.util';
|
||||||
|
|
||||||
|
@ -185,6 +186,7 @@ export class AssetRepository implements IAssetRepository {
|
||||||
return this.repository.save(asset);
|
return this.repository.save(asset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID, DummyValue.DATE] })
|
||||||
getByDate(ownerId: string, date: Date): Promise<AssetEntity[]> {
|
getByDate(ownerId: string, date: Date): Promise<AssetEntity[]> {
|
||||||
// For reference of a correct approach although slower
|
// For reference of a correct approach although slower
|
||||||
|
|
||||||
|
@ -219,6 +221,7 @@ export class AssetRepository implements IAssetRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID, { day: 1, month: 1 }] })
|
||||||
getByDayOfYear(ownerId: string, { day, month }: MonthDay): Promise<AssetEntity[]> {
|
getByDayOfYear(ownerId: string, { day, month }: MonthDay): Promise<AssetEntity[]> {
|
||||||
return this.repository
|
return this.repository
|
||||||
.createQueryBuilder('entity')
|
.createQueryBuilder('entity')
|
||||||
|
@ -240,6 +243,7 @@ export class AssetRepository implements IAssetRepository {
|
||||||
.getMany();
|
.getMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [[DummyValue.UUID]] })
|
||||||
getByIds(ids: string[], relations?: FindOptionsRelations<AssetEntity>): Promise<AssetEntity[]> {
|
getByIds(ids: string[], relations?: FindOptionsRelations<AssetEntity>): Promise<AssetEntity[]> {
|
||||||
if (!relations) {
|
if (!relations) {
|
||||||
relations = {
|
relations = {
|
||||||
|
@ -259,6 +263,7 @@ export class AssetRepository implements IAssetRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
async deleteAll(ownerId: string): Promise<void> {
|
async deleteAll(ownerId: string): Promise<void> {
|
||||||
await this.repository.delete({ ownerId });
|
await this.repository.delete({ ownerId });
|
||||||
}
|
}
|
||||||
|
@ -291,12 +296,14 @@ export class AssetRepository implements IAssetRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [[DummyValue.UUID]] })
|
||||||
getByLibraryId(libraryIds: string[]): Promise<AssetEntity[]> {
|
getByLibraryId(libraryIds: string[]): Promise<AssetEntity[]> {
|
||||||
return this.repository.find({
|
return this.repository.find({
|
||||||
where: { library: { id: In(libraryIds) } },
|
where: { library: { id: In(libraryIds) } },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] })
|
||||||
getByLibraryIdAndOriginalPath(libraryId: string, originalPath: string): Promise<AssetEntity | null> {
|
getByLibraryIdAndOriginalPath(libraryId: string, originalPath: string): Promise<AssetEntity | null> {
|
||||||
return this.repository.findOne({
|
return this.repository.findOne({
|
||||||
where: { library: { id: libraryId }, originalPath: originalPath },
|
where: { library: { id: libraryId }, originalPath: originalPath },
|
||||||
|
@ -333,6 +340,7 @@ export class AssetRepository implements IAssetRepository {
|
||||||
*
|
*
|
||||||
* @returns Promise<string[]> - Array of assetIds belong to the device
|
* @returns Promise<string[]> - Array of assetIds belong to the device
|
||||||
*/
|
*/
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] })
|
||||||
async getAllByDeviceId(ownerId: string, deviceId: string): Promise<string[]> {
|
async getAllByDeviceId(ownerId: string, deviceId: string): Promise<string[]> {
|
||||||
const items = await this.repository.find({
|
const items = await this.repository.find({
|
||||||
select: { deviceAssetId: true },
|
select: { deviceAssetId: true },
|
||||||
|
@ -347,6 +355,7 @@ export class AssetRepository implements IAssetRepository {
|
||||||
return items.map((asset) => asset.deviceAssetId);
|
return items.map((asset) => asset.deviceAssetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
getById(id: string): Promise<AssetEntity | null> {
|
getById(id: string): Promise<AssetEntity | null> {
|
||||||
return this.repository.findOne({
|
return this.repository.findOne({
|
||||||
where: { id },
|
where: { id },
|
||||||
|
@ -362,6 +371,7 @@ export class AssetRepository implements IAssetRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [[DummyValue.UUID], { deviceId: DummyValue.STRING }] })
|
||||||
async updateAll(ids: string[], options: Partial<AssetEntity>): Promise<void> {
|
async updateAll(ids: string[], options: Partial<AssetEntity>): Promise<void> {
|
||||||
await this.repository.update({ id: In(ids) }, options);
|
await this.repository.update({ id: In(ids) }, options);
|
||||||
}
|
}
|
||||||
|
@ -395,6 +405,7 @@ export class AssetRepository implements IAssetRepository {
|
||||||
await this.repository.remove(asset);
|
await this.repository.remove(asset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [[DummyValue.UUID], DummyValue.BUFFER] })
|
||||||
getByChecksum(userId: string, checksum: Buffer): Promise<AssetEntity | null> {
|
getByChecksum(userId: string, checksum: Buffer): Promise<AssetEntity | null> {
|
||||||
return this.repository.findOne({ where: { ownerId: userId, checksum } });
|
return this.repository.findOne({ where: { ownerId: userId, checksum } });
|
||||||
}
|
}
|
||||||
|
@ -417,6 +428,14 @@ export class AssetRepository implements IAssetRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql(
|
||||||
|
...Object.values(WithProperty)
|
||||||
|
.filter((property) => property !== WithProperty.IS_OFFLINE)
|
||||||
|
.map((property) => ({
|
||||||
|
name: property,
|
||||||
|
params: [DummyValue.PAGINATION, property],
|
||||||
|
})),
|
||||||
|
)
|
||||||
getWithout(pagination: PaginationOptions, property: WithoutProperty): Paginated<AssetEntity> {
|
getWithout(pagination: PaginationOptions, property: WithoutProperty): Paginated<AssetEntity> {
|
||||||
let relations: FindOptionsRelations<AssetEntity> = {};
|
let relations: FindOptionsRelations<AssetEntity> = {};
|
||||||
let where: FindOptionsWhere<AssetEntity> | FindOptionsWhere<AssetEntity>[] = {};
|
let where: FindOptionsWhere<AssetEntity> | FindOptionsWhere<AssetEntity>[] = {};
|
||||||
|
|
|
@ -4,11 +4,13 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { IsNull, Not } from 'typeorm';
|
import { IsNull, Not } from 'typeorm';
|
||||||
import { Repository } from 'typeorm/repository/Repository';
|
import { Repository } from 'typeorm/repository/Repository';
|
||||||
import { LibraryEntity, LibraryType } from '../entities';
|
import { LibraryEntity, LibraryType } from '../entities';
|
||||||
|
import { DummyValue, GenerateSql } from '../infra.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LibraryRepository implements ILibraryRepository {
|
export class LibraryRepository implements ILibraryRepository {
|
||||||
constructor(@InjectRepository(LibraryEntity) private repository: Repository<LibraryEntity>) {}
|
constructor(@InjectRepository(LibraryEntity) private repository: Repository<LibraryEntity>) {}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
get(id: string, withDeleted = false): Promise<LibraryEntity | null> {
|
get(id: string, withDeleted = false): Promise<LibraryEntity | null> {
|
||||||
return this.repository.findOneOrFail({
|
return this.repository.findOneOrFail({
|
||||||
where: {
|
where: {
|
||||||
|
@ -19,6 +21,7 @@ export class LibraryRepository implements ILibraryRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.STRING] })
|
||||||
existsByName(name: string, withDeleted = false): Promise<boolean> {
|
existsByName(name: string, withDeleted = false): Promise<boolean> {
|
||||||
return this.repository.exist({
|
return this.repository.exist({
|
||||||
where: {
|
where: {
|
||||||
|
@ -28,10 +31,12 @@ export class LibraryRepository implements ILibraryRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
getCountForUser(ownerId: string): Promise<number> {
|
getCountForUser(ownerId: string): Promise<number> {
|
||||||
return this.repository.countBy({ ownerId });
|
return this.repository.countBy({ ownerId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
getDefaultUploadLibrary(ownerId: string): Promise<LibraryEntity | null> {
|
getDefaultUploadLibrary(ownerId: string): Promise<LibraryEntity | null> {
|
||||||
return this.repository.findOne({
|
return this.repository.findOne({
|
||||||
where: {
|
where: {
|
||||||
|
@ -44,6 +49,7 @@ export class LibraryRepository implements ILibraryRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
getUploadLibraryCount(ownerId: string): Promise<number> {
|
getUploadLibraryCount(ownerId: string): Promise<number> {
|
||||||
return this.repository.count({
|
return this.repository.count({
|
||||||
where: {
|
where: {
|
||||||
|
@ -53,6 +59,7 @@ export class LibraryRepository implements ILibraryRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
getAllByUserId(ownerId: string, type?: LibraryType): Promise<LibraryEntity[]> {
|
getAllByUserId(ownerId: string, type?: LibraryType): Promise<LibraryEntity[]> {
|
||||||
return this.repository.find({
|
return this.repository.find({
|
||||||
where: {
|
where: {
|
||||||
|
@ -69,6 +76,7 @@ export class LibraryRepository implements ILibraryRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [] })
|
||||||
getAll(withDeleted = false, type?: LibraryType): Promise<LibraryEntity[]> {
|
getAll(withDeleted = false, type?: LibraryType): Promise<LibraryEntity[]> {
|
||||||
return this.repository.find({
|
return this.repository.find({
|
||||||
where: { type },
|
where: { type },
|
||||||
|
@ -82,6 +90,7 @@ export class LibraryRepository implements ILibraryRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql()
|
||||||
getAllDeleted(): Promise<LibraryEntity[]> {
|
getAllDeleted(): Promise<LibraryEntity[]> {
|
||||||
return this.repository.find({
|
return this.repository.find({
|
||||||
where: {
|
where: {
|
||||||
|
@ -114,6 +123,7 @@ export class LibraryRepository implements ILibraryRepository {
|
||||||
return this.save(library);
|
return this.save(library);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
async getStatistics(id: string): Promise<LibraryStatsResponseDto> {
|
async getStatistics(id: string): Promise<LibraryStatsResponseDto> {
|
||||||
const stats = await this.repository
|
const stats = await this.repository
|
||||||
.createQueryBuilder('libraries')
|
.createQueryBuilder('libraries')
|
||||||
|
@ -134,6 +144,7 @@ export class LibraryRepository implements ILibraryRepository {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
async getOnlineAssetPaths(libraryId: string): Promise<string[]> {
|
async getOnlineAssetPaths(libraryId: string): Promise<string[]> {
|
||||||
// Return all non-offline asset paths for a given library
|
// Return all non-offline asset paths for a given library
|
||||||
const rawResults = await this.repository
|
const rawResults = await this.repository
|
||||||
|
@ -153,6 +164,7 @@ export class LibraryRepository implements ILibraryRepository {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
async getAssetIds(libraryId: string, withDeleted = false): Promise<string[]> {
|
async getAssetIds(libraryId: string, withDeleted = false): Promise<string[]> {
|
||||||
let query = await this.repository
|
let query = await this.repository
|
||||||
.createQueryBuilder('library')
|
.createQueryBuilder('library')
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { GeodataAdmin1Entity, GeodataAdmin2Entity, GeodataPlacesEntity, SystemMe
|
||||||
import { DatabaseLock } from '@app/infra/utils/database-locks';
|
import { DatabaseLock } from '@app/infra/utils/database-locks';
|
||||||
import { Inject, Logger } from '@nestjs/common';
|
import { Inject, Logger } from '@nestjs/common';
|
||||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||||
import { DefaultReadTaskOptions, exiftool } from 'exiftool-vendored';
|
import { DefaultReadTaskOptions, exiftool, Tags } from 'exiftool-vendored';
|
||||||
import { createReadStream, existsSync } from 'fs';
|
import { createReadStream, existsSync } from 'fs';
|
||||||
import { readFile } from 'fs/promises';
|
import { readFile } from 'fs/promises';
|
||||||
import * as geotz from 'geo-tz';
|
import * as geotz from 'geo-tz';
|
||||||
|
@ -181,7 +181,7 @@ export class MetadataRepository implements IMetadataRepository {
|
||||||
return { country, state, city };
|
return { country, state, city };
|
||||||
}
|
}
|
||||||
|
|
||||||
getExifTags(path: string): Promise<ImmichTags | null> {
|
readTags(path: string): Promise<ImmichTags | null> {
|
||||||
return exiftool
|
return exiftool
|
||||||
.read(path, undefined, {
|
.read(path, undefined, {
|
||||||
...DefaultReadTaskOptions,
|
...DefaultReadTaskOptions,
|
||||||
|
@ -198,4 +198,12 @@ export class MetadataRepository implements IMetadataRepository {
|
||||||
return null;
|
return null;
|
||||||
}) as Promise<ImmichTags | null>;
|
}) as Promise<ImmichTags | null>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async writeTags(path: string, tags: Partial<Tags>): Promise<void> {
|
||||||
|
try {
|
||||||
|
await exiftool.write(path, tags, ['-overwrite_original']);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.warn(`Error writing exif data (${path}): ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { MoveEntity, PathType } from '../entities';
|
import { MoveEntity, PathType } from '../entities';
|
||||||
|
import { DummyValue, GenerateSql } from '../infra.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MoveRepository implements IMoveRepository {
|
export class MoveRepository implements IMoveRepository {
|
||||||
|
@ -12,6 +13,7 @@ export class MoveRepository implements IMoveRepository {
|
||||||
return this.repository.save(entity);
|
return this.repository.save(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] })
|
||||||
getByEntity(entityId: string, pathType: PathType): Promise<MoveEntity | null> {
|
getByEntity(entityId: string, pathType: PathType): Promise<MoveEntity | null> {
|
||||||
return this.repository.findOne({ where: { entityId, pathType } });
|
return this.repository.findOne({ where: { entityId, pathType } });
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { In, Repository } from 'typeorm';
|
import { In, Repository } from 'typeorm';
|
||||||
import { AssetEntity, AssetFaceEntity, PersonEntity } from '../entities';
|
import { AssetEntity, AssetFaceEntity, PersonEntity } from '../entities';
|
||||||
|
import { DummyValue, GenerateSql } from '../infra.util';
|
||||||
|
|
||||||
export class PersonRepository implements IPersonRepository {
|
export class PersonRepository implements IPersonRepository {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -36,6 +37,7 @@ export class PersonRepository implements IPersonRepository {
|
||||||
return assetIds;
|
return assetIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [{ oldPersonId: DummyValue.UUID, newPersonId: DummyValue.UUID }] })
|
||||||
async reassignFaces({ oldPersonId, newPersonId }: UpdateFacesData): Promise<number> {
|
async reassignFaces({ oldPersonId, newPersonId }: UpdateFacesData): Promise<number> {
|
||||||
const result = await this.assetFaceRepository
|
const result = await this.assetFaceRepository
|
||||||
.createQueryBuilder()
|
.createQueryBuilder()
|
||||||
|
@ -57,18 +59,22 @@ export class PersonRepository implements IPersonRepository {
|
||||||
return people.length;
|
return people.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql()
|
||||||
getAllFaces(): Promise<AssetFaceEntity[]> {
|
getAllFaces(): Promise<AssetFaceEntity[]> {
|
||||||
return this.assetFaceRepository.find({ relations: { asset: true }, withDeleted: true });
|
return this.assetFaceRepository.find({ relations: { asset: true }, withDeleted: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql()
|
||||||
getAll(): Promise<PersonEntity[]> {
|
getAll(): Promise<PersonEntity[]> {
|
||||||
return this.personRepository.find();
|
return this.personRepository.find();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql()
|
||||||
getAllWithoutThumbnail(): Promise<PersonEntity[]> {
|
getAllWithoutThumbnail(): Promise<PersonEntity[]> {
|
||||||
return this.personRepository.findBy({ thumbnailPath: '' });
|
return this.personRepository.findBy({ thumbnailPath: '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
getAllForUser(userId: string, options?: PersonSearchOptions): Promise<PersonEntity[]> {
|
getAllForUser(userId: string, options?: PersonSearchOptions): Promise<PersonEntity[]> {
|
||||||
const queryBuilder = this.personRepository
|
const queryBuilder = this.personRepository
|
||||||
.createQueryBuilder('person')
|
.createQueryBuilder('person')
|
||||||
|
@ -89,6 +95,7 @@ export class PersonRepository implements IPersonRepository {
|
||||||
return queryBuilder.getMany();
|
return queryBuilder.getMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql()
|
||||||
getAllWithoutFaces(): Promise<PersonEntity[]> {
|
getAllWithoutFaces(): Promise<PersonEntity[]> {
|
||||||
return this.personRepository
|
return this.personRepository
|
||||||
.createQueryBuilder('person')
|
.createQueryBuilder('person')
|
||||||
|
@ -99,10 +106,12 @@ export class PersonRepository implements IPersonRepository {
|
||||||
.getMany();
|
.getMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
getById(personId: string): Promise<PersonEntity | null> {
|
getById(personId: string): Promise<PersonEntity | null> {
|
||||||
return this.personRepository.findOne({ where: { id: personId } });
|
return this.personRepository.findOne({ where: { id: personId } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING, { withHidden: true }] })
|
||||||
getByName(userId: string, personName: string, { withHidden }: PersonNameSearchOptions): Promise<PersonEntity[]> {
|
getByName(userId: string, personName: string, { withHidden }: PersonNameSearchOptions): Promise<PersonEntity[]> {
|
||||||
const queryBuilder = this.personRepository
|
const queryBuilder = this.personRepository
|
||||||
.createQueryBuilder('person')
|
.createQueryBuilder('person')
|
||||||
|
@ -121,6 +130,7 @@ export class PersonRepository implements IPersonRepository {
|
||||||
return queryBuilder.getMany();
|
return queryBuilder.getMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
async getStatistics(personId: string): Promise<PersonStatistics> {
|
async getStatistics(personId: string): Promise<PersonStatistics> {
|
||||||
return {
|
return {
|
||||||
assets: await this.assetFaceRepository
|
assets: await this.assetFaceRepository
|
||||||
|
@ -135,6 +145,7 @@ export class PersonRepository implements IPersonRepository {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
getAssets(personId: string): Promise<AssetEntity[]> {
|
getAssets(personId: string): Promise<AssetEntity[]> {
|
||||||
return this.assetRepository.find({
|
return this.assetRepository.find({
|
||||||
where: {
|
where: {
|
||||||
|
@ -171,10 +182,12 @@ export class PersonRepository implements IPersonRepository {
|
||||||
return this.personRepository.findOneByOrFail({ id });
|
return this.personRepository.findOneByOrFail({ id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [[{ assetId: DummyValue.UUID, personId: DummyValue.UUID }]] })
|
||||||
async getFacesByIds(ids: AssetFaceId[]): Promise<AssetFaceEntity[]> {
|
async getFacesByIds(ids: AssetFaceId[]): Promise<AssetFaceEntity[]> {
|
||||||
return this.assetFaceRepository.find({ where: ids, relations: { asset: true }, withDeleted: true });
|
return this.assetFaceRepository.find({ where: ids, relations: { asset: true }, withDeleted: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
async getRandomFace(personId: string): Promise<AssetFaceEntity | null> {
|
async getRandomFace(personId: string): Promise<AssetFaceEntity | null> {
|
||||||
return this.assetFaceRepository.findOneBy({ personId });
|
return this.assetFaceRepository.findOneBy({ personId });
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,13 @@ import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { SharedLinkEntity } from '../entities';
|
import { SharedLinkEntity } from '../entities';
|
||||||
|
import { DummyValue, GenerateSql } from '../infra.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SharedLinkRepository implements ISharedLinkRepository {
|
export class SharedLinkRepository implements ISharedLinkRepository {
|
||||||
constructor(@InjectRepository(SharedLinkEntity) private repository: Repository<SharedLinkEntity>) {}
|
constructor(@InjectRepository(SharedLinkEntity) private repository: Repository<SharedLinkEntity>) {}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
|
||||||
get(userId: string, id: string): Promise<SharedLinkEntity | null> {
|
get(userId: string, id: string): Promise<SharedLinkEntity | null> {
|
||||||
return this.repository.findOne({
|
return this.repository.findOne({
|
||||||
where: {
|
where: {
|
||||||
|
@ -39,6 +41,7 @@ export class SharedLinkRepository implements ISharedLinkRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
getAll(userId: string): Promise<SharedLinkEntity[]> {
|
getAll(userId: string): Promise<SharedLinkEntity[]> {
|
||||||
return this.repository.find({
|
return this.repository.find({
|
||||||
where: {
|
where: {
|
||||||
|
@ -56,6 +59,7 @@ export class SharedLinkRepository implements ISharedLinkRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.BUFFER] })
|
||||||
async getByKey(key: Buffer): Promise<SharedLinkEntity | null> {
|
async getByKey(key: Buffer): Promise<SharedLinkEntity | null> {
|
||||||
return await this.repository.findOne({
|
return await this.repository.findOne({
|
||||||
where: {
|
where: {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import axios from 'axios';
|
||||||
import { readFile } from 'fs/promises';
|
import { readFile } from 'fs/promises';
|
||||||
import { In, Repository } from 'typeorm';
|
import { In, Repository } from 'typeorm';
|
||||||
import { SystemConfigEntity } from '../entities';
|
import { SystemConfigEntity } from '../entities';
|
||||||
|
import { DummyValue, GenerateSql } from '../infra.util';
|
||||||
|
|
||||||
export class SystemConfigRepository implements ISystemConfigRepository {
|
export class SystemConfigRepository implements ISystemConfigRepository {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -14,6 +15,7 @@ export class SystemConfigRepository implements ISystemConfigRepository {
|
||||||
return axios.get(url).then((response) => response.data);
|
return axios.get(url).then((response) => response.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql()
|
||||||
load(): Promise<SystemConfigEntity[]> {
|
load(): Promise<SystemConfigEntity[]> {
|
||||||
return this.repository.find();
|
return this.repository.find();
|
||||||
}
|
}
|
||||||
|
@ -26,6 +28,7 @@ export class SystemConfigRepository implements ISystemConfigRepository {
|
||||||
return this.repository.save(items);
|
return this.repository.save(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.STRING] })
|
||||||
async deleteKeys(keys: string[]): Promise<void> {
|
async deleteKeys(keys: string[]): Promise<void> {
|
||||||
await this.repository.delete({ key: In(keys) });
|
await this.repository.delete({ key: In(keys) });
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,13 @@ import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
import { UserTokenEntity } from '../entities';
|
import { UserTokenEntity } from '../entities';
|
||||||
|
import { DummyValue, GenerateSql } from '../infra.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserTokenRepository implements IUserTokenRepository {
|
export class UserTokenRepository implements IUserTokenRepository {
|
||||||
constructor(@InjectRepository(UserTokenEntity) private repository: Repository<UserTokenEntity>) {}
|
constructor(@InjectRepository(UserTokenEntity) private repository: Repository<UserTokenEntity>) {}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.STRING] })
|
||||||
getByToken(token: string): Promise<UserTokenEntity | null> {
|
getByToken(token: string): Promise<UserTokenEntity | null> {
|
||||||
return this.repository.findOne({ where: { token }, relations: { user: true } });
|
return this.repository.findOne({ where: { token }, relations: { user: true } });
|
||||||
}
|
}
|
||||||
|
@ -35,6 +37,7 @@ export class UserTokenRepository implements IUserTokenRepository {
|
||||||
return this.repository.save(userToken);
|
return this.repository.save(userToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
async delete(id: string): Promise<void> {
|
async delete(id: string): Promise<void> {
|
||||||
await this.repository.delete({ id });
|
await this.repository.delete({ id });
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { IsNull, Not, Repository } from 'typeorm';
|
import { IsNull, Not, Repository } from 'typeorm';
|
||||||
import { UserEntity } from '../entities';
|
import { UserEntity } from '../entities';
|
||||||
|
import { DummyValue, GenerateSql } from '../infra.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserRepository implements IUserRepository {
|
export class UserRepository implements IUserRepository {
|
||||||
|
@ -16,14 +17,17 @@ export class UserRepository implements IUserRepository {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql()
|
||||||
async getAdmin(): Promise<UserEntity | null> {
|
async getAdmin(): Promise<UserEntity | null> {
|
||||||
return this.userRepository.findOne({ where: { isAdmin: true } });
|
return this.userRepository.findOne({ where: { isAdmin: true } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql()
|
||||||
async hasAdmin(): Promise<boolean> {
|
async hasAdmin(): Promise<boolean> {
|
||||||
return this.userRepository.exist({ where: { isAdmin: true } });
|
return this.userRepository.exist({ where: { isAdmin: true } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.EMAIL] })
|
||||||
async getByEmail(email: string, withPassword?: boolean): Promise<UserEntity | null> {
|
async getByEmail(email: string, withPassword?: boolean): Promise<UserEntity | null> {
|
||||||
let builder = this.userRepository.createQueryBuilder('user').where({ email });
|
let builder = this.userRepository.createQueryBuilder('user').where({ email });
|
||||||
|
|
||||||
|
@ -34,10 +38,12 @@ export class UserRepository implements IUserRepository {
|
||||||
return builder.getOne();
|
return builder.getOne();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.STRING] })
|
||||||
async getByStorageLabel(storageLabel: string): Promise<UserEntity | null> {
|
async getByStorageLabel(storageLabel: string): Promise<UserEntity | null> {
|
||||||
return this.userRepository.findOne({ where: { storageLabel } });
|
return this.userRepository.findOne({ where: { storageLabel } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql({ params: [DummyValue.STRING] })
|
||||||
async getByOAuthId(oauthId: string): Promise<UserEntity | null> {
|
async getByOAuthId(oauthId: string): Promise<UserEntity | null> {
|
||||||
return this.userRepository.findOne({ where: { oauthId } });
|
return this.userRepository.findOne({ where: { oauthId } });
|
||||||
}
|
}
|
||||||
|
@ -76,6 +82,7 @@ export class UserRepository implements IUserRepository {
|
||||||
return this.userRepository.recover(user);
|
return this.userRepository.recover(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@GenerateSql()
|
||||||
async getUserStats(): Promise<UserStatsQueryResponse[]> {
|
async getUserStats(): Promise<UserStatsQueryResponse[]> {
|
||||||
const stats = await this.userRepository
|
const stats = await this.userRepository
|
||||||
.createQueryBuilder('users')
|
.createQueryBuilder('users')
|
||||||
|
|
168
server/src/infra/sql-generator/index.ts
Normal file
168
server/src/infra/sql-generator/index.ts
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
import { INestApplication } from '@nestjs/common';
|
||||||
|
import { Reflector } from '@nestjs/core';
|
||||||
|
import { Test } from '@nestjs/testing';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { mkdir, rm, writeFile } from 'fs/promises';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { databaseConfig } from '../database.config';
|
||||||
|
import { databaseEntities } from '../entities';
|
||||||
|
import { GENERATE_SQL_KEY, GenerateSqlQueries } from '../infra.util';
|
||||||
|
import {
|
||||||
|
AccessRepository,
|
||||||
|
AlbumRepository,
|
||||||
|
ApiKeyRepository,
|
||||||
|
AssetRepository,
|
||||||
|
AuditRepository,
|
||||||
|
LibraryRepository,
|
||||||
|
MoveRepository,
|
||||||
|
PartnerRepository,
|
||||||
|
PersonRepository,
|
||||||
|
SharedLinkRepository,
|
||||||
|
SystemConfigRepository,
|
||||||
|
SystemMetadataRepository,
|
||||||
|
TagRepository,
|
||||||
|
UserRepository,
|
||||||
|
UserTokenRepository,
|
||||||
|
} from '../repositories';
|
||||||
|
import { SqlLogger } from './sql.logger';
|
||||||
|
|
||||||
|
const reflector = new Reflector();
|
||||||
|
const repositories = [
|
||||||
|
AccessRepository,
|
||||||
|
AlbumRepository,
|
||||||
|
ApiKeyRepository,
|
||||||
|
AssetRepository,
|
||||||
|
AuditRepository,
|
||||||
|
LibraryRepository,
|
||||||
|
MoveRepository,
|
||||||
|
PartnerRepository,
|
||||||
|
PersonRepository,
|
||||||
|
SharedLinkRepository,
|
||||||
|
SystemConfigRepository,
|
||||||
|
SystemMetadataRepository,
|
||||||
|
TagRepository,
|
||||||
|
UserTokenRepository,
|
||||||
|
UserRepository,
|
||||||
|
];
|
||||||
|
|
||||||
|
type Repository = (typeof repositories)[0];
|
||||||
|
type SqlGeneratorOptions = { targetDir: string };
|
||||||
|
|
||||||
|
class SqlGenerator {
|
||||||
|
private app: INestApplication | null = null;
|
||||||
|
private sqlLogger = new SqlLogger();
|
||||||
|
private results: Record<string, string[]> = {};
|
||||||
|
|
||||||
|
constructor(private options: SqlGeneratorOptions) {}
|
||||||
|
|
||||||
|
async run() {
|
||||||
|
try {
|
||||||
|
await this.setup();
|
||||||
|
for (const Repository of repositories) {
|
||||||
|
await this.process(Repository);
|
||||||
|
}
|
||||||
|
await this.write();
|
||||||
|
this.stats();
|
||||||
|
} finally {
|
||||||
|
await this.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async setup() {
|
||||||
|
await rm(this.options.targetDir, { force: true, recursive: true });
|
||||||
|
await mkdir(this.options.targetDir);
|
||||||
|
|
||||||
|
const moduleFixture = await Test.createTestingModule({
|
||||||
|
imports: [
|
||||||
|
TypeOrmModule.forRoot({
|
||||||
|
...databaseConfig,
|
||||||
|
entities: databaseEntities,
|
||||||
|
logging: ['query'],
|
||||||
|
logger: this.sqlLogger,
|
||||||
|
}),
|
||||||
|
TypeOrmModule.forFeature(databaseEntities),
|
||||||
|
],
|
||||||
|
providers: repositories,
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
this.app = await moduleFixture.createNestApplication().init();
|
||||||
|
}
|
||||||
|
|
||||||
|
async process(Repository: Repository) {
|
||||||
|
if (!this.app) {
|
||||||
|
throw new Error('Not initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: string[] = [`-- NOTE: This file is auto generated by ./sql-generator`];
|
||||||
|
const instance = this.app.get<Repository>(Repository);
|
||||||
|
const properties = Object.getOwnPropertyNames(Repository.prototype) as Array<keyof typeof Repository>;
|
||||||
|
for (const key of properties) {
|
||||||
|
const target = instance[key];
|
||||||
|
if (!(target instanceof Function)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const queries = reflector.get<GenerateSqlQueries[] | undefined>(GENERATE_SQL_KEY, target);
|
||||||
|
if (!queries) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty decorator implies calling with no arguments
|
||||||
|
if (queries.length === 0) {
|
||||||
|
queries.push({ params: [] });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const { name, params } of queries) {
|
||||||
|
let queryLabel = `${Repository.name}.${key}`;
|
||||||
|
if (name) {
|
||||||
|
queryLabel += ` (${name})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sqlLogger.clear();
|
||||||
|
|
||||||
|
// errors still generate sql, which is all we care about
|
||||||
|
await target.apply(instance, params).catch(() => null);
|
||||||
|
|
||||||
|
if (this.sqlLogger.queries.length === 0) {
|
||||||
|
console.warn(`No queries recorded for ${queryLabel}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
data.push([`-- ${queryLabel}`, ...this.sqlLogger.queries].join('\n'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.results[Repository.name] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async write() {
|
||||||
|
for (const [repoName, data] of Object.entries(this.results)) {
|
||||||
|
const filename = repoName.replace(/[A-Z]/g, (letter) => `.${letter.toLowerCase()}`).replace('.', '');
|
||||||
|
const file = join(this.options.targetDir, `${filename}.sql`);
|
||||||
|
await writeFile(file, data.join('\n\n') + '\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private stats() {
|
||||||
|
console.log(`Wrote ${Object.keys(this.results).length} files`);
|
||||||
|
console.log(`Generated ${Object.values(this.results).flat().length} queries`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async close() {
|
||||||
|
if (this.app) {
|
||||||
|
await this.app.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new SqlGenerator({ targetDir: './src/infra/sql' })
|
||||||
|
.run()
|
||||||
|
.then(() => {
|
||||||
|
console.log('Done');
|
||||||
|
process.exit(0);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(error);
|
||||||
|
console.log('Something went wrong');
|
||||||
|
process.exit(1);
|
||||||
|
});
|
27
server/src/infra/sql-generator/sql.logger.ts
Normal file
27
server/src/infra/sql-generator/sql.logger.ts
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { Logger } from 'typeorm';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const { format } = require('sql-formatter');
|
||||||
|
|
||||||
|
export class SqlLogger implements Logger {
|
||||||
|
queries: string[] = [];
|
||||||
|
errors: Array<{ error: string | Error; query: string }> = [];
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this.queries = [];
|
||||||
|
this.errors = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
logQuery(query: string) {
|
||||||
|
this.queries.push(format(query, { language: 'postgresql' }));
|
||||||
|
}
|
||||||
|
|
||||||
|
logQueryError(error: string | Error, query: string) {
|
||||||
|
this.errors.push({ error, query });
|
||||||
|
}
|
||||||
|
|
||||||
|
logQuerySlow() {}
|
||||||
|
logSchemaBuild() {}
|
||||||
|
logMigration() {}
|
||||||
|
log() {}
|
||||||
|
}
|
1
server/src/infra/sql/access.repository.sql
Normal file
1
server/src/infra/sql/access.repository.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
-- NOTE: This file is auto generated by ./sql-generator
|
613
server/src/infra/sql/album.repository.sql
Normal file
613
server/src/infra/sql/album.repository.sql
Normal file
|
@ -0,0 +1,613 @@
|
||||||
|
-- NOTE: This file is auto generated by ./sql-generator
|
||||||
|
|
||||||
|
-- AlbumRepository.getById
|
||||||
|
SELECT DISTINCT
|
||||||
|
"distinctAlias"."AlbumEntity_id" AS "ids_AlbumEntity_id"
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
"AlbumEntity"."id" AS "AlbumEntity_id",
|
||||||
|
"AlbumEntity"."ownerId" AS "AlbumEntity_ownerId",
|
||||||
|
"AlbumEntity"."albumName" AS "AlbumEntity_albumName",
|
||||||
|
"AlbumEntity"."description" AS "AlbumEntity_description",
|
||||||
|
"AlbumEntity"."createdAt" AS "AlbumEntity_createdAt",
|
||||||
|
"AlbumEntity"."updatedAt" AS "AlbumEntity_updatedAt",
|
||||||
|
"AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt",
|
||||||
|
"AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId",
|
||||||
|
"AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."avatarColor" AS "AlbumEntity__AlbumEntity_owner_avatarColor",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."externalPath" AS "AlbumEntity__AlbumEntity_owner_externalPath",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."createdAt" AS "AlbumEntity__AlbumEntity_owner_createdAt",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."memoriesEnabled" AS "AlbumEntity__AlbumEntity_owner_memoriesEnabled",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."id" AS "AlbumEntity__AlbumEntity_sharedUsers_id",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."name" AS "AlbumEntity__AlbumEntity_sharedUsers_name",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."avatarColor" AS "AlbumEntity__AlbumEntity_sharedUsers_avatarColor",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."isAdmin" AS "AlbumEntity__AlbumEntity_sharedUsers_isAdmin",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."email" AS "AlbumEntity__AlbumEntity_sharedUsers_email",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."storageLabel" AS "AlbumEntity__AlbumEntity_sharedUsers_storageLabel",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."externalPath" AS "AlbumEntity__AlbumEntity_sharedUsers_externalPath",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."oauthId" AS "AlbumEntity__AlbumEntity_sharedUsers_oauthId",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."profileImagePath" AS "AlbumEntity__AlbumEntity_sharedUsers_profileImagePath",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_sharedUsers_shouldChangePassword",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."createdAt" AS "AlbumEntity__AlbumEntity_sharedUsers_createdAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."deletedAt" AS "AlbumEntity__AlbumEntity_sharedUsers_deletedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."updatedAt" AS "AlbumEntity__AlbumEntity_sharedUsers_updatedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."memoriesEnabled" AS "AlbumEntity__AlbumEntity_sharedUsers_memoriesEnabled",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."id" AS "AlbumEntity__AlbumEntity_sharedLinks_id",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."description" AS "AlbumEntity__AlbumEntity_sharedLinks_description",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."password" AS "AlbumEntity__AlbumEntity_sharedLinks_password",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."userId" AS "AlbumEntity__AlbumEntity_sharedLinks_userId",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."key" AS "AlbumEntity__AlbumEntity_sharedLinks_key",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."type" AS "AlbumEntity__AlbumEntity_sharedLinks_type",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."createdAt" AS "AlbumEntity__AlbumEntity_sharedLinks_createdAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."expiresAt" AS "AlbumEntity__AlbumEntity_sharedLinks_expiresAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."allowUpload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowUpload",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."allowDownload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowDownload",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."showExif" AS "AlbumEntity__AlbumEntity_sharedLinks_showExif",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."albumId" AS "AlbumEntity__AlbumEntity_sharedLinks_albumId"
|
||||||
|
FROM
|
||||||
|
"albums" "AlbumEntity"
|
||||||
|
LEFT JOIN "users" "AlbumEntity__AlbumEntity_owner" ON "AlbumEntity__AlbumEntity_owner"."id" = "AlbumEntity"."ownerId"
|
||||||
|
AND (
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
LEFT JOIN "albums_shared_users_users" "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers" ON "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."albumsId" = "AlbumEntity"."id"
|
||||||
|
LEFT JOIN "users" "AlbumEntity__AlbumEntity_sharedUsers" ON "AlbumEntity__AlbumEntity_sharedUsers"."id" = "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."usersId"
|
||||||
|
AND (
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
LEFT JOIN "shared_links" "AlbumEntity__AlbumEntity_sharedLinks" ON "AlbumEntity__AlbumEntity_sharedLinks"."albumId" = "AlbumEntity"."id"
|
||||||
|
WHERE
|
||||||
|
(("AlbumEntity"."id" = $1))
|
||||||
|
AND ("AlbumEntity"."deletedAt" IS NULL)
|
||||||
|
) "distinctAlias"
|
||||||
|
ORDER BY
|
||||||
|
"AlbumEntity_id" ASC
|
||||||
|
LIMIT
|
||||||
|
1
|
||||||
|
|
||||||
|
-- AlbumRepository.getByIds
|
||||||
|
SELECT
|
||||||
|
"AlbumEntity"."id" AS "AlbumEntity_id",
|
||||||
|
"AlbumEntity"."ownerId" AS "AlbumEntity_ownerId",
|
||||||
|
"AlbumEntity"."albumName" AS "AlbumEntity_albumName",
|
||||||
|
"AlbumEntity"."description" AS "AlbumEntity_description",
|
||||||
|
"AlbumEntity"."createdAt" AS "AlbumEntity_createdAt",
|
||||||
|
"AlbumEntity"."updatedAt" AS "AlbumEntity_updatedAt",
|
||||||
|
"AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt",
|
||||||
|
"AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId",
|
||||||
|
"AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."avatarColor" AS "AlbumEntity__AlbumEntity_owner_avatarColor",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."externalPath" AS "AlbumEntity__AlbumEntity_owner_externalPath",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."createdAt" AS "AlbumEntity__AlbumEntity_owner_createdAt",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."memoriesEnabled" AS "AlbumEntity__AlbumEntity_owner_memoriesEnabled",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."id" AS "AlbumEntity__AlbumEntity_sharedUsers_id",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."name" AS "AlbumEntity__AlbumEntity_sharedUsers_name",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."avatarColor" AS "AlbumEntity__AlbumEntity_sharedUsers_avatarColor",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."isAdmin" AS "AlbumEntity__AlbumEntity_sharedUsers_isAdmin",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."email" AS "AlbumEntity__AlbumEntity_sharedUsers_email",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."storageLabel" AS "AlbumEntity__AlbumEntity_sharedUsers_storageLabel",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."externalPath" AS "AlbumEntity__AlbumEntity_sharedUsers_externalPath",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."oauthId" AS "AlbumEntity__AlbumEntity_sharedUsers_oauthId",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."profileImagePath" AS "AlbumEntity__AlbumEntity_sharedUsers_profileImagePath",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_sharedUsers_shouldChangePassword",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."createdAt" AS "AlbumEntity__AlbumEntity_sharedUsers_createdAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."deletedAt" AS "AlbumEntity__AlbumEntity_sharedUsers_deletedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."updatedAt" AS "AlbumEntity__AlbumEntity_sharedUsers_updatedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."memoriesEnabled" AS "AlbumEntity__AlbumEntity_sharedUsers_memoriesEnabled"
|
||||||
|
FROM
|
||||||
|
"albums" "AlbumEntity"
|
||||||
|
LEFT JOIN "users" "AlbumEntity__AlbumEntity_owner" ON "AlbumEntity__AlbumEntity_owner"."id" = "AlbumEntity"."ownerId"
|
||||||
|
AND (
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
LEFT JOIN "albums_shared_users_users" "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers" ON "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."albumsId" = "AlbumEntity"."id"
|
||||||
|
LEFT JOIN "users" "AlbumEntity__AlbumEntity_sharedUsers" ON "AlbumEntity__AlbumEntity_sharedUsers"."id" = "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."usersId"
|
||||||
|
AND (
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
(("AlbumEntity"."id" IN ($1)))
|
||||||
|
AND ("AlbumEntity"."deletedAt" IS NULL)
|
||||||
|
|
||||||
|
-- AlbumRepository.getByAssetId
|
||||||
|
SELECT
|
||||||
|
"AlbumEntity"."id" AS "AlbumEntity_id",
|
||||||
|
"AlbumEntity"."ownerId" AS "AlbumEntity_ownerId",
|
||||||
|
"AlbumEntity"."albumName" AS "AlbumEntity_albumName",
|
||||||
|
"AlbumEntity"."description" AS "AlbumEntity_description",
|
||||||
|
"AlbumEntity"."createdAt" AS "AlbumEntity_createdAt",
|
||||||
|
"AlbumEntity"."updatedAt" AS "AlbumEntity_updatedAt",
|
||||||
|
"AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt",
|
||||||
|
"AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId",
|
||||||
|
"AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."avatarColor" AS "AlbumEntity__AlbumEntity_owner_avatarColor",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."externalPath" AS "AlbumEntity__AlbumEntity_owner_externalPath",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."createdAt" AS "AlbumEntity__AlbumEntity_owner_createdAt",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."memoriesEnabled" AS "AlbumEntity__AlbumEntity_owner_memoriesEnabled",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."id" AS "AlbumEntity__AlbumEntity_sharedUsers_id",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."name" AS "AlbumEntity__AlbumEntity_sharedUsers_name",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."avatarColor" AS "AlbumEntity__AlbumEntity_sharedUsers_avatarColor",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."isAdmin" AS "AlbumEntity__AlbumEntity_sharedUsers_isAdmin",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."email" AS "AlbumEntity__AlbumEntity_sharedUsers_email",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."storageLabel" AS "AlbumEntity__AlbumEntity_sharedUsers_storageLabel",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."externalPath" AS "AlbumEntity__AlbumEntity_sharedUsers_externalPath",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."oauthId" AS "AlbumEntity__AlbumEntity_sharedUsers_oauthId",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."profileImagePath" AS "AlbumEntity__AlbumEntity_sharedUsers_profileImagePath",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_sharedUsers_shouldChangePassword",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."createdAt" AS "AlbumEntity__AlbumEntity_sharedUsers_createdAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."deletedAt" AS "AlbumEntity__AlbumEntity_sharedUsers_deletedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."updatedAt" AS "AlbumEntity__AlbumEntity_sharedUsers_updatedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."memoriesEnabled" AS "AlbumEntity__AlbumEntity_sharedUsers_memoriesEnabled"
|
||||||
|
FROM
|
||||||
|
"albums" "AlbumEntity"
|
||||||
|
LEFT JOIN "users" "AlbumEntity__AlbumEntity_owner" ON "AlbumEntity__AlbumEntity_owner"."id" = "AlbumEntity"."ownerId"
|
||||||
|
AND (
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
LEFT JOIN "albums_shared_users_users" "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers" ON "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."albumsId" = "AlbumEntity"."id"
|
||||||
|
LEFT JOIN "users" "AlbumEntity__AlbumEntity_sharedUsers" ON "AlbumEntity__AlbumEntity_sharedUsers"."id" = "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."usersId"
|
||||||
|
AND (
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
LEFT JOIN "albums_assets_assets" "AlbumEntity_AlbumEntity__AlbumEntity_assets" ON "AlbumEntity_AlbumEntity__AlbumEntity_assets"."albumsId" = "AlbumEntity"."id"
|
||||||
|
LEFT JOIN "assets" "AlbumEntity__AlbumEntity_assets" ON "AlbumEntity__AlbumEntity_assets"."id" = "AlbumEntity_AlbumEntity__AlbumEntity_assets"."assetsId"
|
||||||
|
AND (
|
||||||
|
"AlbumEntity__AlbumEntity_assets"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"AlbumEntity"."ownerId" = $1
|
||||||
|
AND "AlbumEntity__AlbumEntity_assets"."id" = $2
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."id" = $3
|
||||||
|
AND "AlbumEntity__AlbumEntity_assets"."id" = $4
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND ("AlbumEntity"."deletedAt" IS NULL)
|
||||||
|
ORDER BY
|
||||||
|
"AlbumEntity"."createdAt" DESC
|
||||||
|
|
||||||
|
-- AlbumRepository.getMetadataForIds
|
||||||
|
SELECT
|
||||||
|
"album"."id" AS "album_id",
|
||||||
|
MIN("assets"."fileCreatedAt") AS "start_date",
|
||||||
|
MAX("assets"."fileCreatedAt") AS "end_date",
|
||||||
|
COUNT("album_assets"."assetsId") AS "asset_count"
|
||||||
|
FROM
|
||||||
|
"albums" "album"
|
||||||
|
LEFT JOIN "albums_assets_assets" "album_assets" ON "album_assets"."albumsId" = "album"."id"
|
||||||
|
LEFT JOIN "assets" "assets" ON "assets"."id" = "album_assets"."assetsId"
|
||||||
|
AND "assets"."deletedAt" IS NULL
|
||||||
|
WHERE
|
||||||
|
("album"."id" IN ($1))
|
||||||
|
AND ("album"."deletedAt" IS NULL)
|
||||||
|
GROUP BY
|
||||||
|
"album"."id"
|
||||||
|
|
||||||
|
-- AlbumRepository.getInvalidThumbnail
|
||||||
|
SELECT
|
||||||
|
"albums"."id" AS "albums_id"
|
||||||
|
FROM
|
||||||
|
"albums" "albums"
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
"albums"."albumThumbnailAssetId" IS NULL
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT
|
||||||
|
1
|
||||||
|
FROM
|
||||||
|
"albums_assets_assets" "albums_assets"
|
||||||
|
WHERE
|
||||||
|
"albums"."id" = "albums_assets"."albumsId"
|
||||||
|
)
|
||||||
|
OR "albums"."albumThumbnailAssetId" IS NOT NULL
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT
|
||||||
|
1
|
||||||
|
FROM
|
||||||
|
"albums_assets_assets" "albums_assets"
|
||||||
|
WHERE
|
||||||
|
"albums"."id" = "albums_assets"."albumsId"
|
||||||
|
AND "albums"."albumThumbnailAssetId" = "albums_assets"."assetsId"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND ("albums"."deletedAt" IS NULL)
|
||||||
|
|
||||||
|
-- AlbumRepository.getOwned
|
||||||
|
SELECT
|
||||||
|
"AlbumEntity"."id" AS "AlbumEntity_id",
|
||||||
|
"AlbumEntity"."ownerId" AS "AlbumEntity_ownerId",
|
||||||
|
"AlbumEntity"."albumName" AS "AlbumEntity_albumName",
|
||||||
|
"AlbumEntity"."description" AS "AlbumEntity_description",
|
||||||
|
"AlbumEntity"."createdAt" AS "AlbumEntity_createdAt",
|
||||||
|
"AlbumEntity"."updatedAt" AS "AlbumEntity_updatedAt",
|
||||||
|
"AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt",
|
||||||
|
"AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId",
|
||||||
|
"AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."id" AS "AlbumEntity__AlbumEntity_sharedUsers_id",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."name" AS "AlbumEntity__AlbumEntity_sharedUsers_name",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."avatarColor" AS "AlbumEntity__AlbumEntity_sharedUsers_avatarColor",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."isAdmin" AS "AlbumEntity__AlbumEntity_sharedUsers_isAdmin",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."email" AS "AlbumEntity__AlbumEntity_sharedUsers_email",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."storageLabel" AS "AlbumEntity__AlbumEntity_sharedUsers_storageLabel",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."externalPath" AS "AlbumEntity__AlbumEntity_sharedUsers_externalPath",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."oauthId" AS "AlbumEntity__AlbumEntity_sharedUsers_oauthId",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."profileImagePath" AS "AlbumEntity__AlbumEntity_sharedUsers_profileImagePath",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_sharedUsers_shouldChangePassword",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."createdAt" AS "AlbumEntity__AlbumEntity_sharedUsers_createdAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."deletedAt" AS "AlbumEntity__AlbumEntity_sharedUsers_deletedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."updatedAt" AS "AlbumEntity__AlbumEntity_sharedUsers_updatedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."memoriesEnabled" AS "AlbumEntity__AlbumEntity_sharedUsers_memoriesEnabled",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."id" AS "AlbumEntity__AlbumEntity_sharedLinks_id",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."description" AS "AlbumEntity__AlbumEntity_sharedLinks_description",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."password" AS "AlbumEntity__AlbumEntity_sharedLinks_password",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."userId" AS "AlbumEntity__AlbumEntity_sharedLinks_userId",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."key" AS "AlbumEntity__AlbumEntity_sharedLinks_key",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."type" AS "AlbumEntity__AlbumEntity_sharedLinks_type",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."createdAt" AS "AlbumEntity__AlbumEntity_sharedLinks_createdAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."expiresAt" AS "AlbumEntity__AlbumEntity_sharedLinks_expiresAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."allowUpload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowUpload",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."allowDownload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowDownload",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."showExif" AS "AlbumEntity__AlbumEntity_sharedLinks_showExif",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."albumId" AS "AlbumEntity__AlbumEntity_sharedLinks_albumId",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."avatarColor" AS "AlbumEntity__AlbumEntity_owner_avatarColor",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."externalPath" AS "AlbumEntity__AlbumEntity_owner_externalPath",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."createdAt" AS "AlbumEntity__AlbumEntity_owner_createdAt",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."memoriesEnabled" AS "AlbumEntity__AlbumEntity_owner_memoriesEnabled"
|
||||||
|
FROM
|
||||||
|
"albums" "AlbumEntity"
|
||||||
|
LEFT JOIN "albums_shared_users_users" "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers" ON "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."albumsId" = "AlbumEntity"."id"
|
||||||
|
LEFT JOIN "users" "AlbumEntity__AlbumEntity_sharedUsers" ON "AlbumEntity__AlbumEntity_sharedUsers"."id" = "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."usersId"
|
||||||
|
AND (
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
LEFT JOIN "shared_links" "AlbumEntity__AlbumEntity_sharedLinks" ON "AlbumEntity__AlbumEntity_sharedLinks"."albumId" = "AlbumEntity"."id"
|
||||||
|
LEFT JOIN "users" "AlbumEntity__AlbumEntity_owner" ON "AlbumEntity__AlbumEntity_owner"."id" = "AlbumEntity"."ownerId"
|
||||||
|
AND (
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
(("AlbumEntity"."ownerId" = $1))
|
||||||
|
AND ("AlbumEntity"."deletedAt" IS NULL)
|
||||||
|
ORDER BY
|
||||||
|
"AlbumEntity"."createdAt" DESC
|
||||||
|
|
||||||
|
-- AlbumRepository.getShared
|
||||||
|
SELECT
|
||||||
|
"AlbumEntity"."id" AS "AlbumEntity_id",
|
||||||
|
"AlbumEntity"."ownerId" AS "AlbumEntity_ownerId",
|
||||||
|
"AlbumEntity"."albumName" AS "AlbumEntity_albumName",
|
||||||
|
"AlbumEntity"."description" AS "AlbumEntity_description",
|
||||||
|
"AlbumEntity"."createdAt" AS "AlbumEntity_createdAt",
|
||||||
|
"AlbumEntity"."updatedAt" AS "AlbumEntity_updatedAt",
|
||||||
|
"AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt",
|
||||||
|
"AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId",
|
||||||
|
"AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."id" AS "AlbumEntity__AlbumEntity_sharedUsers_id",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."name" AS "AlbumEntity__AlbumEntity_sharedUsers_name",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."avatarColor" AS "AlbumEntity__AlbumEntity_sharedUsers_avatarColor",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."isAdmin" AS "AlbumEntity__AlbumEntity_sharedUsers_isAdmin",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."email" AS "AlbumEntity__AlbumEntity_sharedUsers_email",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."storageLabel" AS "AlbumEntity__AlbumEntity_sharedUsers_storageLabel",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."externalPath" AS "AlbumEntity__AlbumEntity_sharedUsers_externalPath",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."oauthId" AS "AlbumEntity__AlbumEntity_sharedUsers_oauthId",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."profileImagePath" AS "AlbumEntity__AlbumEntity_sharedUsers_profileImagePath",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_sharedUsers_shouldChangePassword",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."createdAt" AS "AlbumEntity__AlbumEntity_sharedUsers_createdAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."deletedAt" AS "AlbumEntity__AlbumEntity_sharedUsers_deletedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."updatedAt" AS "AlbumEntity__AlbumEntity_sharedUsers_updatedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."memoriesEnabled" AS "AlbumEntity__AlbumEntity_sharedUsers_memoriesEnabled",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."id" AS "AlbumEntity__AlbumEntity_sharedLinks_id",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."description" AS "AlbumEntity__AlbumEntity_sharedLinks_description",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."password" AS "AlbumEntity__AlbumEntity_sharedLinks_password",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."userId" AS "AlbumEntity__AlbumEntity_sharedLinks_userId",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."key" AS "AlbumEntity__AlbumEntity_sharedLinks_key",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."type" AS "AlbumEntity__AlbumEntity_sharedLinks_type",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."createdAt" AS "AlbumEntity__AlbumEntity_sharedLinks_createdAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."expiresAt" AS "AlbumEntity__AlbumEntity_sharedLinks_expiresAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."allowUpload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowUpload",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."allowDownload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowDownload",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."showExif" AS "AlbumEntity__AlbumEntity_sharedLinks_showExif",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."albumId" AS "AlbumEntity__AlbumEntity_sharedLinks_albumId",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."avatarColor" AS "AlbumEntity__AlbumEntity_owner_avatarColor",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."externalPath" AS "AlbumEntity__AlbumEntity_owner_externalPath",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."createdAt" AS "AlbumEntity__AlbumEntity_owner_createdAt",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."memoriesEnabled" AS "AlbumEntity__AlbumEntity_owner_memoriesEnabled"
|
||||||
|
FROM
|
||||||
|
"albums" "AlbumEntity"
|
||||||
|
LEFT JOIN "albums_shared_users_users" "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers" ON "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."albumsId" = "AlbumEntity"."id"
|
||||||
|
LEFT JOIN "users" "AlbumEntity__AlbumEntity_sharedUsers" ON "AlbumEntity__AlbumEntity_sharedUsers"."id" = "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."usersId"
|
||||||
|
AND (
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
LEFT JOIN "shared_links" "AlbumEntity__AlbumEntity_sharedLinks" ON "AlbumEntity__AlbumEntity_sharedLinks"."albumId" = "AlbumEntity"."id"
|
||||||
|
LEFT JOIN "users" "AlbumEntity__AlbumEntity_owner" ON "AlbumEntity__AlbumEntity_owner"."id" = "AlbumEntity"."ownerId"
|
||||||
|
AND (
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(
|
||||||
|
("AlbumEntity__AlbumEntity_sharedUsers"."id" = $1)
|
||||||
|
OR (
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."userId" = $2
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
"AlbumEntity"."ownerId" = $3
|
||||||
|
AND NOT (
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."id" IS NULL
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND ("AlbumEntity"."deletedAt" IS NULL)
|
||||||
|
ORDER BY
|
||||||
|
"AlbumEntity"."createdAt" DESC
|
||||||
|
|
||||||
|
-- AlbumRepository.getNotShared
|
||||||
|
SELECT
|
||||||
|
"AlbumEntity"."id" AS "AlbumEntity_id",
|
||||||
|
"AlbumEntity"."ownerId" AS "AlbumEntity_ownerId",
|
||||||
|
"AlbumEntity"."albumName" AS "AlbumEntity_albumName",
|
||||||
|
"AlbumEntity"."description" AS "AlbumEntity_description",
|
||||||
|
"AlbumEntity"."createdAt" AS "AlbumEntity_createdAt",
|
||||||
|
"AlbumEntity"."updatedAt" AS "AlbumEntity_updatedAt",
|
||||||
|
"AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt",
|
||||||
|
"AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId",
|
||||||
|
"AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."id" AS "AlbumEntity__AlbumEntity_sharedUsers_id",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."name" AS "AlbumEntity__AlbumEntity_sharedUsers_name",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."avatarColor" AS "AlbumEntity__AlbumEntity_sharedUsers_avatarColor",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."isAdmin" AS "AlbumEntity__AlbumEntity_sharedUsers_isAdmin",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."email" AS "AlbumEntity__AlbumEntity_sharedUsers_email",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."storageLabel" AS "AlbumEntity__AlbumEntity_sharedUsers_storageLabel",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."externalPath" AS "AlbumEntity__AlbumEntity_sharedUsers_externalPath",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."oauthId" AS "AlbumEntity__AlbumEntity_sharedUsers_oauthId",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."profileImagePath" AS "AlbumEntity__AlbumEntity_sharedUsers_profileImagePath",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_sharedUsers_shouldChangePassword",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."createdAt" AS "AlbumEntity__AlbumEntity_sharedUsers_createdAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."deletedAt" AS "AlbumEntity__AlbumEntity_sharedUsers_deletedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."updatedAt" AS "AlbumEntity__AlbumEntity_sharedUsers_updatedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."memoriesEnabled" AS "AlbumEntity__AlbumEntity_sharedUsers_memoriesEnabled",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."id" AS "AlbumEntity__AlbumEntity_sharedLinks_id",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."description" AS "AlbumEntity__AlbumEntity_sharedLinks_description",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."password" AS "AlbumEntity__AlbumEntity_sharedLinks_password",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."userId" AS "AlbumEntity__AlbumEntity_sharedLinks_userId",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."key" AS "AlbumEntity__AlbumEntity_sharedLinks_key",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."type" AS "AlbumEntity__AlbumEntity_sharedLinks_type",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."createdAt" AS "AlbumEntity__AlbumEntity_sharedLinks_createdAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."expiresAt" AS "AlbumEntity__AlbumEntity_sharedLinks_expiresAt",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."allowUpload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowUpload",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."allowDownload" AS "AlbumEntity__AlbumEntity_sharedLinks_allowDownload",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."showExif" AS "AlbumEntity__AlbumEntity_sharedLinks_showExif",
|
||||||
|
"AlbumEntity__AlbumEntity_sharedLinks"."albumId" AS "AlbumEntity__AlbumEntity_sharedLinks_albumId",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."avatarColor" AS "AlbumEntity__AlbumEntity_owner_avatarColor",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."externalPath" AS "AlbumEntity__AlbumEntity_owner_externalPath",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."createdAt" AS "AlbumEntity__AlbumEntity_owner_createdAt",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."memoriesEnabled" AS "AlbumEntity__AlbumEntity_owner_memoriesEnabled"
|
||||||
|
FROM
|
||||||
|
"albums" "AlbumEntity"
|
||||||
|
LEFT JOIN "albums_shared_users_users" "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers" ON "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."albumsId" = "AlbumEntity"."id"
|
||||||
|
LEFT JOIN "users" "AlbumEntity__AlbumEntity_sharedUsers" ON "AlbumEntity__AlbumEntity_sharedUsers"."id" = "AlbumEntity_AlbumEntity__AlbumEntity_sharedUsers"."usersId"
|
||||||
|
AND (
|
||||||
|
"AlbumEntity__AlbumEntity_sharedUsers"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
LEFT JOIN "shared_links" "AlbumEntity__AlbumEntity_sharedLinks" ON "AlbumEntity__AlbumEntity_sharedLinks"."albumId" = "AlbumEntity"."id"
|
||||||
|
LEFT JOIN "users" "AlbumEntity__AlbumEntity_owner" ON "AlbumEntity__AlbumEntity_owner"."id" = "AlbumEntity"."ownerId"
|
||||||
|
AND (
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"AlbumEntity"."ownerId" = $1
|
||||||
|
AND "AlbumEntity__AlbumEntity_sharedUsers"."id" IS NULL
|
||||||
|
AND "AlbumEntity__AlbumEntity_sharedLinks"."id" IS NULL
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND ("AlbumEntity"."deletedAt" IS NULL)
|
||||||
|
ORDER BY
|
||||||
|
"AlbumEntity"."createdAt" DESC
|
||||||
|
|
||||||
|
-- AlbumRepository.getAll
|
||||||
|
SELECT
|
||||||
|
"AlbumEntity"."id" AS "AlbumEntity_id",
|
||||||
|
"AlbumEntity"."ownerId" AS "AlbumEntity_ownerId",
|
||||||
|
"AlbumEntity"."albumName" AS "AlbumEntity_albumName",
|
||||||
|
"AlbumEntity"."description" AS "AlbumEntity_description",
|
||||||
|
"AlbumEntity"."createdAt" AS "AlbumEntity_createdAt",
|
||||||
|
"AlbumEntity"."updatedAt" AS "AlbumEntity_updatedAt",
|
||||||
|
"AlbumEntity"."deletedAt" AS "AlbumEntity_deletedAt",
|
||||||
|
"AlbumEntity"."albumThumbnailAssetId" AS "AlbumEntity_albumThumbnailAssetId",
|
||||||
|
"AlbumEntity"."isActivityEnabled" AS "AlbumEntity_isActivityEnabled",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."id" AS "AlbumEntity__AlbumEntity_owner_id",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."name" AS "AlbumEntity__AlbumEntity_owner_name",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."avatarColor" AS "AlbumEntity__AlbumEntity_owner_avatarColor",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."isAdmin" AS "AlbumEntity__AlbumEntity_owner_isAdmin",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."email" AS "AlbumEntity__AlbumEntity_owner_email",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."storageLabel" AS "AlbumEntity__AlbumEntity_owner_storageLabel",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."externalPath" AS "AlbumEntity__AlbumEntity_owner_externalPath",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."oauthId" AS "AlbumEntity__AlbumEntity_owner_oauthId",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."profileImagePath" AS "AlbumEntity__AlbumEntity_owner_profileImagePath",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."shouldChangePassword" AS "AlbumEntity__AlbumEntity_owner_shouldChangePassword",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."createdAt" AS "AlbumEntity__AlbumEntity_owner_createdAt",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."deletedAt" AS "AlbumEntity__AlbumEntity_owner_deletedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."updatedAt" AS "AlbumEntity__AlbumEntity_owner_updatedAt",
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."memoriesEnabled" AS "AlbumEntity__AlbumEntity_owner_memoriesEnabled"
|
||||||
|
FROM
|
||||||
|
"albums" "AlbumEntity"
|
||||||
|
LEFT JOIN "users" "AlbumEntity__AlbumEntity_owner" ON "AlbumEntity__AlbumEntity_owner"."id" = "AlbumEntity"."ownerId"
|
||||||
|
AND (
|
||||||
|
"AlbumEntity__AlbumEntity_owner"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
"AlbumEntity"."deletedAt" IS NULL
|
||||||
|
|
||||||
|
-- AlbumRepository.removeAssets
|
||||||
|
DELETE FROM "albums_assets_assets"
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
"albumsId" = $1
|
||||||
|
AND "assetsId" IN ($2)
|
||||||
|
)
|
||||||
|
|
||||||
|
-- AlbumRepository.getAssetIds
|
||||||
|
SELECT
|
||||||
|
"albums_assets"."assetsId" AS "assetId"
|
||||||
|
FROM
|
||||||
|
"albums_assets_assets" "albums_assets"
|
||||||
|
WHERE
|
||||||
|
"albums_assets"."albumsId" = $1
|
||||||
|
AND "albums_assets"."assetsId" IN ($2)
|
||||||
|
|
||||||
|
-- AlbumRepository.getAssetIds (no assets)
|
||||||
|
SELECT
|
||||||
|
"albums_assets"."assetsId" AS "assetId"
|
||||||
|
FROM
|
||||||
|
"albums_assets_assets" "albums_assets"
|
||||||
|
WHERE
|
||||||
|
"albums_assets"."albumsId" = $1
|
||||||
|
|
||||||
|
-- AlbumRepository.hasAsset
|
||||||
|
SELECT
|
||||||
|
1 AS "row_exists"
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
1 AS dummy_column
|
||||||
|
) "dummy_table"
|
||||||
|
WHERE
|
||||||
|
EXISTS (
|
||||||
|
SELECT
|
||||||
|
1
|
||||||
|
FROM
|
||||||
|
"albums" "AlbumEntity"
|
||||||
|
LEFT JOIN "albums_assets_assets" "AlbumEntity_AlbumEntity__AlbumEntity_assets" ON "AlbumEntity_AlbumEntity__AlbumEntity_assets"."albumsId" = "AlbumEntity"."id"
|
||||||
|
LEFT JOIN "assets" "AlbumEntity__AlbumEntity_assets" ON "AlbumEntity__AlbumEntity_assets"."id" = "AlbumEntity_AlbumEntity__AlbumEntity_assets"."assetsId"
|
||||||
|
AND (
|
||||||
|
"AlbumEntity__AlbumEntity_assets"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"AlbumEntity"."id" = $1
|
||||||
|
AND "AlbumEntity__AlbumEntity_assets"."id" = $2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND ("AlbumEntity"."deletedAt" IS NULL)
|
||||||
|
)
|
||||||
|
LIMIT
|
||||||
|
1
|
||||||
|
|
||||||
|
-- AlbumRepository.addAssets
|
||||||
|
INSERT INTO
|
||||||
|
"albums_assets_assets" ("albumsId", "assetsId")
|
||||||
|
VALUES
|
||||||
|
($1, $2)
|
||||||
|
|
||||||
|
-- AlbumRepository.updateThumbnails
|
||||||
|
UPDATE "albums"
|
||||||
|
SET
|
||||||
|
"albumThumbnailAssetId" = (
|
||||||
|
SELECT
|
||||||
|
"albums_assets2"."assetsId"
|
||||||
|
FROM
|
||||||
|
"assets" "assets",
|
||||||
|
"albums_assets_assets" "albums_assets2"
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
"albums_assets2"."assetsId" = "assets"."id"
|
||||||
|
AND "albums_assets2"."albumsId" = "albums"."id"
|
||||||
|
)
|
||||||
|
AND ("assets"."deletedAt" IS NULL)
|
||||||
|
ORDER BY
|
||||||
|
"assets"."fileCreatedAt" DESC
|
||||||
|
LIMIT
|
||||||
|
1
|
||||||
|
),
|
||||||
|
"updatedAt" = CURRENT_TIMESTAMP
|
||||||
|
WHERE
|
||||||
|
"albums"."albumThumbnailAssetId" IS NULL
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT
|
||||||
|
1
|
||||||
|
FROM
|
||||||
|
"albums_assets_assets" "albums_assets"
|
||||||
|
WHERE
|
||||||
|
"albums"."id" = "albums_assets"."albumsId"
|
||||||
|
)
|
||||||
|
OR "albums"."albumThumbnailAssetId" IS NOT NULL
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT
|
||||||
|
1
|
||||||
|
FROM
|
||||||
|
"albums_assets_assets" "albums_assets"
|
||||||
|
WHERE
|
||||||
|
"albums"."id" = "albums_assets"."albumsId"
|
||||||
|
AND "albums"."albumThumbnailAssetId" = "albums_assets"."assetsId"
|
||||||
|
)
|
69
server/src/infra/sql/api.key.repository.sql
Normal file
69
server/src/infra/sql/api.key.repository.sql
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
-- NOTE: This file is auto generated by ./sql-generator
|
||||||
|
|
||||||
|
-- ApiKeyRepository.getKey
|
||||||
|
SELECT DISTINCT
|
||||||
|
"distinctAlias"."APIKeyEntity_id" AS "ids_APIKeyEntity_id"
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
"APIKeyEntity"."id" AS "APIKeyEntity_id",
|
||||||
|
"APIKeyEntity"."key" AS "APIKeyEntity_key",
|
||||||
|
"APIKeyEntity"."userId" AS "APIKeyEntity_userId",
|
||||||
|
"APIKeyEntity__APIKeyEntity_user"."id" AS "APIKeyEntity__APIKeyEntity_user_id",
|
||||||
|
"APIKeyEntity__APIKeyEntity_user"."name" AS "APIKeyEntity__APIKeyEntity_user_name",
|
||||||
|
"APIKeyEntity__APIKeyEntity_user"."avatarColor" AS "APIKeyEntity__APIKeyEntity_user_avatarColor",
|
||||||
|
"APIKeyEntity__APIKeyEntity_user"."isAdmin" AS "APIKeyEntity__APIKeyEntity_user_isAdmin",
|
||||||
|
"APIKeyEntity__APIKeyEntity_user"."email" AS "APIKeyEntity__APIKeyEntity_user_email",
|
||||||
|
"APIKeyEntity__APIKeyEntity_user"."storageLabel" AS "APIKeyEntity__APIKeyEntity_user_storageLabel",
|
||||||
|
"APIKeyEntity__APIKeyEntity_user"."externalPath" AS "APIKeyEntity__APIKeyEntity_user_externalPath",
|
||||||
|
"APIKeyEntity__APIKeyEntity_user"."oauthId" AS "APIKeyEntity__APIKeyEntity_user_oauthId",
|
||||||
|
"APIKeyEntity__APIKeyEntity_user"."profileImagePath" AS "APIKeyEntity__APIKeyEntity_user_profileImagePath",
|
||||||
|
"APIKeyEntity__APIKeyEntity_user"."shouldChangePassword" AS "APIKeyEntity__APIKeyEntity_user_shouldChangePassword",
|
||||||
|
"APIKeyEntity__APIKeyEntity_user"."createdAt" AS "APIKeyEntity__APIKeyEntity_user_createdAt",
|
||||||
|
"APIKeyEntity__APIKeyEntity_user"."deletedAt" AS "APIKeyEntity__APIKeyEntity_user_deletedAt",
|
||||||
|
"APIKeyEntity__APIKeyEntity_user"."updatedAt" AS "APIKeyEntity__APIKeyEntity_user_updatedAt",
|
||||||
|
"APIKeyEntity__APIKeyEntity_user"."memoriesEnabled" AS "APIKeyEntity__APIKeyEntity_user_memoriesEnabled"
|
||||||
|
FROM
|
||||||
|
"api_keys" "APIKeyEntity"
|
||||||
|
LEFT JOIN "users" "APIKeyEntity__APIKeyEntity_user" ON "APIKeyEntity__APIKeyEntity_user"."id" = "APIKeyEntity"."userId"
|
||||||
|
AND (
|
||||||
|
"APIKeyEntity__APIKeyEntity_user"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
("APIKeyEntity"."key" = $1)
|
||||||
|
) "distinctAlias"
|
||||||
|
ORDER BY
|
||||||
|
"APIKeyEntity_id" ASC
|
||||||
|
LIMIT
|
||||||
|
1
|
||||||
|
|
||||||
|
-- ApiKeyRepository.getById
|
||||||
|
SELECT
|
||||||
|
"APIKeyEntity"."id" AS "APIKeyEntity_id",
|
||||||
|
"APIKeyEntity"."name" AS "APIKeyEntity_name",
|
||||||
|
"APIKeyEntity"."userId" AS "APIKeyEntity_userId",
|
||||||
|
"APIKeyEntity"."createdAt" AS "APIKeyEntity_createdAt",
|
||||||
|
"APIKeyEntity"."updatedAt" AS "APIKeyEntity_updatedAt"
|
||||||
|
FROM
|
||||||
|
"api_keys" "APIKeyEntity"
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
"APIKeyEntity"."userId" = $1
|
||||||
|
AND "APIKeyEntity"."id" = $2
|
||||||
|
)
|
||||||
|
LIMIT
|
||||||
|
1
|
||||||
|
|
||||||
|
-- ApiKeyRepository.getByUserId
|
||||||
|
SELECT
|
||||||
|
"APIKeyEntity"."id" AS "APIKeyEntity_id",
|
||||||
|
"APIKeyEntity"."name" AS "APIKeyEntity_name",
|
||||||
|
"APIKeyEntity"."userId" AS "APIKeyEntity_userId",
|
||||||
|
"APIKeyEntity"."createdAt" AS "APIKeyEntity_createdAt",
|
||||||
|
"APIKeyEntity"."updatedAt" AS "APIKeyEntity_updatedAt"
|
||||||
|
FROM
|
||||||
|
"api_keys" "APIKeyEntity"
|
||||||
|
WHERE
|
||||||
|
("APIKeyEntity"."userId" = $1)
|
||||||
|
ORDER BY
|
||||||
|
"APIKeyEntity"."createdAt" DESC
|
614
server/src/infra/sql/asset.repository.sql
Normal file
614
server/src/infra/sql/asset.repository.sql
Normal file
|
@ -0,0 +1,614 @@
|
||||||
|
-- NOTE: This file is auto generated by ./sql-generator
|
||||||
|
|
||||||
|
-- AssetRepository.getByDate
|
||||||
|
SELECT
|
||||||
|
"AssetEntity"."id" AS "AssetEntity_id",
|
||||||
|
"AssetEntity"."deviceAssetId" AS "AssetEntity_deviceAssetId",
|
||||||
|
"AssetEntity"."ownerId" AS "AssetEntity_ownerId",
|
||||||
|
"AssetEntity"."libraryId" AS "AssetEntity_libraryId",
|
||||||
|
"AssetEntity"."deviceId" AS "AssetEntity_deviceId",
|
||||||
|
"AssetEntity"."type" AS "AssetEntity_type",
|
||||||
|
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
|
||||||
|
"AssetEntity"."resizePath" AS "AssetEntity_resizePath",
|
||||||
|
"AssetEntity"."webpPath" AS "AssetEntity_webpPath",
|
||||||
|
"AssetEntity"."thumbhash" AS "AssetEntity_thumbhash",
|
||||||
|
"AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath",
|
||||||
|
"AssetEntity"."createdAt" AS "AssetEntity_createdAt",
|
||||||
|
"AssetEntity"."updatedAt" AS "AssetEntity_updatedAt",
|
||||||
|
"AssetEntity"."deletedAt" AS "AssetEntity_deletedAt",
|
||||||
|
"AssetEntity"."fileCreatedAt" AS "AssetEntity_fileCreatedAt",
|
||||||
|
"AssetEntity"."localDateTime" AS "AssetEntity_localDateTime",
|
||||||
|
"AssetEntity"."fileModifiedAt" AS "AssetEntity_fileModifiedAt",
|
||||||
|
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
||||||
|
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
||||||
|
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
||||||
|
"AssetEntity"."isReadOnly" AS "AssetEntity_isReadOnly",
|
||||||
|
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
||||||
|
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
||||||
|
"AssetEntity"."duration" AS "AssetEntity_duration",
|
||||||
|
"AssetEntity"."isVisible" AS "AssetEntity_isVisible",
|
||||||
|
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
||||||
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
|
"AssetEntity"."stackParentId" AS "AssetEntity_stackParentId",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."assetId" AS "AssetEntity__AssetEntity_exifInfo_assetId",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."description" AS "AssetEntity__AssetEntity_exifInfo_description",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."exifImageWidth" AS "AssetEntity__AssetEntity_exifInfo_exifImageWidth",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."exifImageHeight" AS "AssetEntity__AssetEntity_exifInfo_exifImageHeight",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."fileSizeInByte" AS "AssetEntity__AssetEntity_exifInfo_fileSizeInByte",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."orientation" AS "AssetEntity__AssetEntity_exifInfo_orientation",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."dateTimeOriginal" AS "AssetEntity__AssetEntity_exifInfo_dateTimeOriginal",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."modifyDate" AS "AssetEntity__AssetEntity_exifInfo_modifyDate",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."timeZone" AS "AssetEntity__AssetEntity_exifInfo_timeZone",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."latitude" AS "AssetEntity__AssetEntity_exifInfo_latitude",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."longitude" AS "AssetEntity__AssetEntity_exifInfo_longitude",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."projectionType" AS "AssetEntity__AssetEntity_exifInfo_projectionType",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."city" AS "AssetEntity__AssetEntity_exifInfo_city",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."livePhotoCID" AS "AssetEntity__AssetEntity_exifInfo_livePhotoCID",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."state" AS "AssetEntity__AssetEntity_exifInfo_state",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."country" AS "AssetEntity__AssetEntity_exifInfo_country",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."make" AS "AssetEntity__AssetEntity_exifInfo_make",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."model" AS "AssetEntity__AssetEntity_exifInfo_model",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."lensModel" AS "AssetEntity__AssetEntity_exifInfo_lensModel",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."fNumber" AS "AssetEntity__AssetEntity_exifInfo_fNumber",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."focalLength" AS "AssetEntity__AssetEntity_exifInfo_focalLength",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."iso" AS "AssetEntity__AssetEntity_exifInfo_iso",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."exposureTime" AS "AssetEntity__AssetEntity_exifInfo_exposureTime",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."profileDescription" AS "AssetEntity__AssetEntity_exifInfo_profileDescription",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."colorspace" AS "AssetEntity__AssetEntity_exifInfo_colorspace",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."bitsPerSample" AS "AssetEntity__AssetEntity_exifInfo_bitsPerSample",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."fps" AS "AssetEntity__AssetEntity_exifInfo_fps",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."exifTextSearchableColumn" AS "AssetEntity__AssetEntity_exifInfo_exifTextSearchableColumn"
|
||||||
|
FROM
|
||||||
|
"assets" "AssetEntity"
|
||||||
|
LEFT JOIN "exif" "AssetEntity__AssetEntity_exifInfo" ON "AssetEntity__AssetEntity_exifInfo"."assetId" = "AssetEntity"."id"
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"AssetEntity"."ownerId" = $1
|
||||||
|
AND "AssetEntity"."isVisible" = $2
|
||||||
|
AND "AssetEntity"."isArchived" = $3
|
||||||
|
AND NOT ("AssetEntity"."resizePath" IS NULL)
|
||||||
|
AND "AssetEntity"."fileCreatedAt" BETWEEN $4 AND $5
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND ("AssetEntity"."deletedAt" IS NULL)
|
||||||
|
ORDER BY
|
||||||
|
"AssetEntity"."fileCreatedAt" DESC
|
||||||
|
|
||||||
|
-- AssetRepository.getByDayOfYear
|
||||||
|
SELECT
|
||||||
|
"entity"."id" AS "entity_id",
|
||||||
|
"entity"."deviceAssetId" AS "entity_deviceAssetId",
|
||||||
|
"entity"."ownerId" AS "entity_ownerId",
|
||||||
|
"entity"."libraryId" AS "entity_libraryId",
|
||||||
|
"entity"."deviceId" AS "entity_deviceId",
|
||||||
|
"entity"."type" AS "entity_type",
|
||||||
|
"entity"."originalPath" AS "entity_originalPath",
|
||||||
|
"entity"."resizePath" AS "entity_resizePath",
|
||||||
|
"entity"."webpPath" AS "entity_webpPath",
|
||||||
|
"entity"."thumbhash" AS "entity_thumbhash",
|
||||||
|
"entity"."encodedVideoPath" AS "entity_encodedVideoPath",
|
||||||
|
"entity"."createdAt" AS "entity_createdAt",
|
||||||
|
"entity"."updatedAt" AS "entity_updatedAt",
|
||||||
|
"entity"."deletedAt" AS "entity_deletedAt",
|
||||||
|
"entity"."fileCreatedAt" AS "entity_fileCreatedAt",
|
||||||
|
"entity"."localDateTime" AS "entity_localDateTime",
|
||||||
|
"entity"."fileModifiedAt" AS "entity_fileModifiedAt",
|
||||||
|
"entity"."isFavorite" AS "entity_isFavorite",
|
||||||
|
"entity"."isArchived" AS "entity_isArchived",
|
||||||
|
"entity"."isExternal" AS "entity_isExternal",
|
||||||
|
"entity"."isReadOnly" AS "entity_isReadOnly",
|
||||||
|
"entity"."isOffline" AS "entity_isOffline",
|
||||||
|
"entity"."checksum" AS "entity_checksum",
|
||||||
|
"entity"."duration" AS "entity_duration",
|
||||||
|
"entity"."isVisible" AS "entity_isVisible",
|
||||||
|
"entity"."livePhotoVideoId" AS "entity_livePhotoVideoId",
|
||||||
|
"entity"."originalFileName" AS "entity_originalFileName",
|
||||||
|
"entity"."sidecarPath" AS "entity_sidecarPath",
|
||||||
|
"entity"."stackParentId" AS "entity_stackParentId",
|
||||||
|
"exifInfo"."assetId" AS "exifInfo_assetId",
|
||||||
|
"exifInfo"."description" AS "exifInfo_description",
|
||||||
|
"exifInfo"."exifImageWidth" AS "exifInfo_exifImageWidth",
|
||||||
|
"exifInfo"."exifImageHeight" AS "exifInfo_exifImageHeight",
|
||||||
|
"exifInfo"."fileSizeInByte" AS "exifInfo_fileSizeInByte",
|
||||||
|
"exifInfo"."orientation" AS "exifInfo_orientation",
|
||||||
|
"exifInfo"."dateTimeOriginal" AS "exifInfo_dateTimeOriginal",
|
||||||
|
"exifInfo"."modifyDate" AS "exifInfo_modifyDate",
|
||||||
|
"exifInfo"."timeZone" AS "exifInfo_timeZone",
|
||||||
|
"exifInfo"."latitude" AS "exifInfo_latitude",
|
||||||
|
"exifInfo"."longitude" AS "exifInfo_longitude",
|
||||||
|
"exifInfo"."projectionType" AS "exifInfo_projectionType",
|
||||||
|
"exifInfo"."city" AS "exifInfo_city",
|
||||||
|
"exifInfo"."livePhotoCID" AS "exifInfo_livePhotoCID",
|
||||||
|
"exifInfo"."state" AS "exifInfo_state",
|
||||||
|
"exifInfo"."country" AS "exifInfo_country",
|
||||||
|
"exifInfo"."make" AS "exifInfo_make",
|
||||||
|
"exifInfo"."model" AS "exifInfo_model",
|
||||||
|
"exifInfo"."lensModel" AS "exifInfo_lensModel",
|
||||||
|
"exifInfo"."fNumber" AS "exifInfo_fNumber",
|
||||||
|
"exifInfo"."focalLength" AS "exifInfo_focalLength",
|
||||||
|
"exifInfo"."iso" AS "exifInfo_iso",
|
||||||
|
"exifInfo"."exposureTime" AS "exifInfo_exposureTime",
|
||||||
|
"exifInfo"."profileDescription" AS "exifInfo_profileDescription",
|
||||||
|
"exifInfo"."colorspace" AS "exifInfo_colorspace",
|
||||||
|
"exifInfo"."bitsPerSample" AS "exifInfo_bitsPerSample",
|
||||||
|
"exifInfo"."fps" AS "exifInfo_fps",
|
||||||
|
"exifInfo"."exifTextSearchableColumn" AS "exifInfo_exifTextSearchableColumn"
|
||||||
|
FROM
|
||||||
|
"assets" "entity"
|
||||||
|
LEFT JOIN "exif" "exifInfo" ON "exifInfo"."assetId" = "entity"."id"
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
"entity"."ownerId" = $1
|
||||||
|
AND "entity"."isVisible" = true
|
||||||
|
AND "entity"."isArchived" = false
|
||||||
|
AND "entity"."resizePath" IS NOT NULL
|
||||||
|
AND EXTRACT(
|
||||||
|
DAY
|
||||||
|
FROM
|
||||||
|
"entity"."localDateTime" AT TIME ZONE 'UTC'
|
||||||
|
) = $2
|
||||||
|
AND EXTRACT(
|
||||||
|
MONTH
|
||||||
|
FROM
|
||||||
|
"entity"."localDateTime" AT TIME ZONE 'UTC'
|
||||||
|
) = $3
|
||||||
|
)
|
||||||
|
AND ("entity"."deletedAt" IS NULL)
|
||||||
|
ORDER BY
|
||||||
|
"entity"."localDateTime" DESC
|
||||||
|
|
||||||
|
-- AssetRepository.getByIds
|
||||||
|
SELECT
|
||||||
|
"AssetEntity"."id" AS "AssetEntity_id",
|
||||||
|
"AssetEntity"."deviceAssetId" AS "AssetEntity_deviceAssetId",
|
||||||
|
"AssetEntity"."ownerId" AS "AssetEntity_ownerId",
|
||||||
|
"AssetEntity"."libraryId" AS "AssetEntity_libraryId",
|
||||||
|
"AssetEntity"."deviceId" AS "AssetEntity_deviceId",
|
||||||
|
"AssetEntity"."type" AS "AssetEntity_type",
|
||||||
|
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
|
||||||
|
"AssetEntity"."resizePath" AS "AssetEntity_resizePath",
|
||||||
|
"AssetEntity"."webpPath" AS "AssetEntity_webpPath",
|
||||||
|
"AssetEntity"."thumbhash" AS "AssetEntity_thumbhash",
|
||||||
|
"AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath",
|
||||||
|
"AssetEntity"."createdAt" AS "AssetEntity_createdAt",
|
||||||
|
"AssetEntity"."updatedAt" AS "AssetEntity_updatedAt",
|
||||||
|
"AssetEntity"."deletedAt" AS "AssetEntity_deletedAt",
|
||||||
|
"AssetEntity"."fileCreatedAt" AS "AssetEntity_fileCreatedAt",
|
||||||
|
"AssetEntity"."localDateTime" AS "AssetEntity_localDateTime",
|
||||||
|
"AssetEntity"."fileModifiedAt" AS "AssetEntity_fileModifiedAt",
|
||||||
|
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
||||||
|
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
||||||
|
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
||||||
|
"AssetEntity"."isReadOnly" AS "AssetEntity_isReadOnly",
|
||||||
|
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
||||||
|
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
||||||
|
"AssetEntity"."duration" AS "AssetEntity_duration",
|
||||||
|
"AssetEntity"."isVisible" AS "AssetEntity_isVisible",
|
||||||
|
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
||||||
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
|
"AssetEntity"."stackParentId" AS "AssetEntity_stackParentId",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."assetId" AS "AssetEntity__AssetEntity_exifInfo_assetId",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."description" AS "AssetEntity__AssetEntity_exifInfo_description",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."exifImageWidth" AS "AssetEntity__AssetEntity_exifInfo_exifImageWidth",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."exifImageHeight" AS "AssetEntity__AssetEntity_exifInfo_exifImageHeight",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."fileSizeInByte" AS "AssetEntity__AssetEntity_exifInfo_fileSizeInByte",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."orientation" AS "AssetEntity__AssetEntity_exifInfo_orientation",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."dateTimeOriginal" AS "AssetEntity__AssetEntity_exifInfo_dateTimeOriginal",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."modifyDate" AS "AssetEntity__AssetEntity_exifInfo_modifyDate",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."timeZone" AS "AssetEntity__AssetEntity_exifInfo_timeZone",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."latitude" AS "AssetEntity__AssetEntity_exifInfo_latitude",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."longitude" AS "AssetEntity__AssetEntity_exifInfo_longitude",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."projectionType" AS "AssetEntity__AssetEntity_exifInfo_projectionType",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."city" AS "AssetEntity__AssetEntity_exifInfo_city",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."livePhotoCID" AS "AssetEntity__AssetEntity_exifInfo_livePhotoCID",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."state" AS "AssetEntity__AssetEntity_exifInfo_state",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."country" AS "AssetEntity__AssetEntity_exifInfo_country",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."make" AS "AssetEntity__AssetEntity_exifInfo_make",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."model" AS "AssetEntity__AssetEntity_exifInfo_model",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."lensModel" AS "AssetEntity__AssetEntity_exifInfo_lensModel",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."fNumber" AS "AssetEntity__AssetEntity_exifInfo_fNumber",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."focalLength" AS "AssetEntity__AssetEntity_exifInfo_focalLength",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."iso" AS "AssetEntity__AssetEntity_exifInfo_iso",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."exposureTime" AS "AssetEntity__AssetEntity_exifInfo_exposureTime",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."profileDescription" AS "AssetEntity__AssetEntity_exifInfo_profileDescription",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."colorspace" AS "AssetEntity__AssetEntity_exifInfo_colorspace",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."bitsPerSample" AS "AssetEntity__AssetEntity_exifInfo_bitsPerSample",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."fps" AS "AssetEntity__AssetEntity_exifInfo_fps",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."exifTextSearchableColumn" AS "AssetEntity__AssetEntity_exifInfo_exifTextSearchableColumn",
|
||||||
|
"AssetEntity__AssetEntity_smartInfo"."assetId" AS "AssetEntity__AssetEntity_smartInfo_assetId",
|
||||||
|
"AssetEntity__AssetEntity_smartInfo"."tags" AS "AssetEntity__AssetEntity_smartInfo_tags",
|
||||||
|
"AssetEntity__AssetEntity_smartInfo"."objects" AS "AssetEntity__AssetEntity_smartInfo_objects",
|
||||||
|
"AssetEntity__AssetEntity_smartInfo"."clipEmbedding" AS "AssetEntity__AssetEntity_smartInfo_clipEmbedding",
|
||||||
|
"AssetEntity__AssetEntity_tags"."id" AS "AssetEntity__AssetEntity_tags_id",
|
||||||
|
"AssetEntity__AssetEntity_tags"."type" AS "AssetEntity__AssetEntity_tags_type",
|
||||||
|
"AssetEntity__AssetEntity_tags"."name" AS "AssetEntity__AssetEntity_tags_name",
|
||||||
|
"AssetEntity__AssetEntity_tags"."userId" AS "AssetEntity__AssetEntity_tags_userId",
|
||||||
|
"AssetEntity__AssetEntity_tags"."renameTagId" AS "AssetEntity__AssetEntity_tags_renameTagId",
|
||||||
|
"AssetEntity__AssetEntity_faces"."id" AS "AssetEntity__AssetEntity_faces_id",
|
||||||
|
"AssetEntity__AssetEntity_faces"."assetId" AS "AssetEntity__AssetEntity_faces_assetId",
|
||||||
|
"AssetEntity__AssetEntity_faces"."personId" AS "AssetEntity__AssetEntity_faces_personId",
|
||||||
|
"AssetEntity__AssetEntity_faces"."embedding" AS "AssetEntity__AssetEntity_faces_embedding",
|
||||||
|
"AssetEntity__AssetEntity_faces"."imageWidth" AS "AssetEntity__AssetEntity_faces_imageWidth",
|
||||||
|
"AssetEntity__AssetEntity_faces"."imageHeight" AS "AssetEntity__AssetEntity_faces_imageHeight",
|
||||||
|
"AssetEntity__AssetEntity_faces"."boundingBoxX1" AS "AssetEntity__AssetEntity_faces_boundingBoxX1",
|
||||||
|
"AssetEntity__AssetEntity_faces"."boundingBoxY1" AS "AssetEntity__AssetEntity_faces_boundingBoxY1",
|
||||||
|
"AssetEntity__AssetEntity_faces"."boundingBoxX2" AS "AssetEntity__AssetEntity_faces_boundingBoxX2",
|
||||||
|
"AssetEntity__AssetEntity_faces"."boundingBoxY2" AS "AssetEntity__AssetEntity_faces_boundingBoxY2",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."id" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_id",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."createdAt" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_createdAt",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."updatedAt" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_updatedAt",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."ownerId" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_ownerId",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."name" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_name",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."birthDate" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_birthDate",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."thumbnailPath" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_thumbnailPath",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."faceAssetId" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_faceAssetId",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."isHidden" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_isHidden",
|
||||||
|
"AssetEntity__AssetEntity_stack"."id" AS "AssetEntity__AssetEntity_stack_id",
|
||||||
|
"AssetEntity__AssetEntity_stack"."deviceAssetId" AS "AssetEntity__AssetEntity_stack_deviceAssetId",
|
||||||
|
"AssetEntity__AssetEntity_stack"."ownerId" AS "AssetEntity__AssetEntity_stack_ownerId",
|
||||||
|
"AssetEntity__AssetEntity_stack"."libraryId" AS "AssetEntity__AssetEntity_stack_libraryId",
|
||||||
|
"AssetEntity__AssetEntity_stack"."deviceId" AS "AssetEntity__AssetEntity_stack_deviceId",
|
||||||
|
"AssetEntity__AssetEntity_stack"."type" AS "AssetEntity__AssetEntity_stack_type",
|
||||||
|
"AssetEntity__AssetEntity_stack"."originalPath" AS "AssetEntity__AssetEntity_stack_originalPath",
|
||||||
|
"AssetEntity__AssetEntity_stack"."resizePath" AS "AssetEntity__AssetEntity_stack_resizePath",
|
||||||
|
"AssetEntity__AssetEntity_stack"."webpPath" AS "AssetEntity__AssetEntity_stack_webpPath",
|
||||||
|
"AssetEntity__AssetEntity_stack"."thumbhash" AS "AssetEntity__AssetEntity_stack_thumbhash",
|
||||||
|
"AssetEntity__AssetEntity_stack"."encodedVideoPath" AS "AssetEntity__AssetEntity_stack_encodedVideoPath",
|
||||||
|
"AssetEntity__AssetEntity_stack"."createdAt" AS "AssetEntity__AssetEntity_stack_createdAt",
|
||||||
|
"AssetEntity__AssetEntity_stack"."updatedAt" AS "AssetEntity__AssetEntity_stack_updatedAt",
|
||||||
|
"AssetEntity__AssetEntity_stack"."deletedAt" AS "AssetEntity__AssetEntity_stack_deletedAt",
|
||||||
|
"AssetEntity__AssetEntity_stack"."fileCreatedAt" AS "AssetEntity__AssetEntity_stack_fileCreatedAt",
|
||||||
|
"AssetEntity__AssetEntity_stack"."localDateTime" AS "AssetEntity__AssetEntity_stack_localDateTime",
|
||||||
|
"AssetEntity__AssetEntity_stack"."fileModifiedAt" AS "AssetEntity__AssetEntity_stack_fileModifiedAt",
|
||||||
|
"AssetEntity__AssetEntity_stack"."isFavorite" AS "AssetEntity__AssetEntity_stack_isFavorite",
|
||||||
|
"AssetEntity__AssetEntity_stack"."isArchived" AS "AssetEntity__AssetEntity_stack_isArchived",
|
||||||
|
"AssetEntity__AssetEntity_stack"."isExternal" AS "AssetEntity__AssetEntity_stack_isExternal",
|
||||||
|
"AssetEntity__AssetEntity_stack"."isReadOnly" AS "AssetEntity__AssetEntity_stack_isReadOnly",
|
||||||
|
"AssetEntity__AssetEntity_stack"."isOffline" AS "AssetEntity__AssetEntity_stack_isOffline",
|
||||||
|
"AssetEntity__AssetEntity_stack"."checksum" AS "AssetEntity__AssetEntity_stack_checksum",
|
||||||
|
"AssetEntity__AssetEntity_stack"."duration" AS "AssetEntity__AssetEntity_stack_duration",
|
||||||
|
"AssetEntity__AssetEntity_stack"."isVisible" AS "AssetEntity__AssetEntity_stack_isVisible",
|
||||||
|
"AssetEntity__AssetEntity_stack"."livePhotoVideoId" AS "AssetEntity__AssetEntity_stack_livePhotoVideoId",
|
||||||
|
"AssetEntity__AssetEntity_stack"."originalFileName" AS "AssetEntity__AssetEntity_stack_originalFileName",
|
||||||
|
"AssetEntity__AssetEntity_stack"."sidecarPath" AS "AssetEntity__AssetEntity_stack_sidecarPath",
|
||||||
|
"AssetEntity__AssetEntity_stack"."stackParentId" AS "AssetEntity__AssetEntity_stack_stackParentId"
|
||||||
|
FROM
|
||||||
|
"assets" "AssetEntity"
|
||||||
|
LEFT JOIN "exif" "AssetEntity__AssetEntity_exifInfo" ON "AssetEntity__AssetEntity_exifInfo"."assetId" = "AssetEntity"."id"
|
||||||
|
LEFT JOIN "smart_info" "AssetEntity__AssetEntity_smartInfo" ON "AssetEntity__AssetEntity_smartInfo"."assetId" = "AssetEntity"."id"
|
||||||
|
LEFT JOIN "tag_asset" "AssetEntity_AssetEntity__AssetEntity_tags" ON "AssetEntity_AssetEntity__AssetEntity_tags"."assetsId" = "AssetEntity"."id"
|
||||||
|
LEFT JOIN "tags" "AssetEntity__AssetEntity_tags" ON "AssetEntity__AssetEntity_tags"."id" = "AssetEntity_AssetEntity__AssetEntity_tags"."tagsId"
|
||||||
|
LEFT JOIN "asset_faces" "AssetEntity__AssetEntity_faces" ON "AssetEntity__AssetEntity_faces"."assetId" = "AssetEntity"."id"
|
||||||
|
LEFT JOIN "person" "8258e303a73a72cf6abb13d73fb592dde0d68280" ON "8258e303a73a72cf6abb13d73fb592dde0d68280"."id" = "AssetEntity__AssetEntity_faces"."personId"
|
||||||
|
LEFT JOIN "assets" "AssetEntity__AssetEntity_stack" ON "AssetEntity__AssetEntity_stack"."stackParentId" = "AssetEntity"."id"
|
||||||
|
WHERE
|
||||||
|
("AssetEntity"."id" IN ($1))
|
||||||
|
|
||||||
|
-- AssetRepository.deleteAll
|
||||||
|
DELETE FROM "assets"
|
||||||
|
WHERE
|
||||||
|
"ownerId" = $1
|
||||||
|
|
||||||
|
-- AssetRepository.getByLibraryId
|
||||||
|
SELECT
|
||||||
|
"AssetEntity"."id" AS "AssetEntity_id",
|
||||||
|
"AssetEntity"."deviceAssetId" AS "AssetEntity_deviceAssetId",
|
||||||
|
"AssetEntity"."ownerId" AS "AssetEntity_ownerId",
|
||||||
|
"AssetEntity"."libraryId" AS "AssetEntity_libraryId",
|
||||||
|
"AssetEntity"."deviceId" AS "AssetEntity_deviceId",
|
||||||
|
"AssetEntity"."type" AS "AssetEntity_type",
|
||||||
|
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
|
||||||
|
"AssetEntity"."resizePath" AS "AssetEntity_resizePath",
|
||||||
|
"AssetEntity"."webpPath" AS "AssetEntity_webpPath",
|
||||||
|
"AssetEntity"."thumbhash" AS "AssetEntity_thumbhash",
|
||||||
|
"AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath",
|
||||||
|
"AssetEntity"."createdAt" AS "AssetEntity_createdAt",
|
||||||
|
"AssetEntity"."updatedAt" AS "AssetEntity_updatedAt",
|
||||||
|
"AssetEntity"."deletedAt" AS "AssetEntity_deletedAt",
|
||||||
|
"AssetEntity"."fileCreatedAt" AS "AssetEntity_fileCreatedAt",
|
||||||
|
"AssetEntity"."localDateTime" AS "AssetEntity_localDateTime",
|
||||||
|
"AssetEntity"."fileModifiedAt" AS "AssetEntity_fileModifiedAt",
|
||||||
|
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
||||||
|
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
||||||
|
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
||||||
|
"AssetEntity"."isReadOnly" AS "AssetEntity_isReadOnly",
|
||||||
|
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
||||||
|
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
||||||
|
"AssetEntity"."duration" AS "AssetEntity_duration",
|
||||||
|
"AssetEntity"."isVisible" AS "AssetEntity_isVisible",
|
||||||
|
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
||||||
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
|
"AssetEntity"."stackParentId" AS "AssetEntity_stackParentId"
|
||||||
|
FROM
|
||||||
|
"assets" "AssetEntity"
|
||||||
|
LEFT JOIN "libraries" "AssetEntity__AssetEntity_library" ON "AssetEntity__AssetEntity_library"."id" = "AssetEntity"."libraryId"
|
||||||
|
AND (
|
||||||
|
"AssetEntity__AssetEntity_library"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
(("AssetEntity__AssetEntity_library"."id" IN ($1)))
|
||||||
|
AND ("AssetEntity"."deletedAt" IS NULL)
|
||||||
|
|
||||||
|
-- AssetRepository.getByLibraryIdAndOriginalPath
|
||||||
|
SELECT DISTINCT
|
||||||
|
"distinctAlias"."AssetEntity_id" AS "ids_AssetEntity_id"
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
"AssetEntity"."id" AS "AssetEntity_id",
|
||||||
|
"AssetEntity"."deviceAssetId" AS "AssetEntity_deviceAssetId",
|
||||||
|
"AssetEntity"."ownerId" AS "AssetEntity_ownerId",
|
||||||
|
"AssetEntity"."libraryId" AS "AssetEntity_libraryId",
|
||||||
|
"AssetEntity"."deviceId" AS "AssetEntity_deviceId",
|
||||||
|
"AssetEntity"."type" AS "AssetEntity_type",
|
||||||
|
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
|
||||||
|
"AssetEntity"."resizePath" AS "AssetEntity_resizePath",
|
||||||
|
"AssetEntity"."webpPath" AS "AssetEntity_webpPath",
|
||||||
|
"AssetEntity"."thumbhash" AS "AssetEntity_thumbhash",
|
||||||
|
"AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath",
|
||||||
|
"AssetEntity"."createdAt" AS "AssetEntity_createdAt",
|
||||||
|
"AssetEntity"."updatedAt" AS "AssetEntity_updatedAt",
|
||||||
|
"AssetEntity"."deletedAt" AS "AssetEntity_deletedAt",
|
||||||
|
"AssetEntity"."fileCreatedAt" AS "AssetEntity_fileCreatedAt",
|
||||||
|
"AssetEntity"."localDateTime" AS "AssetEntity_localDateTime",
|
||||||
|
"AssetEntity"."fileModifiedAt" AS "AssetEntity_fileModifiedAt",
|
||||||
|
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
||||||
|
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
||||||
|
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
||||||
|
"AssetEntity"."isReadOnly" AS "AssetEntity_isReadOnly",
|
||||||
|
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
||||||
|
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
||||||
|
"AssetEntity"."duration" AS "AssetEntity_duration",
|
||||||
|
"AssetEntity"."isVisible" AS "AssetEntity_isVisible",
|
||||||
|
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
||||||
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
|
"AssetEntity"."stackParentId" AS "AssetEntity_stackParentId"
|
||||||
|
FROM
|
||||||
|
"assets" "AssetEntity"
|
||||||
|
LEFT JOIN "libraries" "AssetEntity__AssetEntity_library" ON "AssetEntity__AssetEntity_library"."id" = "AssetEntity"."libraryId"
|
||||||
|
AND (
|
||||||
|
"AssetEntity__AssetEntity_library"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"AssetEntity__AssetEntity_library"."id" = $1
|
||||||
|
AND "AssetEntity"."originalPath" = $2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND ("AssetEntity"."deletedAt" IS NULL)
|
||||||
|
) "distinctAlias"
|
||||||
|
ORDER BY
|
||||||
|
"AssetEntity_id" ASC
|
||||||
|
LIMIT
|
||||||
|
1
|
||||||
|
|
||||||
|
-- AssetRepository.getAllByDeviceId
|
||||||
|
SELECT
|
||||||
|
"AssetEntity"."deviceAssetId" AS "AssetEntity_deviceAssetId",
|
||||||
|
"AssetEntity"."id" AS "AssetEntity_id"
|
||||||
|
FROM
|
||||||
|
"assets" "AssetEntity"
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
"AssetEntity"."ownerId" = $1
|
||||||
|
AND "AssetEntity"."deviceId" = $2
|
||||||
|
AND "AssetEntity"."isVisible" = $3
|
||||||
|
)
|
||||||
|
|
||||||
|
-- AssetRepository.getById
|
||||||
|
SELECT DISTINCT
|
||||||
|
"distinctAlias"."AssetEntity_id" AS "ids_AssetEntity_id"
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
"AssetEntity"."id" AS "AssetEntity_id",
|
||||||
|
"AssetEntity"."deviceAssetId" AS "AssetEntity_deviceAssetId",
|
||||||
|
"AssetEntity"."ownerId" AS "AssetEntity_ownerId",
|
||||||
|
"AssetEntity"."libraryId" AS "AssetEntity_libraryId",
|
||||||
|
"AssetEntity"."deviceId" AS "AssetEntity_deviceId",
|
||||||
|
"AssetEntity"."type" AS "AssetEntity_type",
|
||||||
|
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
|
||||||
|
"AssetEntity"."resizePath" AS "AssetEntity_resizePath",
|
||||||
|
"AssetEntity"."webpPath" AS "AssetEntity_webpPath",
|
||||||
|
"AssetEntity"."thumbhash" AS "AssetEntity_thumbhash",
|
||||||
|
"AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath",
|
||||||
|
"AssetEntity"."createdAt" AS "AssetEntity_createdAt",
|
||||||
|
"AssetEntity"."updatedAt" AS "AssetEntity_updatedAt",
|
||||||
|
"AssetEntity"."deletedAt" AS "AssetEntity_deletedAt",
|
||||||
|
"AssetEntity"."fileCreatedAt" AS "AssetEntity_fileCreatedAt",
|
||||||
|
"AssetEntity"."localDateTime" AS "AssetEntity_localDateTime",
|
||||||
|
"AssetEntity"."fileModifiedAt" AS "AssetEntity_fileModifiedAt",
|
||||||
|
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
||||||
|
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
||||||
|
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
||||||
|
"AssetEntity"."isReadOnly" AS "AssetEntity_isReadOnly",
|
||||||
|
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
||||||
|
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
||||||
|
"AssetEntity"."duration" AS "AssetEntity_duration",
|
||||||
|
"AssetEntity"."isVisible" AS "AssetEntity_isVisible",
|
||||||
|
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
||||||
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
|
"AssetEntity"."stackParentId" AS "AssetEntity_stackParentId",
|
||||||
|
"AssetEntity__AssetEntity_faces"."id" AS "AssetEntity__AssetEntity_faces_id",
|
||||||
|
"AssetEntity__AssetEntity_faces"."assetId" AS "AssetEntity__AssetEntity_faces_assetId",
|
||||||
|
"AssetEntity__AssetEntity_faces"."personId" AS "AssetEntity__AssetEntity_faces_personId",
|
||||||
|
"AssetEntity__AssetEntity_faces"."embedding" AS "AssetEntity__AssetEntity_faces_embedding",
|
||||||
|
"AssetEntity__AssetEntity_faces"."imageWidth" AS "AssetEntity__AssetEntity_faces_imageWidth",
|
||||||
|
"AssetEntity__AssetEntity_faces"."imageHeight" AS "AssetEntity__AssetEntity_faces_imageHeight",
|
||||||
|
"AssetEntity__AssetEntity_faces"."boundingBoxX1" AS "AssetEntity__AssetEntity_faces_boundingBoxX1",
|
||||||
|
"AssetEntity__AssetEntity_faces"."boundingBoxY1" AS "AssetEntity__AssetEntity_faces_boundingBoxY1",
|
||||||
|
"AssetEntity__AssetEntity_faces"."boundingBoxX2" AS "AssetEntity__AssetEntity_faces_boundingBoxX2",
|
||||||
|
"AssetEntity__AssetEntity_faces"."boundingBoxY2" AS "AssetEntity__AssetEntity_faces_boundingBoxY2",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."id" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_id",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."createdAt" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_createdAt",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."updatedAt" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_updatedAt",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."ownerId" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_ownerId",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."name" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_name",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."birthDate" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_birthDate",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."thumbnailPath" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_thumbnailPath",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."faceAssetId" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_faceAssetId",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."isHidden" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_isHidden",
|
||||||
|
"AssetEntity__AssetEntity_library"."id" AS "AssetEntity__AssetEntity_library_id",
|
||||||
|
"AssetEntity__AssetEntity_library"."name" AS "AssetEntity__AssetEntity_library_name",
|
||||||
|
"AssetEntity__AssetEntity_library"."ownerId" AS "AssetEntity__AssetEntity_library_ownerId",
|
||||||
|
"AssetEntity__AssetEntity_library"."type" AS "AssetEntity__AssetEntity_library_type",
|
||||||
|
"AssetEntity__AssetEntity_library"."importPaths" AS "AssetEntity__AssetEntity_library_importPaths",
|
||||||
|
"AssetEntity__AssetEntity_library"."exclusionPatterns" AS "AssetEntity__AssetEntity_library_exclusionPatterns",
|
||||||
|
"AssetEntity__AssetEntity_library"."createdAt" AS "AssetEntity__AssetEntity_library_createdAt",
|
||||||
|
"AssetEntity__AssetEntity_library"."updatedAt" AS "AssetEntity__AssetEntity_library_updatedAt",
|
||||||
|
"AssetEntity__AssetEntity_library"."deletedAt" AS "AssetEntity__AssetEntity_library_deletedAt",
|
||||||
|
"AssetEntity__AssetEntity_library"."refreshedAt" AS "AssetEntity__AssetEntity_library_refreshedAt",
|
||||||
|
"AssetEntity__AssetEntity_library"."isVisible" AS "AssetEntity__AssetEntity_library_isVisible",
|
||||||
|
"AssetEntity__AssetEntity_stack"."id" AS "AssetEntity__AssetEntity_stack_id",
|
||||||
|
"AssetEntity__AssetEntity_stack"."deviceAssetId" AS "AssetEntity__AssetEntity_stack_deviceAssetId",
|
||||||
|
"AssetEntity__AssetEntity_stack"."ownerId" AS "AssetEntity__AssetEntity_stack_ownerId",
|
||||||
|
"AssetEntity__AssetEntity_stack"."libraryId" AS "AssetEntity__AssetEntity_stack_libraryId",
|
||||||
|
"AssetEntity__AssetEntity_stack"."deviceId" AS "AssetEntity__AssetEntity_stack_deviceId",
|
||||||
|
"AssetEntity__AssetEntity_stack"."type" AS "AssetEntity__AssetEntity_stack_type",
|
||||||
|
"AssetEntity__AssetEntity_stack"."originalPath" AS "AssetEntity__AssetEntity_stack_originalPath",
|
||||||
|
"AssetEntity__AssetEntity_stack"."resizePath" AS "AssetEntity__AssetEntity_stack_resizePath",
|
||||||
|
"AssetEntity__AssetEntity_stack"."webpPath" AS "AssetEntity__AssetEntity_stack_webpPath",
|
||||||
|
"AssetEntity__AssetEntity_stack"."thumbhash" AS "AssetEntity__AssetEntity_stack_thumbhash",
|
||||||
|
"AssetEntity__AssetEntity_stack"."encodedVideoPath" AS "AssetEntity__AssetEntity_stack_encodedVideoPath",
|
||||||
|
"AssetEntity__AssetEntity_stack"."createdAt" AS "AssetEntity__AssetEntity_stack_createdAt",
|
||||||
|
"AssetEntity__AssetEntity_stack"."updatedAt" AS "AssetEntity__AssetEntity_stack_updatedAt",
|
||||||
|
"AssetEntity__AssetEntity_stack"."deletedAt" AS "AssetEntity__AssetEntity_stack_deletedAt",
|
||||||
|
"AssetEntity__AssetEntity_stack"."fileCreatedAt" AS "AssetEntity__AssetEntity_stack_fileCreatedAt",
|
||||||
|
"AssetEntity__AssetEntity_stack"."localDateTime" AS "AssetEntity__AssetEntity_stack_localDateTime",
|
||||||
|
"AssetEntity__AssetEntity_stack"."fileModifiedAt" AS "AssetEntity__AssetEntity_stack_fileModifiedAt",
|
||||||
|
"AssetEntity__AssetEntity_stack"."isFavorite" AS "AssetEntity__AssetEntity_stack_isFavorite",
|
||||||
|
"AssetEntity__AssetEntity_stack"."isArchived" AS "AssetEntity__AssetEntity_stack_isArchived",
|
||||||
|
"AssetEntity__AssetEntity_stack"."isExternal" AS "AssetEntity__AssetEntity_stack_isExternal",
|
||||||
|
"AssetEntity__AssetEntity_stack"."isReadOnly" AS "AssetEntity__AssetEntity_stack_isReadOnly",
|
||||||
|
"AssetEntity__AssetEntity_stack"."isOffline" AS "AssetEntity__AssetEntity_stack_isOffline",
|
||||||
|
"AssetEntity__AssetEntity_stack"."checksum" AS "AssetEntity__AssetEntity_stack_checksum",
|
||||||
|
"AssetEntity__AssetEntity_stack"."duration" AS "AssetEntity__AssetEntity_stack_duration",
|
||||||
|
"AssetEntity__AssetEntity_stack"."isVisible" AS "AssetEntity__AssetEntity_stack_isVisible",
|
||||||
|
"AssetEntity__AssetEntity_stack"."livePhotoVideoId" AS "AssetEntity__AssetEntity_stack_livePhotoVideoId",
|
||||||
|
"AssetEntity__AssetEntity_stack"."originalFileName" AS "AssetEntity__AssetEntity_stack_originalFileName",
|
||||||
|
"AssetEntity__AssetEntity_stack"."sidecarPath" AS "AssetEntity__AssetEntity_stack_sidecarPath",
|
||||||
|
"AssetEntity__AssetEntity_stack"."stackParentId" AS "AssetEntity__AssetEntity_stack_stackParentId"
|
||||||
|
FROM
|
||||||
|
"assets" "AssetEntity"
|
||||||
|
LEFT JOIN "asset_faces" "AssetEntity__AssetEntity_faces" ON "AssetEntity__AssetEntity_faces"."assetId" = "AssetEntity"."id"
|
||||||
|
LEFT JOIN "person" "8258e303a73a72cf6abb13d73fb592dde0d68280" ON "8258e303a73a72cf6abb13d73fb592dde0d68280"."id" = "AssetEntity__AssetEntity_faces"."personId"
|
||||||
|
LEFT JOIN "libraries" "AssetEntity__AssetEntity_library" ON "AssetEntity__AssetEntity_library"."id" = "AssetEntity"."libraryId"
|
||||||
|
LEFT JOIN "assets" "AssetEntity__AssetEntity_stack" ON "AssetEntity__AssetEntity_stack"."stackParentId" = "AssetEntity"."id"
|
||||||
|
WHERE
|
||||||
|
("AssetEntity"."id" = $1)
|
||||||
|
) "distinctAlias"
|
||||||
|
ORDER BY
|
||||||
|
"AssetEntity_id" ASC
|
||||||
|
LIMIT
|
||||||
|
1
|
||||||
|
|
||||||
|
-- AssetRepository.updateAll
|
||||||
|
UPDATE "assets"
|
||||||
|
SET
|
||||||
|
"deviceId" = $1,
|
||||||
|
"updatedAt" = CURRENT_TIMESTAMP
|
||||||
|
WHERE
|
||||||
|
"id" IN ($2)
|
||||||
|
|
||||||
|
-- AssetRepository.getByChecksum
|
||||||
|
SELECT
|
||||||
|
"AssetEntity"."id" AS "AssetEntity_id",
|
||||||
|
"AssetEntity"."deviceAssetId" AS "AssetEntity_deviceAssetId",
|
||||||
|
"AssetEntity"."ownerId" AS "AssetEntity_ownerId",
|
||||||
|
"AssetEntity"."libraryId" AS "AssetEntity_libraryId",
|
||||||
|
"AssetEntity"."deviceId" AS "AssetEntity_deviceId",
|
||||||
|
"AssetEntity"."type" AS "AssetEntity_type",
|
||||||
|
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
|
||||||
|
"AssetEntity"."resizePath" AS "AssetEntity_resizePath",
|
||||||
|
"AssetEntity"."webpPath" AS "AssetEntity_webpPath",
|
||||||
|
"AssetEntity"."thumbhash" AS "AssetEntity_thumbhash",
|
||||||
|
"AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath",
|
||||||
|
"AssetEntity"."createdAt" AS "AssetEntity_createdAt",
|
||||||
|
"AssetEntity"."updatedAt" AS "AssetEntity_updatedAt",
|
||||||
|
"AssetEntity"."deletedAt" AS "AssetEntity_deletedAt",
|
||||||
|
"AssetEntity"."fileCreatedAt" AS "AssetEntity_fileCreatedAt",
|
||||||
|
"AssetEntity"."localDateTime" AS "AssetEntity_localDateTime",
|
||||||
|
"AssetEntity"."fileModifiedAt" AS "AssetEntity_fileModifiedAt",
|
||||||
|
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
||||||
|
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
||||||
|
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
||||||
|
"AssetEntity"."isReadOnly" AS "AssetEntity_isReadOnly",
|
||||||
|
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
||||||
|
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
||||||
|
"AssetEntity"."duration" AS "AssetEntity_duration",
|
||||||
|
"AssetEntity"."isVisible" AS "AssetEntity_isVisible",
|
||||||
|
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
||||||
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
|
"AssetEntity"."stackParentId" AS "AssetEntity_stackParentId"
|
||||||
|
FROM
|
||||||
|
"assets" "AssetEntity"
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"AssetEntity"."ownerId" = $1
|
||||||
|
AND "AssetEntity"."checksum" = $2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND ("AssetEntity"."deletedAt" IS NULL)
|
||||||
|
LIMIT
|
||||||
|
1
|
||||||
|
|
||||||
|
-- AssetRepository.getWithout (sidecar)
|
||||||
|
SELECT
|
||||||
|
"AssetEntity"."id" AS "AssetEntity_id",
|
||||||
|
"AssetEntity"."deviceAssetId" AS "AssetEntity_deviceAssetId",
|
||||||
|
"AssetEntity"."ownerId" AS "AssetEntity_ownerId",
|
||||||
|
"AssetEntity"."libraryId" AS "AssetEntity_libraryId",
|
||||||
|
"AssetEntity"."deviceId" AS "AssetEntity_deviceId",
|
||||||
|
"AssetEntity"."type" AS "AssetEntity_type",
|
||||||
|
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
|
||||||
|
"AssetEntity"."resizePath" AS "AssetEntity_resizePath",
|
||||||
|
"AssetEntity"."webpPath" AS "AssetEntity_webpPath",
|
||||||
|
"AssetEntity"."thumbhash" AS "AssetEntity_thumbhash",
|
||||||
|
"AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath",
|
||||||
|
"AssetEntity"."createdAt" AS "AssetEntity_createdAt",
|
||||||
|
"AssetEntity"."updatedAt" AS "AssetEntity_updatedAt",
|
||||||
|
"AssetEntity"."deletedAt" AS "AssetEntity_deletedAt",
|
||||||
|
"AssetEntity"."fileCreatedAt" AS "AssetEntity_fileCreatedAt",
|
||||||
|
"AssetEntity"."localDateTime" AS "AssetEntity_localDateTime",
|
||||||
|
"AssetEntity"."fileModifiedAt" AS "AssetEntity_fileModifiedAt",
|
||||||
|
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
||||||
|
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
||||||
|
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
||||||
|
"AssetEntity"."isReadOnly" AS "AssetEntity_isReadOnly",
|
||||||
|
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
||||||
|
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
||||||
|
"AssetEntity"."duration" AS "AssetEntity_duration",
|
||||||
|
"AssetEntity"."isVisible" AS "AssetEntity_isVisible",
|
||||||
|
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
||||||
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
|
"AssetEntity"."stackParentId" AS "AssetEntity_stackParentId"
|
||||||
|
FROM
|
||||||
|
"assets" "AssetEntity"
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"AssetEntity"."sidecarPath" IS NULL
|
||||||
|
AND "AssetEntity"."isVisible" = $1
|
||||||
|
)
|
||||||
|
OR (
|
||||||
|
"AssetEntity"."sidecarPath" = $2
|
||||||
|
AND "AssetEntity"."isVisible" = $3
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND ("AssetEntity"."deletedAt" IS NULL)
|
||||||
|
ORDER BY
|
||||||
|
"AssetEntity"."createdAt" ASC
|
||||||
|
LIMIT
|
||||||
|
11
|
1
server/src/infra/sql/audit.repository.sql
Normal file
1
server/src/infra/sql/audit.repository.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
-- NOTE: This file is auto generated by ./sql-generator
|
299
server/src/infra/sql/library.repository.sql
Normal file
299
server/src/infra/sql/library.repository.sql
Normal file
|
@ -0,0 +1,299 @@
|
||||||
|
-- NOTE: This file is auto generated by ./sql-generator
|
||||||
|
|
||||||
|
-- LibraryRepository.get
|
||||||
|
SELECT DISTINCT
|
||||||
|
"distinctAlias"."LibraryEntity_id" AS "ids_LibraryEntity_id"
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
"LibraryEntity"."id" AS "LibraryEntity_id",
|
||||||
|
"LibraryEntity"."name" AS "LibraryEntity_name",
|
||||||
|
"LibraryEntity"."ownerId" AS "LibraryEntity_ownerId",
|
||||||
|
"LibraryEntity"."type" AS "LibraryEntity_type",
|
||||||
|
"LibraryEntity"."importPaths" AS "LibraryEntity_importPaths",
|
||||||
|
"LibraryEntity"."exclusionPatterns" AS "LibraryEntity_exclusionPatterns",
|
||||||
|
"LibraryEntity"."createdAt" AS "LibraryEntity_createdAt",
|
||||||
|
"LibraryEntity"."updatedAt" AS "LibraryEntity_updatedAt",
|
||||||
|
"LibraryEntity"."deletedAt" AS "LibraryEntity_deletedAt",
|
||||||
|
"LibraryEntity"."refreshedAt" AS "LibraryEntity_refreshedAt",
|
||||||
|
"LibraryEntity"."isVisible" AS "LibraryEntity_isVisible",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."id" AS "LibraryEntity__LibraryEntity_owner_id",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."name" AS "LibraryEntity__LibraryEntity_owner_name",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."avatarColor" AS "LibraryEntity__LibraryEntity_owner_avatarColor",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."isAdmin" AS "LibraryEntity__LibraryEntity_owner_isAdmin",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."email" AS "LibraryEntity__LibraryEntity_owner_email",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."storageLabel" AS "LibraryEntity__LibraryEntity_owner_storageLabel",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."externalPath" AS "LibraryEntity__LibraryEntity_owner_externalPath",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."oauthId" AS "LibraryEntity__LibraryEntity_owner_oauthId",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."profileImagePath" AS "LibraryEntity__LibraryEntity_owner_profileImagePath",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."shouldChangePassword" AS "LibraryEntity__LibraryEntity_owner_shouldChangePassword",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."createdAt" AS "LibraryEntity__LibraryEntity_owner_createdAt",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."deletedAt" AS "LibraryEntity__LibraryEntity_owner_deletedAt",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."updatedAt" AS "LibraryEntity__LibraryEntity_owner_updatedAt",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."memoriesEnabled" AS "LibraryEntity__LibraryEntity_owner_memoriesEnabled"
|
||||||
|
FROM
|
||||||
|
"libraries" "LibraryEntity"
|
||||||
|
LEFT JOIN "users" "LibraryEntity__LibraryEntity_owner" ON "LibraryEntity__LibraryEntity_owner"."id" = "LibraryEntity"."ownerId"
|
||||||
|
AND (
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
(("LibraryEntity"."id" = $1))
|
||||||
|
AND ("LibraryEntity"."deletedAt" IS NULL)
|
||||||
|
) "distinctAlias"
|
||||||
|
ORDER BY
|
||||||
|
"LibraryEntity_id" ASC
|
||||||
|
LIMIT
|
||||||
|
1
|
||||||
|
|
||||||
|
-- LibraryRepository.existsByName
|
||||||
|
SELECT
|
||||||
|
1 AS "row_exists"
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
1 AS dummy_column
|
||||||
|
) "dummy_table"
|
||||||
|
WHERE
|
||||||
|
EXISTS (
|
||||||
|
SELECT
|
||||||
|
1
|
||||||
|
FROM
|
||||||
|
"libraries" "LibraryEntity"
|
||||||
|
WHERE
|
||||||
|
(("LibraryEntity"."name" = $1))
|
||||||
|
AND ("LibraryEntity"."deletedAt" IS NULL)
|
||||||
|
)
|
||||||
|
LIMIT
|
||||||
|
1
|
||||||
|
|
||||||
|
-- LibraryRepository.getCountForUser
|
||||||
|
SELECT
|
||||||
|
COUNT(1) AS "cnt"
|
||||||
|
FROM
|
||||||
|
"libraries" "LibraryEntity"
|
||||||
|
WHERE
|
||||||
|
(("LibraryEntity"."ownerId" = $1))
|
||||||
|
AND ("LibraryEntity"."deletedAt" IS NULL)
|
||||||
|
|
||||||
|
-- LibraryRepository.getDefaultUploadLibrary
|
||||||
|
SELECT
|
||||||
|
"LibraryEntity"."id" AS "LibraryEntity_id",
|
||||||
|
"LibraryEntity"."name" AS "LibraryEntity_name",
|
||||||
|
"LibraryEntity"."ownerId" AS "LibraryEntity_ownerId",
|
||||||
|
"LibraryEntity"."type" AS "LibraryEntity_type",
|
||||||
|
"LibraryEntity"."importPaths" AS "LibraryEntity_importPaths",
|
||||||
|
"LibraryEntity"."exclusionPatterns" AS "LibraryEntity_exclusionPatterns",
|
||||||
|
"LibraryEntity"."createdAt" AS "LibraryEntity_createdAt",
|
||||||
|
"LibraryEntity"."updatedAt" AS "LibraryEntity_updatedAt",
|
||||||
|
"LibraryEntity"."deletedAt" AS "LibraryEntity_deletedAt",
|
||||||
|
"LibraryEntity"."refreshedAt" AS "LibraryEntity_refreshedAt",
|
||||||
|
"LibraryEntity"."isVisible" AS "LibraryEntity_isVisible"
|
||||||
|
FROM
|
||||||
|
"libraries" "LibraryEntity"
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"LibraryEntity"."ownerId" = $1
|
||||||
|
AND "LibraryEntity"."type" = $2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND ("LibraryEntity"."deletedAt" IS NULL)
|
||||||
|
ORDER BY
|
||||||
|
"LibraryEntity"."createdAt" ASC
|
||||||
|
LIMIT
|
||||||
|
1
|
||||||
|
|
||||||
|
-- LibraryRepository.getUploadLibraryCount
|
||||||
|
SELECT
|
||||||
|
COUNT(1) AS "cnt"
|
||||||
|
FROM
|
||||||
|
"libraries" "LibraryEntity"
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"LibraryEntity"."ownerId" = $1
|
||||||
|
AND "LibraryEntity"."type" = $2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND ("LibraryEntity"."deletedAt" IS NULL)
|
||||||
|
|
||||||
|
-- LibraryRepository.getAllByUserId
|
||||||
|
SELECT
|
||||||
|
"LibraryEntity"."id" AS "LibraryEntity_id",
|
||||||
|
"LibraryEntity"."name" AS "LibraryEntity_name",
|
||||||
|
"LibraryEntity"."ownerId" AS "LibraryEntity_ownerId",
|
||||||
|
"LibraryEntity"."type" AS "LibraryEntity_type",
|
||||||
|
"LibraryEntity"."importPaths" AS "LibraryEntity_importPaths",
|
||||||
|
"LibraryEntity"."exclusionPatterns" AS "LibraryEntity_exclusionPatterns",
|
||||||
|
"LibraryEntity"."createdAt" AS "LibraryEntity_createdAt",
|
||||||
|
"LibraryEntity"."updatedAt" AS "LibraryEntity_updatedAt",
|
||||||
|
"LibraryEntity"."deletedAt" AS "LibraryEntity_deletedAt",
|
||||||
|
"LibraryEntity"."refreshedAt" AS "LibraryEntity_refreshedAt",
|
||||||
|
"LibraryEntity"."isVisible" AS "LibraryEntity_isVisible",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."id" AS "LibraryEntity__LibraryEntity_owner_id",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."name" AS "LibraryEntity__LibraryEntity_owner_name",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."avatarColor" AS "LibraryEntity__LibraryEntity_owner_avatarColor",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."isAdmin" AS "LibraryEntity__LibraryEntity_owner_isAdmin",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."email" AS "LibraryEntity__LibraryEntity_owner_email",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."storageLabel" AS "LibraryEntity__LibraryEntity_owner_storageLabel",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."externalPath" AS "LibraryEntity__LibraryEntity_owner_externalPath",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."oauthId" AS "LibraryEntity__LibraryEntity_owner_oauthId",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."profileImagePath" AS "LibraryEntity__LibraryEntity_owner_profileImagePath",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."shouldChangePassword" AS "LibraryEntity__LibraryEntity_owner_shouldChangePassword",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."createdAt" AS "LibraryEntity__LibraryEntity_owner_createdAt",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."deletedAt" AS "LibraryEntity__LibraryEntity_owner_deletedAt",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."updatedAt" AS "LibraryEntity__LibraryEntity_owner_updatedAt",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."memoriesEnabled" AS "LibraryEntity__LibraryEntity_owner_memoriesEnabled"
|
||||||
|
FROM
|
||||||
|
"libraries" "LibraryEntity"
|
||||||
|
LEFT JOIN "users" "LibraryEntity__LibraryEntity_owner" ON "LibraryEntity__LibraryEntity_owner"."id" = "LibraryEntity"."ownerId"
|
||||||
|
AND (
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"LibraryEntity"."ownerId" = $1
|
||||||
|
AND "LibraryEntity"."isVisible" = $2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND ("LibraryEntity"."deletedAt" IS NULL)
|
||||||
|
ORDER BY
|
||||||
|
"LibraryEntity"."createdAt" ASC
|
||||||
|
|
||||||
|
-- LibraryRepository.getAll
|
||||||
|
SELECT
|
||||||
|
"LibraryEntity"."id" AS "LibraryEntity_id",
|
||||||
|
"LibraryEntity"."name" AS "LibraryEntity_name",
|
||||||
|
"LibraryEntity"."ownerId" AS "LibraryEntity_ownerId",
|
||||||
|
"LibraryEntity"."type" AS "LibraryEntity_type",
|
||||||
|
"LibraryEntity"."importPaths" AS "LibraryEntity_importPaths",
|
||||||
|
"LibraryEntity"."exclusionPatterns" AS "LibraryEntity_exclusionPatterns",
|
||||||
|
"LibraryEntity"."createdAt" AS "LibraryEntity_createdAt",
|
||||||
|
"LibraryEntity"."updatedAt" AS "LibraryEntity_updatedAt",
|
||||||
|
"LibraryEntity"."deletedAt" AS "LibraryEntity_deletedAt",
|
||||||
|
"LibraryEntity"."refreshedAt" AS "LibraryEntity_refreshedAt",
|
||||||
|
"LibraryEntity"."isVisible" AS "LibraryEntity_isVisible",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."id" AS "LibraryEntity__LibraryEntity_owner_id",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."name" AS "LibraryEntity__LibraryEntity_owner_name",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."avatarColor" AS "LibraryEntity__LibraryEntity_owner_avatarColor",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."isAdmin" AS "LibraryEntity__LibraryEntity_owner_isAdmin",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."email" AS "LibraryEntity__LibraryEntity_owner_email",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."storageLabel" AS "LibraryEntity__LibraryEntity_owner_storageLabel",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."externalPath" AS "LibraryEntity__LibraryEntity_owner_externalPath",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."oauthId" AS "LibraryEntity__LibraryEntity_owner_oauthId",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."profileImagePath" AS "LibraryEntity__LibraryEntity_owner_profileImagePath",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."shouldChangePassword" AS "LibraryEntity__LibraryEntity_owner_shouldChangePassword",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."createdAt" AS "LibraryEntity__LibraryEntity_owner_createdAt",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."deletedAt" AS "LibraryEntity__LibraryEntity_owner_deletedAt",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."updatedAt" AS "LibraryEntity__LibraryEntity_owner_updatedAt",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."memoriesEnabled" AS "LibraryEntity__LibraryEntity_owner_memoriesEnabled"
|
||||||
|
FROM
|
||||||
|
"libraries" "LibraryEntity"
|
||||||
|
LEFT JOIN "users" "LibraryEntity__LibraryEntity_owner" ON "LibraryEntity__LibraryEntity_owner"."id" = "LibraryEntity"."ownerId"
|
||||||
|
AND (
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
"LibraryEntity"."deletedAt" IS NULL
|
||||||
|
ORDER BY
|
||||||
|
"LibraryEntity"."createdAt" ASC
|
||||||
|
|
||||||
|
-- LibraryRepository.getAllDeleted
|
||||||
|
SELECT
|
||||||
|
"LibraryEntity"."id" AS "LibraryEntity_id",
|
||||||
|
"LibraryEntity"."name" AS "LibraryEntity_name",
|
||||||
|
"LibraryEntity"."ownerId" AS "LibraryEntity_ownerId",
|
||||||
|
"LibraryEntity"."type" AS "LibraryEntity_type",
|
||||||
|
"LibraryEntity"."importPaths" AS "LibraryEntity_importPaths",
|
||||||
|
"LibraryEntity"."exclusionPatterns" AS "LibraryEntity_exclusionPatterns",
|
||||||
|
"LibraryEntity"."createdAt" AS "LibraryEntity_createdAt",
|
||||||
|
"LibraryEntity"."updatedAt" AS "LibraryEntity_updatedAt",
|
||||||
|
"LibraryEntity"."deletedAt" AS "LibraryEntity_deletedAt",
|
||||||
|
"LibraryEntity"."refreshedAt" AS "LibraryEntity_refreshedAt",
|
||||||
|
"LibraryEntity"."isVisible" AS "LibraryEntity_isVisible",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."id" AS "LibraryEntity__LibraryEntity_owner_id",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."name" AS "LibraryEntity__LibraryEntity_owner_name",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."avatarColor" AS "LibraryEntity__LibraryEntity_owner_avatarColor",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."isAdmin" AS "LibraryEntity__LibraryEntity_owner_isAdmin",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."email" AS "LibraryEntity__LibraryEntity_owner_email",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."storageLabel" AS "LibraryEntity__LibraryEntity_owner_storageLabel",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."externalPath" AS "LibraryEntity__LibraryEntity_owner_externalPath",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."oauthId" AS "LibraryEntity__LibraryEntity_owner_oauthId",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."profileImagePath" AS "LibraryEntity__LibraryEntity_owner_profileImagePath",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."shouldChangePassword" AS "LibraryEntity__LibraryEntity_owner_shouldChangePassword",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."createdAt" AS "LibraryEntity__LibraryEntity_owner_createdAt",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."deletedAt" AS "LibraryEntity__LibraryEntity_owner_deletedAt",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."updatedAt" AS "LibraryEntity__LibraryEntity_owner_updatedAt",
|
||||||
|
"LibraryEntity__LibraryEntity_owner"."memoriesEnabled" AS "LibraryEntity__LibraryEntity_owner_memoriesEnabled"
|
||||||
|
FROM
|
||||||
|
"libraries" "LibraryEntity"
|
||||||
|
LEFT JOIN "users" "LibraryEntity__LibraryEntity_owner" ON "LibraryEntity__LibraryEntity_owner"."id" = "LibraryEntity"."ownerId"
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
"LibraryEntity"."isVisible" = $1
|
||||||
|
AND NOT ("LibraryEntity"."deletedAt" IS NULL)
|
||||||
|
)
|
||||||
|
ORDER BY
|
||||||
|
"LibraryEntity"."createdAt" ASC
|
||||||
|
|
||||||
|
-- LibraryRepository.getStatistics
|
||||||
|
SELECT
|
||||||
|
"libraries"."id" AS "libraries_id",
|
||||||
|
"libraries"."name" AS "libraries_name",
|
||||||
|
"libraries"."ownerId" AS "libraries_ownerId",
|
||||||
|
"libraries"."type" AS "libraries_type",
|
||||||
|
"libraries"."importPaths" AS "libraries_importPaths",
|
||||||
|
"libraries"."exclusionPatterns" AS "libraries_exclusionPatterns",
|
||||||
|
"libraries"."createdAt" AS "libraries_createdAt",
|
||||||
|
"libraries"."updatedAt" AS "libraries_updatedAt",
|
||||||
|
"libraries"."deletedAt" AS "libraries_deletedAt",
|
||||||
|
"libraries"."refreshedAt" AS "libraries_refreshedAt",
|
||||||
|
"libraries"."isVisible" AS "libraries_isVisible",
|
||||||
|
COUNT("assets"."id") FILTER (
|
||||||
|
WHERE
|
||||||
|
"assets"."type" = 'IMAGE'
|
||||||
|
AND "assets"."isVisible"
|
||||||
|
) AS "photos",
|
||||||
|
COUNT("assets"."id") FILTER (
|
||||||
|
WHERE
|
||||||
|
"assets"."type" = 'VIDEO'
|
||||||
|
AND "assets"."isVisible"
|
||||||
|
) AS "videos",
|
||||||
|
COALESCE(SUM("exif"."fileSizeInByte"), 0) AS "usage"
|
||||||
|
FROM
|
||||||
|
"libraries" "libraries"
|
||||||
|
LEFT JOIN "assets" "assets" ON "assets"."libraryId" = "libraries"."id"
|
||||||
|
AND ("assets"."deletedAt" IS NULL)
|
||||||
|
LEFT JOIN "exif" "exif" ON "exif"."assetId" = "assets"."id"
|
||||||
|
WHERE
|
||||||
|
("libraries"."id" = $1)
|
||||||
|
AND ("libraries"."deletedAt" IS NULL)
|
||||||
|
GROUP BY
|
||||||
|
"libraries"."id"
|
||||||
|
|
||||||
|
-- LibraryRepository.getOnlineAssetPaths
|
||||||
|
SELECT
|
||||||
|
"assets"."originalPath" AS "assets_originalPath"
|
||||||
|
FROM
|
||||||
|
"libraries" "library"
|
||||||
|
INNER JOIN "assets" "assets" ON "assets"."libraryId" = "library"."id"
|
||||||
|
AND ("assets"."deletedAt" IS NULL)
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
"library"."id" = $1
|
||||||
|
AND "assets"."isOffline" = false
|
||||||
|
)
|
||||||
|
AND ("library"."deletedAt" IS NULL)
|
||||||
|
|
||||||
|
-- LibraryRepository.getAssetIds
|
||||||
|
SELECT
|
||||||
|
"assets"."id" AS "assets_id"
|
||||||
|
FROM
|
||||||
|
"libraries" "library"
|
||||||
|
INNER JOIN "assets" "assets" ON "assets"."libraryId" = "library"."id"
|
||||||
|
AND ("assets"."deletedAt" IS NULL)
|
||||||
|
WHERE
|
||||||
|
("library"."id" = $1)
|
||||||
|
AND ("library"."deletedAt" IS NULL)
|
18
server/src/infra/sql/move.repository.sql
Normal file
18
server/src/infra/sql/move.repository.sql
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
-- NOTE: This file is auto generated by ./sql-generator
|
||||||
|
|
||||||
|
-- MoveRepository.getByEntity
|
||||||
|
SELECT
|
||||||
|
"MoveEntity"."id" AS "MoveEntity_id",
|
||||||
|
"MoveEntity"."entityId" AS "MoveEntity_entityId",
|
||||||
|
"MoveEntity"."pathType" AS "MoveEntity_pathType",
|
||||||
|
"MoveEntity"."oldPath" AS "MoveEntity_oldPath",
|
||||||
|
"MoveEntity"."newPath" AS "MoveEntity_newPath"
|
||||||
|
FROM
|
||||||
|
"move_history" "MoveEntity"
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
"MoveEntity"."entityId" = $1
|
||||||
|
AND "MoveEntity"."pathType" = $2
|
||||||
|
)
|
||||||
|
LIMIT
|
||||||
|
1
|
1
server/src/infra/sql/partner.repository.sql
Normal file
1
server/src/infra/sql/partner.repository.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
-- NOTE: This file is auto generated by ./sql-generator
|
367
server/src/infra/sql/person.repository.sql
Normal file
367
server/src/infra/sql/person.repository.sql
Normal file
|
@ -0,0 +1,367 @@
|
||||||
|
-- NOTE: This file is auto generated by ./sql-generator
|
||||||
|
|
||||||
|
-- PersonRepository.reassignFaces
|
||||||
|
UPDATE "asset_faces"
|
||||||
|
SET
|
||||||
|
"personId" = $1
|
||||||
|
WHERE
|
||||||
|
"personId" = $2
|
||||||
|
|
||||||
|
-- PersonRepository.getAllFaces
|
||||||
|
SELECT
|
||||||
|
"AssetFaceEntity"."id" AS "AssetFaceEntity_id",
|
||||||
|
"AssetFaceEntity"."assetId" AS "AssetFaceEntity_assetId",
|
||||||
|
"AssetFaceEntity"."personId" AS "AssetFaceEntity_personId",
|
||||||
|
"AssetFaceEntity"."embedding" AS "AssetFaceEntity_embedding",
|
||||||
|
"AssetFaceEntity"."imageWidth" AS "AssetFaceEntity_imageWidth",
|
||||||
|
"AssetFaceEntity"."imageHeight" AS "AssetFaceEntity_imageHeight",
|
||||||
|
"AssetFaceEntity"."boundingBoxX1" AS "AssetFaceEntity_boundingBoxX1",
|
||||||
|
"AssetFaceEntity"."boundingBoxY1" AS "AssetFaceEntity_boundingBoxY1",
|
||||||
|
"AssetFaceEntity"."boundingBoxX2" AS "AssetFaceEntity_boundingBoxX2",
|
||||||
|
"AssetFaceEntity"."boundingBoxY2" AS "AssetFaceEntity_boundingBoxY2",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."id" AS "AssetFaceEntity__AssetFaceEntity_asset_id",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."deviceAssetId" AS "AssetFaceEntity__AssetFaceEntity_asset_deviceAssetId",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."ownerId" AS "AssetFaceEntity__AssetFaceEntity_asset_ownerId",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."libraryId" AS "AssetFaceEntity__AssetFaceEntity_asset_libraryId",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."deviceId" AS "AssetFaceEntity__AssetFaceEntity_asset_deviceId",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."type" AS "AssetFaceEntity__AssetFaceEntity_asset_type",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."originalPath" AS "AssetFaceEntity__AssetFaceEntity_asset_originalPath",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."resizePath" AS "AssetFaceEntity__AssetFaceEntity_asset_resizePath",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."webpPath" AS "AssetFaceEntity__AssetFaceEntity_asset_webpPath",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."thumbhash" AS "AssetFaceEntity__AssetFaceEntity_asset_thumbhash",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."encodedVideoPath" AS "AssetFaceEntity__AssetFaceEntity_asset_encodedVideoPath",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."createdAt" AS "AssetFaceEntity__AssetFaceEntity_asset_createdAt",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."updatedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_updatedAt",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."deletedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_deletedAt",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."fileCreatedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_fileCreatedAt",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."localDateTime" AS "AssetFaceEntity__AssetFaceEntity_asset_localDateTime",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."fileModifiedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_fileModifiedAt",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."isFavorite" AS "AssetFaceEntity__AssetFaceEntity_asset_isFavorite",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."isArchived" AS "AssetFaceEntity__AssetFaceEntity_asset_isArchived",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."isExternal" AS "AssetFaceEntity__AssetFaceEntity_asset_isExternal",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."isReadOnly" AS "AssetFaceEntity__AssetFaceEntity_asset_isReadOnly",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."isOffline" AS "AssetFaceEntity__AssetFaceEntity_asset_isOffline",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."checksum" AS "AssetFaceEntity__AssetFaceEntity_asset_checksum",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."duration" AS "AssetFaceEntity__AssetFaceEntity_asset_duration",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."isVisible" AS "AssetFaceEntity__AssetFaceEntity_asset_isVisible",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."livePhotoVideoId" AS "AssetFaceEntity__AssetFaceEntity_asset_livePhotoVideoId",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."originalFileName" AS "AssetFaceEntity__AssetFaceEntity_asset_originalFileName",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."sidecarPath" AS "AssetFaceEntity__AssetFaceEntity_asset_sidecarPath",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."stackParentId" AS "AssetFaceEntity__AssetFaceEntity_asset_stackParentId"
|
||||||
|
FROM
|
||||||
|
"asset_faces" "AssetFaceEntity"
|
||||||
|
LEFT JOIN "assets" "AssetFaceEntity__AssetFaceEntity_asset" ON "AssetFaceEntity__AssetFaceEntity_asset"."id" = "AssetFaceEntity"."assetId"
|
||||||
|
|
||||||
|
-- PersonRepository.getAll
|
||||||
|
SELECT
|
||||||
|
"PersonEntity"."id" AS "PersonEntity_id",
|
||||||
|
"PersonEntity"."createdAt" AS "PersonEntity_createdAt",
|
||||||
|
"PersonEntity"."updatedAt" AS "PersonEntity_updatedAt",
|
||||||
|
"PersonEntity"."ownerId" AS "PersonEntity_ownerId",
|
||||||
|
"PersonEntity"."name" AS "PersonEntity_name",
|
||||||
|
"PersonEntity"."birthDate" AS "PersonEntity_birthDate",
|
||||||
|
"PersonEntity"."thumbnailPath" AS "PersonEntity_thumbnailPath",
|
||||||
|
"PersonEntity"."faceAssetId" AS "PersonEntity_faceAssetId",
|
||||||
|
"PersonEntity"."isHidden" AS "PersonEntity_isHidden"
|
||||||
|
FROM
|
||||||
|
"person" "PersonEntity"
|
||||||
|
|
||||||
|
-- PersonRepository.getAllWithoutThumbnail
|
||||||
|
SELECT
|
||||||
|
"PersonEntity"."id" AS "PersonEntity_id",
|
||||||
|
"PersonEntity"."createdAt" AS "PersonEntity_createdAt",
|
||||||
|
"PersonEntity"."updatedAt" AS "PersonEntity_updatedAt",
|
||||||
|
"PersonEntity"."ownerId" AS "PersonEntity_ownerId",
|
||||||
|
"PersonEntity"."name" AS "PersonEntity_name",
|
||||||
|
"PersonEntity"."birthDate" AS "PersonEntity_birthDate",
|
||||||
|
"PersonEntity"."thumbnailPath" AS "PersonEntity_thumbnailPath",
|
||||||
|
"PersonEntity"."faceAssetId" AS "PersonEntity_faceAssetId",
|
||||||
|
"PersonEntity"."isHidden" AS "PersonEntity_isHidden"
|
||||||
|
FROM
|
||||||
|
"person" "PersonEntity"
|
||||||
|
WHERE
|
||||||
|
("PersonEntity"."thumbnailPath" = $1)
|
||||||
|
|
||||||
|
-- PersonRepository.getAllForUser
|
||||||
|
SELECT
|
||||||
|
"person"."id" AS "person_id",
|
||||||
|
"person"."createdAt" AS "person_createdAt",
|
||||||
|
"person"."updatedAt" AS "person_updatedAt",
|
||||||
|
"person"."ownerId" AS "person_ownerId",
|
||||||
|
"person"."name" AS "person_name",
|
||||||
|
"person"."birthDate" AS "person_birthDate",
|
||||||
|
"person"."thumbnailPath" AS "person_thumbnailPath",
|
||||||
|
"person"."faceAssetId" AS "person_faceAssetId",
|
||||||
|
"person"."isHidden" AS "person_isHidden"
|
||||||
|
FROM
|
||||||
|
"person" "person"
|
||||||
|
LEFT JOIN "asset_faces" "face" ON "face"."personId" = "person"."id"
|
||||||
|
INNER JOIN "assets" "asset" ON "asset"."id" = "face"."assetId"
|
||||||
|
AND ("asset"."deletedAt" IS NULL)
|
||||||
|
WHERE
|
||||||
|
"person"."ownerId" = $1
|
||||||
|
AND "person"."isHidden" = false
|
||||||
|
GROUP BY
|
||||||
|
"person"."id"
|
||||||
|
HAVING
|
||||||
|
"person"."name" != ''
|
||||||
|
OR COUNT("face"."assetId") >= $2
|
||||||
|
ORDER BY
|
||||||
|
"person"."isHidden" ASC,
|
||||||
|
NULLIF("person"."name", '') IS NULL ASC,
|
||||||
|
COUNT("face"."assetId") DESC,
|
||||||
|
NULLIF("person"."name", '') ASC NULLS LAST
|
||||||
|
LIMIT
|
||||||
|
500
|
||||||
|
|
||||||
|
-- PersonRepository.getAllWithoutFaces
|
||||||
|
SELECT
|
||||||
|
"person"."id" AS "person_id",
|
||||||
|
"person"."createdAt" AS "person_createdAt",
|
||||||
|
"person"."updatedAt" AS "person_updatedAt",
|
||||||
|
"person"."ownerId" AS "person_ownerId",
|
||||||
|
"person"."name" AS "person_name",
|
||||||
|
"person"."birthDate" AS "person_birthDate",
|
||||||
|
"person"."thumbnailPath" AS "person_thumbnailPath",
|
||||||
|
"person"."faceAssetId" AS "person_faceAssetId",
|
||||||
|
"person"."isHidden" AS "person_isHidden"
|
||||||
|
FROM
|
||||||
|
"person" "person"
|
||||||
|
LEFT JOIN "asset_faces" "face" ON "face"."personId" = "person"."id"
|
||||||
|
GROUP BY
|
||||||
|
"person"."id"
|
||||||
|
HAVING
|
||||||
|
COUNT("face"."assetId") = 0
|
||||||
|
|
||||||
|
-- PersonRepository.getById
|
||||||
|
SELECT
|
||||||
|
"PersonEntity"."id" AS "PersonEntity_id",
|
||||||
|
"PersonEntity"."createdAt" AS "PersonEntity_createdAt",
|
||||||
|
"PersonEntity"."updatedAt" AS "PersonEntity_updatedAt",
|
||||||
|
"PersonEntity"."ownerId" AS "PersonEntity_ownerId",
|
||||||
|
"PersonEntity"."name" AS "PersonEntity_name",
|
||||||
|
"PersonEntity"."birthDate" AS "PersonEntity_birthDate",
|
||||||
|
"PersonEntity"."thumbnailPath" AS "PersonEntity_thumbnailPath",
|
||||||
|
"PersonEntity"."faceAssetId" AS "PersonEntity_faceAssetId",
|
||||||
|
"PersonEntity"."isHidden" AS "PersonEntity_isHidden"
|
||||||
|
FROM
|
||||||
|
"person" "PersonEntity"
|
||||||
|
WHERE
|
||||||
|
("PersonEntity"."id" = $1)
|
||||||
|
LIMIT
|
||||||
|
1
|
||||||
|
|
||||||
|
-- PersonRepository.getByName
|
||||||
|
SELECT
|
||||||
|
"person"."id" AS "person_id",
|
||||||
|
"person"."createdAt" AS "person_createdAt",
|
||||||
|
"person"."updatedAt" AS "person_updatedAt",
|
||||||
|
"person"."ownerId" AS "person_ownerId",
|
||||||
|
"person"."name" AS "person_name",
|
||||||
|
"person"."birthDate" AS "person_birthDate",
|
||||||
|
"person"."thumbnailPath" AS "person_thumbnailPath",
|
||||||
|
"person"."faceAssetId" AS "person_faceAssetId",
|
||||||
|
"person"."isHidden" AS "person_isHidden"
|
||||||
|
FROM
|
||||||
|
"person" "person"
|
||||||
|
LEFT JOIN "asset_faces" "face" ON "face"."personId" = "person"."id"
|
||||||
|
WHERE
|
||||||
|
"person"."ownerId" = $1
|
||||||
|
AND (
|
||||||
|
LOWER("person"."name") LIKE $2
|
||||||
|
OR LOWER("person"."name") LIKE $3
|
||||||
|
)
|
||||||
|
GROUP BY
|
||||||
|
"person"."id"
|
||||||
|
ORDER BY
|
||||||
|
COUNT("face"."assetId") DESC
|
||||||
|
LIMIT
|
||||||
|
20
|
||||||
|
|
||||||
|
-- PersonRepository.getStatistics
|
||||||
|
SELECT DISTINCT
|
||||||
|
COUNT(DISTINCT ("face"."id")) AS "cnt"
|
||||||
|
FROM
|
||||||
|
"asset_faces" "face"
|
||||||
|
LEFT JOIN "assets" "asset" ON "asset"."id" = "face"."assetId"
|
||||||
|
AND ("asset"."deletedAt" IS NULL)
|
||||||
|
WHERE
|
||||||
|
"face"."personId" = $1
|
||||||
|
AND "asset"."isArchived" = false
|
||||||
|
AND "asset"."deletedAt" IS NULL
|
||||||
|
AND "asset"."livePhotoVideoId" IS NULL
|
||||||
|
|
||||||
|
-- PersonRepository.getAssets
|
||||||
|
SELECT DISTINCT
|
||||||
|
"distinctAlias"."AssetEntity_id" AS "ids_AssetEntity_id",
|
||||||
|
"distinctAlias"."AssetEntity_fileCreatedAt"
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
"AssetEntity"."id" AS "AssetEntity_id",
|
||||||
|
"AssetEntity"."deviceAssetId" AS "AssetEntity_deviceAssetId",
|
||||||
|
"AssetEntity"."ownerId" AS "AssetEntity_ownerId",
|
||||||
|
"AssetEntity"."libraryId" AS "AssetEntity_libraryId",
|
||||||
|
"AssetEntity"."deviceId" AS "AssetEntity_deviceId",
|
||||||
|
"AssetEntity"."type" AS "AssetEntity_type",
|
||||||
|
"AssetEntity"."originalPath" AS "AssetEntity_originalPath",
|
||||||
|
"AssetEntity"."resizePath" AS "AssetEntity_resizePath",
|
||||||
|
"AssetEntity"."webpPath" AS "AssetEntity_webpPath",
|
||||||
|
"AssetEntity"."thumbhash" AS "AssetEntity_thumbhash",
|
||||||
|
"AssetEntity"."encodedVideoPath" AS "AssetEntity_encodedVideoPath",
|
||||||
|
"AssetEntity"."createdAt" AS "AssetEntity_createdAt",
|
||||||
|
"AssetEntity"."updatedAt" AS "AssetEntity_updatedAt",
|
||||||
|
"AssetEntity"."deletedAt" AS "AssetEntity_deletedAt",
|
||||||
|
"AssetEntity"."fileCreatedAt" AS "AssetEntity_fileCreatedAt",
|
||||||
|
"AssetEntity"."localDateTime" AS "AssetEntity_localDateTime",
|
||||||
|
"AssetEntity"."fileModifiedAt" AS "AssetEntity_fileModifiedAt",
|
||||||
|
"AssetEntity"."isFavorite" AS "AssetEntity_isFavorite",
|
||||||
|
"AssetEntity"."isArchived" AS "AssetEntity_isArchived",
|
||||||
|
"AssetEntity"."isExternal" AS "AssetEntity_isExternal",
|
||||||
|
"AssetEntity"."isReadOnly" AS "AssetEntity_isReadOnly",
|
||||||
|
"AssetEntity"."isOffline" AS "AssetEntity_isOffline",
|
||||||
|
"AssetEntity"."checksum" AS "AssetEntity_checksum",
|
||||||
|
"AssetEntity"."duration" AS "AssetEntity_duration",
|
||||||
|
"AssetEntity"."isVisible" AS "AssetEntity_isVisible",
|
||||||
|
"AssetEntity"."livePhotoVideoId" AS "AssetEntity_livePhotoVideoId",
|
||||||
|
"AssetEntity"."originalFileName" AS "AssetEntity_originalFileName",
|
||||||
|
"AssetEntity"."sidecarPath" AS "AssetEntity_sidecarPath",
|
||||||
|
"AssetEntity"."stackParentId" AS "AssetEntity_stackParentId",
|
||||||
|
"AssetEntity__AssetEntity_faces"."id" AS "AssetEntity__AssetEntity_faces_id",
|
||||||
|
"AssetEntity__AssetEntity_faces"."assetId" AS "AssetEntity__AssetEntity_faces_assetId",
|
||||||
|
"AssetEntity__AssetEntity_faces"."personId" AS "AssetEntity__AssetEntity_faces_personId",
|
||||||
|
"AssetEntity__AssetEntity_faces"."embedding" AS "AssetEntity__AssetEntity_faces_embedding",
|
||||||
|
"AssetEntity__AssetEntity_faces"."imageWidth" AS "AssetEntity__AssetEntity_faces_imageWidth",
|
||||||
|
"AssetEntity__AssetEntity_faces"."imageHeight" AS "AssetEntity__AssetEntity_faces_imageHeight",
|
||||||
|
"AssetEntity__AssetEntity_faces"."boundingBoxX1" AS "AssetEntity__AssetEntity_faces_boundingBoxX1",
|
||||||
|
"AssetEntity__AssetEntity_faces"."boundingBoxY1" AS "AssetEntity__AssetEntity_faces_boundingBoxY1",
|
||||||
|
"AssetEntity__AssetEntity_faces"."boundingBoxX2" AS "AssetEntity__AssetEntity_faces_boundingBoxX2",
|
||||||
|
"AssetEntity__AssetEntity_faces"."boundingBoxY2" AS "AssetEntity__AssetEntity_faces_boundingBoxY2",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."id" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_id",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."createdAt" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_createdAt",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."updatedAt" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_updatedAt",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."ownerId" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_ownerId",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."name" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_name",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."birthDate" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_birthDate",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."thumbnailPath" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_thumbnailPath",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."faceAssetId" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_faceAssetId",
|
||||||
|
"8258e303a73a72cf6abb13d73fb592dde0d68280"."isHidden" AS "8258e303a73a72cf6abb13d73fb592dde0d68280_isHidden",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."assetId" AS "AssetEntity__AssetEntity_exifInfo_assetId",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."description" AS "AssetEntity__AssetEntity_exifInfo_description",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."exifImageWidth" AS "AssetEntity__AssetEntity_exifInfo_exifImageWidth",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."exifImageHeight" AS "AssetEntity__AssetEntity_exifInfo_exifImageHeight",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."fileSizeInByte" AS "AssetEntity__AssetEntity_exifInfo_fileSizeInByte",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."orientation" AS "AssetEntity__AssetEntity_exifInfo_orientation",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."dateTimeOriginal" AS "AssetEntity__AssetEntity_exifInfo_dateTimeOriginal",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."modifyDate" AS "AssetEntity__AssetEntity_exifInfo_modifyDate",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."timeZone" AS "AssetEntity__AssetEntity_exifInfo_timeZone",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."latitude" AS "AssetEntity__AssetEntity_exifInfo_latitude",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."longitude" AS "AssetEntity__AssetEntity_exifInfo_longitude",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."projectionType" AS "AssetEntity__AssetEntity_exifInfo_projectionType",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."city" AS "AssetEntity__AssetEntity_exifInfo_city",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."livePhotoCID" AS "AssetEntity__AssetEntity_exifInfo_livePhotoCID",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."state" AS "AssetEntity__AssetEntity_exifInfo_state",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."country" AS "AssetEntity__AssetEntity_exifInfo_country",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."make" AS "AssetEntity__AssetEntity_exifInfo_make",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."model" AS "AssetEntity__AssetEntity_exifInfo_model",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."lensModel" AS "AssetEntity__AssetEntity_exifInfo_lensModel",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."fNumber" AS "AssetEntity__AssetEntity_exifInfo_fNumber",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."focalLength" AS "AssetEntity__AssetEntity_exifInfo_focalLength",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."iso" AS "AssetEntity__AssetEntity_exifInfo_iso",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."exposureTime" AS "AssetEntity__AssetEntity_exifInfo_exposureTime",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."profileDescription" AS "AssetEntity__AssetEntity_exifInfo_profileDescription",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."colorspace" AS "AssetEntity__AssetEntity_exifInfo_colorspace",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."bitsPerSample" AS "AssetEntity__AssetEntity_exifInfo_bitsPerSample",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."fps" AS "AssetEntity__AssetEntity_exifInfo_fps",
|
||||||
|
"AssetEntity__AssetEntity_exifInfo"."exifTextSearchableColumn" AS "AssetEntity__AssetEntity_exifInfo_exifTextSearchableColumn"
|
||||||
|
FROM
|
||||||
|
"assets" "AssetEntity"
|
||||||
|
LEFT JOIN "asset_faces" "AssetEntity__AssetEntity_faces" ON "AssetEntity__AssetEntity_faces"."assetId" = "AssetEntity"."id"
|
||||||
|
LEFT JOIN "person" "8258e303a73a72cf6abb13d73fb592dde0d68280" ON "8258e303a73a72cf6abb13d73fb592dde0d68280"."id" = "AssetEntity__AssetEntity_faces"."personId"
|
||||||
|
LEFT JOIN "exif" "AssetEntity__AssetEntity_exifInfo" ON "AssetEntity__AssetEntity_exifInfo"."assetId" = "AssetEntity"."id"
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"AssetEntity__AssetEntity_faces"."personId" = $1
|
||||||
|
AND "AssetEntity"."isVisible" = $2
|
||||||
|
AND "AssetEntity"."isArchived" = $3
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND ("AssetEntity"."deletedAt" IS NULL)
|
||||||
|
) "distinctAlias"
|
||||||
|
ORDER BY
|
||||||
|
"distinctAlias"."AssetEntity_fileCreatedAt" DESC,
|
||||||
|
"AssetEntity_id" ASC
|
||||||
|
LIMIT
|
||||||
|
1000
|
||||||
|
|
||||||
|
-- PersonRepository.getFacesByIds
|
||||||
|
SELECT
|
||||||
|
"AssetFaceEntity"."id" AS "AssetFaceEntity_id",
|
||||||
|
"AssetFaceEntity"."assetId" AS "AssetFaceEntity_assetId",
|
||||||
|
"AssetFaceEntity"."personId" AS "AssetFaceEntity_personId",
|
||||||
|
"AssetFaceEntity"."embedding" AS "AssetFaceEntity_embedding",
|
||||||
|
"AssetFaceEntity"."imageWidth" AS "AssetFaceEntity_imageWidth",
|
||||||
|
"AssetFaceEntity"."imageHeight" AS "AssetFaceEntity_imageHeight",
|
||||||
|
"AssetFaceEntity"."boundingBoxX1" AS "AssetFaceEntity_boundingBoxX1",
|
||||||
|
"AssetFaceEntity"."boundingBoxY1" AS "AssetFaceEntity_boundingBoxY1",
|
||||||
|
"AssetFaceEntity"."boundingBoxX2" AS "AssetFaceEntity_boundingBoxX2",
|
||||||
|
"AssetFaceEntity"."boundingBoxY2" AS "AssetFaceEntity_boundingBoxY2",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."id" AS "AssetFaceEntity__AssetFaceEntity_asset_id",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."deviceAssetId" AS "AssetFaceEntity__AssetFaceEntity_asset_deviceAssetId",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."ownerId" AS "AssetFaceEntity__AssetFaceEntity_asset_ownerId",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."libraryId" AS "AssetFaceEntity__AssetFaceEntity_asset_libraryId",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."deviceId" AS "AssetFaceEntity__AssetFaceEntity_asset_deviceId",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."type" AS "AssetFaceEntity__AssetFaceEntity_asset_type",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."originalPath" AS "AssetFaceEntity__AssetFaceEntity_asset_originalPath",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."resizePath" AS "AssetFaceEntity__AssetFaceEntity_asset_resizePath",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."webpPath" AS "AssetFaceEntity__AssetFaceEntity_asset_webpPath",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."thumbhash" AS "AssetFaceEntity__AssetFaceEntity_asset_thumbhash",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."encodedVideoPath" AS "AssetFaceEntity__AssetFaceEntity_asset_encodedVideoPath",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."createdAt" AS "AssetFaceEntity__AssetFaceEntity_asset_createdAt",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."updatedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_updatedAt",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."deletedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_deletedAt",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."fileCreatedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_fileCreatedAt",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."localDateTime" AS "AssetFaceEntity__AssetFaceEntity_asset_localDateTime",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."fileModifiedAt" AS "AssetFaceEntity__AssetFaceEntity_asset_fileModifiedAt",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."isFavorite" AS "AssetFaceEntity__AssetFaceEntity_asset_isFavorite",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."isArchived" AS "AssetFaceEntity__AssetFaceEntity_asset_isArchived",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."isExternal" AS "AssetFaceEntity__AssetFaceEntity_asset_isExternal",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."isReadOnly" AS "AssetFaceEntity__AssetFaceEntity_asset_isReadOnly",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."isOffline" AS "AssetFaceEntity__AssetFaceEntity_asset_isOffline",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."checksum" AS "AssetFaceEntity__AssetFaceEntity_asset_checksum",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."duration" AS "AssetFaceEntity__AssetFaceEntity_asset_duration",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."isVisible" AS "AssetFaceEntity__AssetFaceEntity_asset_isVisible",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."livePhotoVideoId" AS "AssetFaceEntity__AssetFaceEntity_asset_livePhotoVideoId",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."originalFileName" AS "AssetFaceEntity__AssetFaceEntity_asset_originalFileName",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."sidecarPath" AS "AssetFaceEntity__AssetFaceEntity_asset_sidecarPath",
|
||||||
|
"AssetFaceEntity__AssetFaceEntity_asset"."stackParentId" AS "AssetFaceEntity__AssetFaceEntity_asset_stackParentId"
|
||||||
|
FROM
|
||||||
|
"asset_faces" "AssetFaceEntity"
|
||||||
|
LEFT JOIN "assets" "AssetFaceEntity__AssetFaceEntity_asset" ON "AssetFaceEntity__AssetFaceEntity_asset"."id" = "AssetFaceEntity"."assetId"
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
(
|
||||||
|
"AssetFaceEntity"."assetId" = $1
|
||||||
|
AND "AssetFaceEntity"."personId" = $2
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
-- PersonRepository.getRandomFace
|
||||||
|
SELECT
|
||||||
|
"AssetFaceEntity"."id" AS "AssetFaceEntity_id",
|
||||||
|
"AssetFaceEntity"."assetId" AS "AssetFaceEntity_assetId",
|
||||||
|
"AssetFaceEntity"."personId" AS "AssetFaceEntity_personId",
|
||||||
|
"AssetFaceEntity"."embedding" AS "AssetFaceEntity_embedding",
|
||||||
|
"AssetFaceEntity"."imageWidth" AS "AssetFaceEntity_imageWidth",
|
||||||
|
"AssetFaceEntity"."imageHeight" AS "AssetFaceEntity_imageHeight",
|
||||||
|
"AssetFaceEntity"."boundingBoxX1" AS "AssetFaceEntity_boundingBoxX1",
|
||||||
|
"AssetFaceEntity"."boundingBoxY1" AS "AssetFaceEntity_boundingBoxY1",
|
||||||
|
"AssetFaceEntity"."boundingBoxX2" AS "AssetFaceEntity_boundingBoxX2",
|
||||||
|
"AssetFaceEntity"."boundingBoxY2" AS "AssetFaceEntity_boundingBoxY2"
|
||||||
|
FROM
|
||||||
|
"asset_faces" "AssetFaceEntity"
|
||||||
|
WHERE
|
||||||
|
("AssetFaceEntity"."personId" = $1)
|
||||||
|
LIMIT
|
||||||
|
1
|
327
server/src/infra/sql/shared.link.repository.sql
Normal file
327
server/src/infra/sql/shared.link.repository.sql
Normal file
|
@ -0,0 +1,327 @@
|
||||||
|
-- NOTE: This file is auto generated by ./sql-generator
|
||||||
|
|
||||||
|
-- SharedLinkRepository.get
|
||||||
|
SELECT DISTINCT
|
||||||
|
"distinctAlias"."SharedLinkEntity_id" AS "ids_SharedLinkEntity_id",
|
||||||
|
"distinctAlias"."SharedLinkEntity_createdAt",
|
||||||
|
"distinctAlias"."SharedLinkEntity__SharedLinkEntity_assets_fileCreatedAt",
|
||||||
|
"distinctAlias"."4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_fileCreatedAt"
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
"SharedLinkEntity"."id" AS "SharedLinkEntity_id",
|
||||||
|
"SharedLinkEntity"."description" AS "SharedLinkEntity_description",
|
||||||
|
"SharedLinkEntity"."password" AS "SharedLinkEntity_password",
|
||||||
|
"SharedLinkEntity"."userId" AS "SharedLinkEntity_userId",
|
||||||
|
"SharedLinkEntity"."key" AS "SharedLinkEntity_key",
|
||||||
|
"SharedLinkEntity"."type" AS "SharedLinkEntity_type",
|
||||||
|
"SharedLinkEntity"."createdAt" AS "SharedLinkEntity_createdAt",
|
||||||
|
"SharedLinkEntity"."expiresAt" AS "SharedLinkEntity_expiresAt",
|
||||||
|
"SharedLinkEntity"."allowUpload" AS "SharedLinkEntity_allowUpload",
|
||||||
|
"SharedLinkEntity"."allowDownload" AS "SharedLinkEntity_allowDownload",
|
||||||
|
"SharedLinkEntity"."showExif" AS "SharedLinkEntity_showExif",
|
||||||
|
"SharedLinkEntity"."albumId" AS "SharedLinkEntity_albumId",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."id" AS "SharedLinkEntity__SharedLinkEntity_assets_id",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."deviceAssetId" AS "SharedLinkEntity__SharedLinkEntity_assets_deviceAssetId",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."ownerId" AS "SharedLinkEntity__SharedLinkEntity_assets_ownerId",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."libraryId" AS "SharedLinkEntity__SharedLinkEntity_assets_libraryId",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."deviceId" AS "SharedLinkEntity__SharedLinkEntity_assets_deviceId",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."type" AS "SharedLinkEntity__SharedLinkEntity_assets_type",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."originalPath" AS "SharedLinkEntity__SharedLinkEntity_assets_originalPath",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."resizePath" AS "SharedLinkEntity__SharedLinkEntity_assets_resizePath",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."webpPath" AS "SharedLinkEntity__SharedLinkEntity_assets_webpPath",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."thumbhash" AS "SharedLinkEntity__SharedLinkEntity_assets_thumbhash",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."encodedVideoPath" AS "SharedLinkEntity__SharedLinkEntity_assets_encodedVideoPath",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."createdAt" AS "SharedLinkEntity__SharedLinkEntity_assets_createdAt",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."updatedAt" AS "SharedLinkEntity__SharedLinkEntity_assets_updatedAt",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."deletedAt" AS "SharedLinkEntity__SharedLinkEntity_assets_deletedAt",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."fileCreatedAt" AS "SharedLinkEntity__SharedLinkEntity_assets_fileCreatedAt",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."localDateTime" AS "SharedLinkEntity__SharedLinkEntity_assets_localDateTime",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."fileModifiedAt" AS "SharedLinkEntity__SharedLinkEntity_assets_fileModifiedAt",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."isFavorite" AS "SharedLinkEntity__SharedLinkEntity_assets_isFavorite",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."isArchived" AS "SharedLinkEntity__SharedLinkEntity_assets_isArchived",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."isExternal" AS "SharedLinkEntity__SharedLinkEntity_assets_isExternal",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."isReadOnly" AS "SharedLinkEntity__SharedLinkEntity_assets_isReadOnly",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."isOffline" AS "SharedLinkEntity__SharedLinkEntity_assets_isOffline",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."checksum" AS "SharedLinkEntity__SharedLinkEntity_assets_checksum",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."duration" AS "SharedLinkEntity__SharedLinkEntity_assets_duration",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."isVisible" AS "SharedLinkEntity__SharedLinkEntity_assets_isVisible",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."livePhotoVideoId" AS "SharedLinkEntity__SharedLinkEntity_assets_livePhotoVideoId",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."originalFileName" AS "SharedLinkEntity__SharedLinkEntity_assets_originalFileName",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."sidecarPath" AS "SharedLinkEntity__SharedLinkEntity_assets_sidecarPath",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."stackParentId" AS "SharedLinkEntity__SharedLinkEntity_assets_stackParentId",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."assetId" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_assetId",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."description" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_description",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."exifImageWidth" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_exifImageWidth",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."exifImageHeight" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_exifImageHeight",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."fileSizeInByte" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_fileSizeInByte",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."orientation" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_orientation",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."dateTimeOriginal" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_dateTimeOriginal",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."modifyDate" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_modifyDate",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."timeZone" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_timeZone",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."latitude" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_latitude",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."longitude" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_longitude",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."projectionType" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_projectionType",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."city" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_city",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."livePhotoCID" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_livePhotoCID",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."state" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_state",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."country" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_country",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."make" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_make",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."model" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_model",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."lensModel" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_lensModel",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."fNumber" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_fNumber",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."focalLength" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_focalLength",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."iso" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_iso",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."exposureTime" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_exposureTime",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."profileDescription" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_profileDescription",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."colorspace" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_colorspace",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."bitsPerSample" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_bitsPerSample",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."fps" AS "9b1d35b344d838023994a3233afd6ffe098be6d8_fps",
|
||||||
|
"9b1d35b344d838023994a3233afd6ffe098be6d8"."exifTextSearchableColumn" AS "e18de9deffa83f81ac3c43b5e8c2f08dba727bf8",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_album"."id" AS "SharedLinkEntity__SharedLinkEntity_album_id",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_album"."ownerId" AS "SharedLinkEntity__SharedLinkEntity_album_ownerId",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_album"."albumName" AS "SharedLinkEntity__SharedLinkEntity_album_albumName",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_album"."description" AS "SharedLinkEntity__SharedLinkEntity_album_description",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_album"."createdAt" AS "SharedLinkEntity__SharedLinkEntity_album_createdAt",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_album"."updatedAt" AS "SharedLinkEntity__SharedLinkEntity_album_updatedAt",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_album"."deletedAt" AS "SharedLinkEntity__SharedLinkEntity_album_deletedAt",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_album"."albumThumbnailAssetId" AS "SharedLinkEntity__SharedLinkEntity_album_albumThumbnailAssetId",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_album"."isActivityEnabled" AS "SharedLinkEntity__SharedLinkEntity_album_isActivityEnabled",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."id" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_id",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."deviceAssetId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_deviceAssetId",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."ownerId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_ownerId",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."libraryId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_libraryId",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."deviceId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_deviceId",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."type" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_type",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."originalPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_originalPath",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."resizePath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_resizePath",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."webpPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_webpPath",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."thumbhash" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_thumbhash",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."encodedVideoPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_encodedVideoPath",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."createdAt" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_createdAt",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."updatedAt" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_updatedAt",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."deletedAt" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_deletedAt",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."fileCreatedAt" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_fileCreatedAt",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."localDateTime" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_localDateTime",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."fileModifiedAt" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_fileModifiedAt",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."isFavorite" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_isFavorite",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."isArchived" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_isArchived",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."isExternal" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_isExternal",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."isReadOnly" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_isReadOnly",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."isOffline" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_isOffline",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."checksum" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_checksum",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."duration" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_duration",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."isVisible" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_isVisible",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."livePhotoVideoId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_livePhotoVideoId",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."originalFileName" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_originalFileName",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."sidecarPath" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_sidecarPath",
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."stackParentId" AS "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_stackParentId",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."assetId" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_assetId",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."description" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_description",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."exifImageWidth" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_exifImageWidth",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."exifImageHeight" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_exifImageHeight",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."fileSizeInByte" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_fileSizeInByte",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."orientation" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_orientation",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."dateTimeOriginal" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_dateTimeOriginal",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."modifyDate" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_modifyDate",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."timeZone" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_timeZone",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."latitude" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_latitude",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."longitude" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_longitude",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."projectionType" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_projectionType",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."city" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_city",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."livePhotoCID" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_livePhotoCID",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."state" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_state",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."country" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_country",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."make" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_make",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."model" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_model",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."lensModel" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_lensModel",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."fNumber" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_fNumber",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."focalLength" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_focalLength",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."iso" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_iso",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."exposureTime" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_exposureTime",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."profileDescription" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_profileDescription",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."colorspace" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_colorspace",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."bitsPerSample" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_bitsPerSample",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."fps" AS "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f_fps",
|
||||||
|
"d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."exifTextSearchableColumn" AS "96535c8046de591cca9b8c5825e6c5db502b0e6a",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."id" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_id",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."name" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_name",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."avatarColor" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_avatarColor",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."isAdmin" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_isAdmin",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."email" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_email",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."storageLabel" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_storageLabel",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."externalPath" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_externalPath",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."oauthId" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_oauthId",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."profileImagePath" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_profileImagePath",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."shouldChangePassword" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_shouldChangePassword",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."createdAt" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_createdAt",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."deletedAt" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_deletedAt",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."updatedAt" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_updatedAt",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."memoriesEnabled" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_memoriesEnabled"
|
||||||
|
FROM
|
||||||
|
"shared_links" "SharedLinkEntity"
|
||||||
|
LEFT JOIN "shared_link__asset" "SharedLinkEntity__SharedLinkEntity_assets_SharedLinkEntity" ON "SharedLinkEntity__SharedLinkEntity_assets_SharedLinkEntity"."sharedLinksId" = "SharedLinkEntity"."id"
|
||||||
|
LEFT JOIN "assets" "SharedLinkEntity__SharedLinkEntity_assets" ON "SharedLinkEntity__SharedLinkEntity_assets"."id" = "SharedLinkEntity__SharedLinkEntity_assets_SharedLinkEntity"."assetsId"
|
||||||
|
AND (
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
LEFT JOIN "exif" "9b1d35b344d838023994a3233afd6ffe098be6d8" ON "9b1d35b344d838023994a3233afd6ffe098be6d8"."assetId" = "SharedLinkEntity__SharedLinkEntity_assets"."id"
|
||||||
|
LEFT JOIN "albums" "SharedLinkEntity__SharedLinkEntity_album" ON "SharedLinkEntity__SharedLinkEntity_album"."id" = "SharedLinkEntity"."albumId"
|
||||||
|
AND (
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_album"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
LEFT JOIN "albums_assets_assets" "760f12c00d97bdcec1ce224d1e3bf449859942b6" ON "760f12c00d97bdcec1ce224d1e3bf449859942b6"."albumsId" = "SharedLinkEntity__SharedLinkEntity_album"."id"
|
||||||
|
LEFT JOIN "assets" "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6" ON "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."id" = "760f12c00d97bdcec1ce224d1e3bf449859942b6"."assetsId"
|
||||||
|
AND (
|
||||||
|
"4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
LEFT JOIN "exif" "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f" ON "d9f2f4dd8920bad1d6907cdb1d699732daff3c2f"."assetId" = "4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6"."id"
|
||||||
|
LEFT JOIN "users" "6d7fd45329a05fd86b3dbcacde87fe76e33a422d" ON "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."id" = "SharedLinkEntity__SharedLinkEntity_album"."ownerId"
|
||||||
|
AND (
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
(
|
||||||
|
"SharedLinkEntity"."id" = $1
|
||||||
|
AND "SharedLinkEntity"."userId" = $2
|
||||||
|
)
|
||||||
|
) "distinctAlias"
|
||||||
|
ORDER BY
|
||||||
|
"distinctAlias"."SharedLinkEntity_createdAt" DESC,
|
||||||
|
"distinctAlias"."SharedLinkEntity__SharedLinkEntity_assets_fileCreatedAt" ASC,
|
||||||
|
"distinctAlias"."4a35f463ae8c5544ede95c4b6d9ce8c686b6bfe6_fileCreatedAt" ASC,
|
||||||
|
"SharedLinkEntity_id" ASC
|
||||||
|
LIMIT
|
||||||
|
1
|
||||||
|
|
||||||
|
-- SharedLinkRepository.getAll
|
||||||
|
SELECT
|
||||||
|
"SharedLinkEntity"."id" AS "SharedLinkEntity_id",
|
||||||
|
"SharedLinkEntity"."description" AS "SharedLinkEntity_description",
|
||||||
|
"SharedLinkEntity"."password" AS "SharedLinkEntity_password",
|
||||||
|
"SharedLinkEntity"."userId" AS "SharedLinkEntity_userId",
|
||||||
|
"SharedLinkEntity"."key" AS "SharedLinkEntity_key",
|
||||||
|
"SharedLinkEntity"."type" AS "SharedLinkEntity_type",
|
||||||
|
"SharedLinkEntity"."createdAt" AS "SharedLinkEntity_createdAt",
|
||||||
|
"SharedLinkEntity"."expiresAt" AS "SharedLinkEntity_expiresAt",
|
||||||
|
"SharedLinkEntity"."allowUpload" AS "SharedLinkEntity_allowUpload",
|
||||||
|
"SharedLinkEntity"."allowDownload" AS "SharedLinkEntity_allowDownload",
|
||||||
|
"SharedLinkEntity"."showExif" AS "SharedLinkEntity_showExif",
|
||||||
|
"SharedLinkEntity"."albumId" AS "SharedLinkEntity_albumId",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."id" AS "SharedLinkEntity__SharedLinkEntity_assets_id",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."deviceAssetId" AS "SharedLinkEntity__SharedLinkEntity_assets_deviceAssetId",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."ownerId" AS "SharedLinkEntity__SharedLinkEntity_assets_ownerId",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."libraryId" AS "SharedLinkEntity__SharedLinkEntity_assets_libraryId",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."deviceId" AS "SharedLinkEntity__SharedLinkEntity_assets_deviceId",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."type" AS "SharedLinkEntity__SharedLinkEntity_assets_type",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."originalPath" AS "SharedLinkEntity__SharedLinkEntity_assets_originalPath",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."resizePath" AS "SharedLinkEntity__SharedLinkEntity_assets_resizePath",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."webpPath" AS "SharedLinkEntity__SharedLinkEntity_assets_webpPath",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."thumbhash" AS "SharedLinkEntity__SharedLinkEntity_assets_thumbhash",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."encodedVideoPath" AS "SharedLinkEntity__SharedLinkEntity_assets_encodedVideoPath",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."createdAt" AS "SharedLinkEntity__SharedLinkEntity_assets_createdAt",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."updatedAt" AS "SharedLinkEntity__SharedLinkEntity_assets_updatedAt",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."deletedAt" AS "SharedLinkEntity__SharedLinkEntity_assets_deletedAt",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."fileCreatedAt" AS "SharedLinkEntity__SharedLinkEntity_assets_fileCreatedAt",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."localDateTime" AS "SharedLinkEntity__SharedLinkEntity_assets_localDateTime",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."fileModifiedAt" AS "SharedLinkEntity__SharedLinkEntity_assets_fileModifiedAt",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."isFavorite" AS "SharedLinkEntity__SharedLinkEntity_assets_isFavorite",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."isArchived" AS "SharedLinkEntity__SharedLinkEntity_assets_isArchived",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."isExternal" AS "SharedLinkEntity__SharedLinkEntity_assets_isExternal",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."isReadOnly" AS "SharedLinkEntity__SharedLinkEntity_assets_isReadOnly",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."isOffline" AS "SharedLinkEntity__SharedLinkEntity_assets_isOffline",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."checksum" AS "SharedLinkEntity__SharedLinkEntity_assets_checksum",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."duration" AS "SharedLinkEntity__SharedLinkEntity_assets_duration",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."isVisible" AS "SharedLinkEntity__SharedLinkEntity_assets_isVisible",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."livePhotoVideoId" AS "SharedLinkEntity__SharedLinkEntity_assets_livePhotoVideoId",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."originalFileName" AS "SharedLinkEntity__SharedLinkEntity_assets_originalFileName",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."sidecarPath" AS "SharedLinkEntity__SharedLinkEntity_assets_sidecarPath",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."stackParentId" AS "SharedLinkEntity__SharedLinkEntity_assets_stackParentId",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_album"."id" AS "SharedLinkEntity__SharedLinkEntity_album_id",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_album"."ownerId" AS "SharedLinkEntity__SharedLinkEntity_album_ownerId",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_album"."albumName" AS "SharedLinkEntity__SharedLinkEntity_album_albumName",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_album"."description" AS "SharedLinkEntity__SharedLinkEntity_album_description",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_album"."createdAt" AS "SharedLinkEntity__SharedLinkEntity_album_createdAt",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_album"."updatedAt" AS "SharedLinkEntity__SharedLinkEntity_album_updatedAt",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_album"."deletedAt" AS "SharedLinkEntity__SharedLinkEntity_album_deletedAt",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_album"."albumThumbnailAssetId" AS "SharedLinkEntity__SharedLinkEntity_album_albumThumbnailAssetId",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_album"."isActivityEnabled" AS "SharedLinkEntity__SharedLinkEntity_album_isActivityEnabled",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."id" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_id",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."name" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_name",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."avatarColor" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_avatarColor",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."isAdmin" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_isAdmin",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."email" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_email",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."storageLabel" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_storageLabel",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."externalPath" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_externalPath",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."oauthId" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_oauthId",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."profileImagePath" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_profileImagePath",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."shouldChangePassword" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_shouldChangePassword",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."createdAt" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_createdAt",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."deletedAt" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_deletedAt",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."updatedAt" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_updatedAt",
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."memoriesEnabled" AS "6d7fd45329a05fd86b3dbcacde87fe76e33a422d_memoriesEnabled"
|
||||||
|
FROM
|
||||||
|
"shared_links" "SharedLinkEntity"
|
||||||
|
LEFT JOIN "shared_link__asset" "SharedLinkEntity__SharedLinkEntity_assets_SharedLinkEntity" ON "SharedLinkEntity__SharedLinkEntity_assets_SharedLinkEntity"."sharedLinksId" = "SharedLinkEntity"."id"
|
||||||
|
LEFT JOIN "assets" "SharedLinkEntity__SharedLinkEntity_assets" ON "SharedLinkEntity__SharedLinkEntity_assets"."id" = "SharedLinkEntity__SharedLinkEntity_assets_SharedLinkEntity"."assetsId"
|
||||||
|
AND (
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_assets"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
LEFT JOIN "albums" "SharedLinkEntity__SharedLinkEntity_album" ON "SharedLinkEntity__SharedLinkEntity_album"."id" = "SharedLinkEntity"."albumId"
|
||||||
|
AND (
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_album"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
LEFT JOIN "users" "6d7fd45329a05fd86b3dbcacde87fe76e33a422d" ON "6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."id" = "SharedLinkEntity__SharedLinkEntity_album"."ownerId"
|
||||||
|
AND (
|
||||||
|
"6d7fd45329a05fd86b3dbcacde87fe76e33a422d"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
("SharedLinkEntity"."userId" = $1)
|
||||||
|
ORDER BY
|
||||||
|
"SharedLinkEntity"."createdAt" DESC
|
||||||
|
|
||||||
|
-- SharedLinkRepository.getByKey
|
||||||
|
SELECT DISTINCT
|
||||||
|
"distinctAlias"."SharedLinkEntity_id" AS "ids_SharedLinkEntity_id"
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
"SharedLinkEntity"."id" AS "SharedLinkEntity_id",
|
||||||
|
"SharedLinkEntity"."description" AS "SharedLinkEntity_description",
|
||||||
|
"SharedLinkEntity"."password" AS "SharedLinkEntity_password",
|
||||||
|
"SharedLinkEntity"."userId" AS "SharedLinkEntity_userId",
|
||||||
|
"SharedLinkEntity"."key" AS "SharedLinkEntity_key",
|
||||||
|
"SharedLinkEntity"."type" AS "SharedLinkEntity_type",
|
||||||
|
"SharedLinkEntity"."createdAt" AS "SharedLinkEntity_createdAt",
|
||||||
|
"SharedLinkEntity"."expiresAt" AS "SharedLinkEntity_expiresAt",
|
||||||
|
"SharedLinkEntity"."allowUpload" AS "SharedLinkEntity_allowUpload",
|
||||||
|
"SharedLinkEntity"."allowDownload" AS "SharedLinkEntity_allowDownload",
|
||||||
|
"SharedLinkEntity"."showExif" AS "SharedLinkEntity_showExif",
|
||||||
|
"SharedLinkEntity"."albumId" AS "SharedLinkEntity_albumId",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_user"."id" AS "SharedLinkEntity__SharedLinkEntity_user_id",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_user"."name" AS "SharedLinkEntity__SharedLinkEntity_user_name",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_user"."avatarColor" AS "SharedLinkEntity__SharedLinkEntity_user_avatarColor",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_user"."isAdmin" AS "SharedLinkEntity__SharedLinkEntity_user_isAdmin",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_user"."email" AS "SharedLinkEntity__SharedLinkEntity_user_email",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_user"."storageLabel" AS "SharedLinkEntity__SharedLinkEntity_user_storageLabel",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_user"."externalPath" AS "SharedLinkEntity__SharedLinkEntity_user_externalPath",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_user"."oauthId" AS "SharedLinkEntity__SharedLinkEntity_user_oauthId",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_user"."profileImagePath" AS "SharedLinkEntity__SharedLinkEntity_user_profileImagePath",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_user"."shouldChangePassword" AS "SharedLinkEntity__SharedLinkEntity_user_shouldChangePassword",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_user"."createdAt" AS "SharedLinkEntity__SharedLinkEntity_user_createdAt",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_user"."deletedAt" AS "SharedLinkEntity__SharedLinkEntity_user_deletedAt",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_user"."updatedAt" AS "SharedLinkEntity__SharedLinkEntity_user_updatedAt",
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_user"."memoriesEnabled" AS "SharedLinkEntity__SharedLinkEntity_user_memoriesEnabled"
|
||||||
|
FROM
|
||||||
|
"shared_links" "SharedLinkEntity"
|
||||||
|
LEFT JOIN "users" "SharedLinkEntity__SharedLinkEntity_user" ON "SharedLinkEntity__SharedLinkEntity_user"."id" = "SharedLinkEntity"."userId"
|
||||||
|
AND (
|
||||||
|
"SharedLinkEntity__SharedLinkEntity_user"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
("SharedLinkEntity"."key" = $1)
|
||||||
|
) "distinctAlias"
|
||||||
|
ORDER BY
|
||||||
|
"SharedLinkEntity_id" ASC
|
||||||
|
LIMIT
|
||||||
|
1
|
13
server/src/infra/sql/system.config.repository.sql
Normal file
13
server/src/infra/sql/system.config.repository.sql
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
-- NOTE: This file is auto generated by ./sql-generator
|
||||||
|
|
||||||
|
-- SystemConfigRepository.load
|
||||||
|
SELECT
|
||||||
|
"SystemConfigEntity"."key" AS "SystemConfigEntity_key",
|
||||||
|
"SystemConfigEntity"."value" AS "SystemConfigEntity_value"
|
||||||
|
FROM
|
||||||
|
"system_config" "SystemConfigEntity"
|
||||||
|
|
||||||
|
-- SystemConfigRepository.deleteKeys
|
||||||
|
DELETE FROM "system_config"
|
||||||
|
WHERE
|
||||||
|
"key" IN ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
1
server/src/infra/sql/system.metadata.repository.sql
Normal file
1
server/src/infra/sql/system.metadata.repository.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
-- NOTE: This file is auto generated by ./sql-generator
|
1
server/src/infra/sql/tag.repository.sql
Normal file
1
server/src/infra/sql/tag.repository.sql
Normal file
|
@ -0,0 +1 @@
|
||||||
|
-- NOTE: This file is auto generated by ./sql-generator
|
143
server/src/infra/sql/user.repository.sql
Normal file
143
server/src/infra/sql/user.repository.sql
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
-- NOTE: This file is auto generated by ./sql-generator
|
||||||
|
|
||||||
|
-- UserRepository.getAdmin
|
||||||
|
SELECT
|
||||||
|
"UserEntity"."id" AS "UserEntity_id",
|
||||||
|
"UserEntity"."name" AS "UserEntity_name",
|
||||||
|
"UserEntity"."avatarColor" AS "UserEntity_avatarColor",
|
||||||
|
"UserEntity"."isAdmin" AS "UserEntity_isAdmin",
|
||||||
|
"UserEntity"."email" AS "UserEntity_email",
|
||||||
|
"UserEntity"."storageLabel" AS "UserEntity_storageLabel",
|
||||||
|
"UserEntity"."externalPath" AS "UserEntity_externalPath",
|
||||||
|
"UserEntity"."oauthId" AS "UserEntity_oauthId",
|
||||||
|
"UserEntity"."profileImagePath" AS "UserEntity_profileImagePath",
|
||||||
|
"UserEntity"."shouldChangePassword" AS "UserEntity_shouldChangePassword",
|
||||||
|
"UserEntity"."createdAt" AS "UserEntity_createdAt",
|
||||||
|
"UserEntity"."deletedAt" AS "UserEntity_deletedAt",
|
||||||
|
"UserEntity"."updatedAt" AS "UserEntity_updatedAt",
|
||||||
|
"UserEntity"."memoriesEnabled" AS "UserEntity_memoriesEnabled"
|
||||||
|
FROM
|
||||||
|
"users" "UserEntity"
|
||||||
|
WHERE
|
||||||
|
(("UserEntity"."isAdmin" = $1))
|
||||||
|
AND ("UserEntity"."deletedAt" IS NULL)
|
||||||
|
LIMIT
|
||||||
|
1
|
||||||
|
|
||||||
|
-- UserRepository.hasAdmin
|
||||||
|
SELECT
|
||||||
|
1 AS "row_exists"
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
1 AS dummy_column
|
||||||
|
) "dummy_table"
|
||||||
|
WHERE
|
||||||
|
EXISTS (
|
||||||
|
SELECT
|
||||||
|
1
|
||||||
|
FROM
|
||||||
|
"users" "UserEntity"
|
||||||
|
WHERE
|
||||||
|
(("UserEntity"."isAdmin" = $1))
|
||||||
|
AND ("UserEntity"."deletedAt" IS NULL)
|
||||||
|
)
|
||||||
|
LIMIT
|
||||||
|
1
|
||||||
|
|
||||||
|
-- UserRepository.getByEmail
|
||||||
|
SELECT
|
||||||
|
"user"."id" AS "user_id",
|
||||||
|
"user"."name" AS "user_name",
|
||||||
|
"user"."avatarColor" AS "user_avatarColor",
|
||||||
|
"user"."isAdmin" AS "user_isAdmin",
|
||||||
|
"user"."email" AS "user_email",
|
||||||
|
"user"."storageLabel" AS "user_storageLabel",
|
||||||
|
"user"."externalPath" AS "user_externalPath",
|
||||||
|
"user"."oauthId" AS "user_oauthId",
|
||||||
|
"user"."profileImagePath" AS "user_profileImagePath",
|
||||||
|
"user"."shouldChangePassword" AS "user_shouldChangePassword",
|
||||||
|
"user"."createdAt" AS "user_createdAt",
|
||||||
|
"user"."deletedAt" AS "user_deletedAt",
|
||||||
|
"user"."updatedAt" AS "user_updatedAt",
|
||||||
|
"user"."memoriesEnabled" AS "user_memoriesEnabled"
|
||||||
|
FROM
|
||||||
|
"users" "user"
|
||||||
|
WHERE
|
||||||
|
("user"."email" = $1)
|
||||||
|
AND ("user"."deletedAt" IS NULL)
|
||||||
|
|
||||||
|
-- UserRepository.getByStorageLabel
|
||||||
|
SELECT
|
||||||
|
"UserEntity"."id" AS "UserEntity_id",
|
||||||
|
"UserEntity"."name" AS "UserEntity_name",
|
||||||
|
"UserEntity"."avatarColor" AS "UserEntity_avatarColor",
|
||||||
|
"UserEntity"."isAdmin" AS "UserEntity_isAdmin",
|
||||||
|
"UserEntity"."email" AS "UserEntity_email",
|
||||||
|
"UserEntity"."storageLabel" AS "UserEntity_storageLabel",
|
||||||
|
"UserEntity"."externalPath" AS "UserEntity_externalPath",
|
||||||
|
"UserEntity"."oauthId" AS "UserEntity_oauthId",
|
||||||
|
"UserEntity"."profileImagePath" AS "UserEntity_profileImagePath",
|
||||||
|
"UserEntity"."shouldChangePassword" AS "UserEntity_shouldChangePassword",
|
||||||
|
"UserEntity"."createdAt" AS "UserEntity_createdAt",
|
||||||
|
"UserEntity"."deletedAt" AS "UserEntity_deletedAt",
|
||||||
|
"UserEntity"."updatedAt" AS "UserEntity_updatedAt",
|
||||||
|
"UserEntity"."memoriesEnabled" AS "UserEntity_memoriesEnabled"
|
||||||
|
FROM
|
||||||
|
"users" "UserEntity"
|
||||||
|
WHERE
|
||||||
|
(("UserEntity"."storageLabel" = $1))
|
||||||
|
AND ("UserEntity"."deletedAt" IS NULL)
|
||||||
|
LIMIT
|
||||||
|
1
|
||||||
|
|
||||||
|
-- UserRepository.getByOAuthId
|
||||||
|
SELECT
|
||||||
|
"UserEntity"."id" AS "UserEntity_id",
|
||||||
|
"UserEntity"."name" AS "UserEntity_name",
|
||||||
|
"UserEntity"."avatarColor" AS "UserEntity_avatarColor",
|
||||||
|
"UserEntity"."isAdmin" AS "UserEntity_isAdmin",
|
||||||
|
"UserEntity"."email" AS "UserEntity_email",
|
||||||
|
"UserEntity"."storageLabel" AS "UserEntity_storageLabel",
|
||||||
|
"UserEntity"."externalPath" AS "UserEntity_externalPath",
|
||||||
|
"UserEntity"."oauthId" AS "UserEntity_oauthId",
|
||||||
|
"UserEntity"."profileImagePath" AS "UserEntity_profileImagePath",
|
||||||
|
"UserEntity"."shouldChangePassword" AS "UserEntity_shouldChangePassword",
|
||||||
|
"UserEntity"."createdAt" AS "UserEntity_createdAt",
|
||||||
|
"UserEntity"."deletedAt" AS "UserEntity_deletedAt",
|
||||||
|
"UserEntity"."updatedAt" AS "UserEntity_updatedAt",
|
||||||
|
"UserEntity"."memoriesEnabled" AS "UserEntity_memoriesEnabled"
|
||||||
|
FROM
|
||||||
|
"users" "UserEntity"
|
||||||
|
WHERE
|
||||||
|
(("UserEntity"."oauthId" = $1))
|
||||||
|
AND ("UserEntity"."deletedAt" IS NULL)
|
||||||
|
LIMIT
|
||||||
|
1
|
||||||
|
|
||||||
|
-- UserRepository.getUserStats
|
||||||
|
SELECT
|
||||||
|
"users"."id" AS "userId",
|
||||||
|
"users"."name" AS "userName",
|
||||||
|
COUNT("assets"."id") FILTER (
|
||||||
|
WHERE
|
||||||
|
"assets"."type" = 'IMAGE'
|
||||||
|
AND "assets"."isVisible"
|
||||||
|
) AS "photos",
|
||||||
|
COUNT("assets"."id") FILTER (
|
||||||
|
WHERE
|
||||||
|
"assets"."type" = 'VIDEO'
|
||||||
|
AND "assets"."isVisible"
|
||||||
|
) AS "videos",
|
||||||
|
COALESCE(SUM("exif"."fileSizeInByte"), 0) AS "usage"
|
||||||
|
FROM
|
||||||
|
"users" "users"
|
||||||
|
LEFT JOIN "assets" "assets" ON "assets"."ownerId" = "users"."id"
|
||||||
|
AND ("assets"."deletedAt" IS NULL)
|
||||||
|
LEFT JOIN "exif" "exif" ON "exif"."assetId" = "assets"."id"
|
||||||
|
WHERE
|
||||||
|
"users"."deletedAt" IS NULL
|
||||||
|
GROUP BY
|
||||||
|
"users"."id"
|
||||||
|
ORDER BY
|
||||||
|
"users"."createdAt" ASC
|
46
server/src/infra/sql/user.token.repository.sql
Normal file
46
server/src/infra/sql/user.token.repository.sql
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
-- NOTE: This file is auto generated by ./sql-generator
|
||||||
|
|
||||||
|
-- UserTokenRepository.getByToken
|
||||||
|
SELECT DISTINCT
|
||||||
|
"distinctAlias"."UserTokenEntity_id" AS "ids_UserTokenEntity_id"
|
||||||
|
FROM
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
"UserTokenEntity"."id" AS "UserTokenEntity_id",
|
||||||
|
"UserTokenEntity"."userId" AS "UserTokenEntity_userId",
|
||||||
|
"UserTokenEntity"."createdAt" AS "UserTokenEntity_createdAt",
|
||||||
|
"UserTokenEntity"."updatedAt" AS "UserTokenEntity_updatedAt",
|
||||||
|
"UserTokenEntity"."deviceType" AS "UserTokenEntity_deviceType",
|
||||||
|
"UserTokenEntity"."deviceOS" AS "UserTokenEntity_deviceOS",
|
||||||
|
"UserTokenEntity__UserTokenEntity_user"."id" AS "UserTokenEntity__UserTokenEntity_user_id",
|
||||||
|
"UserTokenEntity__UserTokenEntity_user"."name" AS "UserTokenEntity__UserTokenEntity_user_name",
|
||||||
|
"UserTokenEntity__UserTokenEntity_user"."avatarColor" AS "UserTokenEntity__UserTokenEntity_user_avatarColor",
|
||||||
|
"UserTokenEntity__UserTokenEntity_user"."isAdmin" AS "UserTokenEntity__UserTokenEntity_user_isAdmin",
|
||||||
|
"UserTokenEntity__UserTokenEntity_user"."email" AS "UserTokenEntity__UserTokenEntity_user_email",
|
||||||
|
"UserTokenEntity__UserTokenEntity_user"."storageLabel" AS "UserTokenEntity__UserTokenEntity_user_storageLabel",
|
||||||
|
"UserTokenEntity__UserTokenEntity_user"."externalPath" AS "UserTokenEntity__UserTokenEntity_user_externalPath",
|
||||||
|
"UserTokenEntity__UserTokenEntity_user"."oauthId" AS "UserTokenEntity__UserTokenEntity_user_oauthId",
|
||||||
|
"UserTokenEntity__UserTokenEntity_user"."profileImagePath" AS "UserTokenEntity__UserTokenEntity_user_profileImagePath",
|
||||||
|
"UserTokenEntity__UserTokenEntity_user"."shouldChangePassword" AS "UserTokenEntity__UserTokenEntity_user_shouldChangePassword",
|
||||||
|
"UserTokenEntity__UserTokenEntity_user"."createdAt" AS "UserTokenEntity__UserTokenEntity_user_createdAt",
|
||||||
|
"UserTokenEntity__UserTokenEntity_user"."deletedAt" AS "UserTokenEntity__UserTokenEntity_user_deletedAt",
|
||||||
|
"UserTokenEntity__UserTokenEntity_user"."updatedAt" AS "UserTokenEntity__UserTokenEntity_user_updatedAt",
|
||||||
|
"UserTokenEntity__UserTokenEntity_user"."memoriesEnabled" AS "UserTokenEntity__UserTokenEntity_user_memoriesEnabled"
|
||||||
|
FROM
|
||||||
|
"user_token" "UserTokenEntity"
|
||||||
|
LEFT JOIN "users" "UserTokenEntity__UserTokenEntity_user" ON "UserTokenEntity__UserTokenEntity_user"."id" = "UserTokenEntity"."userId"
|
||||||
|
AND (
|
||||||
|
"UserTokenEntity__UserTokenEntity_user"."deletedAt" IS NULL
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
("UserTokenEntity"."token" = $1)
|
||||||
|
) "distinctAlias"
|
||||||
|
ORDER BY
|
||||||
|
"UserTokenEntity_id" ASC
|
||||||
|
LIMIT
|
||||||
|
1
|
||||||
|
|
||||||
|
-- UserTokenRepository.delete
|
||||||
|
DELETE FROM "user_token"
|
||||||
|
WHERE
|
||||||
|
"id" = $1
|
|
@ -1,5 +1,5 @@
|
||||||
import { bootstrap as adminCli } from './admin-cli/main';
|
import { bootstrap as admin } from './immich-admin/main';
|
||||||
import { bootstrap as immich } from './immich/main';
|
import { bootstrap as server } from './immich/main';
|
||||||
import { bootstrap as microservices } from './microservices/main';
|
import { bootstrap as microservices } from './microservices/main';
|
||||||
|
|
||||||
const immichApp = process.argv[2] || process.env.IMMICH_APP;
|
const immichApp = process.argv[2] || process.env.IMMICH_APP;
|
||||||
|
@ -12,13 +12,13 @@ function bootstrap() {
|
||||||
switch (immichApp) {
|
switch (immichApp) {
|
||||||
case 'immich':
|
case 'immich':
|
||||||
process.title = 'immich_server';
|
process.title = 'immich_server';
|
||||||
return immich();
|
return server();
|
||||||
case 'microservices':
|
case 'microservices':
|
||||||
process.title = 'immich_microservices';
|
process.title = 'immich_microservices';
|
||||||
return microservices();
|
return microservices();
|
||||||
case 'admin-cli':
|
case 'immich-admin':
|
||||||
process.title = 'immich_admin_cli';
|
process.title = 'immich_admin_cli';
|
||||||
return adminCli();
|
return admin();
|
||||||
default:
|
default:
|
||||||
console.log(`Invalid app name: ${immichApp}. Expected one of immich|microservices|cli`);
|
console.log(`Invalid app name: ${immichApp}. Expected one of immich|microservices|cli`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|
|
@ -84,6 +84,7 @@ export class AppService {
|
||||||
[JobName.QUEUE_SIDECAR]: (data) => this.metadataService.handleQueueSidecar(data),
|
[JobName.QUEUE_SIDECAR]: (data) => this.metadataService.handleQueueSidecar(data),
|
||||||
[JobName.SIDECAR_DISCOVERY]: (data) => this.metadataService.handleSidecarDiscovery(data),
|
[JobName.SIDECAR_DISCOVERY]: (data) => this.metadataService.handleSidecarDiscovery(data),
|
||||||
[JobName.SIDECAR_SYNC]: () => this.metadataService.handleSidecarSync(),
|
[JobName.SIDECAR_SYNC]: () => this.metadataService.handleSidecarSync(),
|
||||||
|
[JobName.SIDECAR_WRITE]: (data) => this.metadataService.handleSidecarWrite(data),
|
||||||
[JobName.LIBRARY_SCAN_ASSET]: (data) => this.libraryService.handleAssetRefresh(data),
|
[JobName.LIBRARY_SCAN_ASSET]: (data) => this.libraryService.handleAssetRefresh(data),
|
||||||
[JobName.LIBRARY_SCAN]: (data) => this.libraryService.handleQueueAssetRefresh(data),
|
[JobName.LIBRARY_SCAN]: (data) => this.libraryService.handleQueueAssetRefresh(data),
|
||||||
[JobName.LIBRARY_DELETE]: (data) => this.libraryService.handleDeleteLibrary(data),
|
[JobName.LIBRARY_DELETE]: (data) => this.libraryService.handleDeleteLibrary(data),
|
||||||
|
|
|
@ -32,4 +32,4 @@ if [ "$REDIS_PASSWORD_FILE" ]; then
|
||||||
unset REDIS_PASSWORD_FILE
|
unset REDIS_PASSWORD_FILE
|
||||||
fi
|
fi
|
||||||
|
|
||||||
exec node dist/main $@
|
exec node /usr/src/app/dist/main $@
|
||||||
|
|
|
@ -700,6 +700,54 @@ describe(`${AssetController.name} (e2e)`, () => {
|
||||||
expect(status).toEqual(200);
|
expect(status).toEqual(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should update date time original', async () => {
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.put(`/asset/${asset1.id}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ dateTimeOriginal: '2023-11-19T18:11:00.000-07:00' });
|
||||||
|
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
id: asset1.id,
|
||||||
|
exifInfo: expect.objectContaining({ dateTimeOriginal: '2023-11-20T01:11:00.000Z' }),
|
||||||
|
});
|
||||||
|
expect(status).toEqual(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject invalid gps coordinates', async () => {
|
||||||
|
for (const test of [
|
||||||
|
{ latitude: 12 },
|
||||||
|
{ longitude: 12 },
|
||||||
|
{ latitude: 12, longitude: 'abc' },
|
||||||
|
{ latitude: 'abc', longitude: 12 },
|
||||||
|
{ latitude: null, longitude: 12 },
|
||||||
|
{ latitude: 12, longitude: null },
|
||||||
|
{ latitude: 91, longitude: 12 },
|
||||||
|
{ latitude: -91, longitude: 12 },
|
||||||
|
{ latitude: 12, longitude: -181 },
|
||||||
|
{ latitude: 12, longitude: 181 },
|
||||||
|
]) {
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.put(`/asset/${asset1.id}`)
|
||||||
|
.send(test)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`);
|
||||||
|
expect(status).toBe(400);
|
||||||
|
expect(body).toEqual(errorStub.badRequest());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should update gps data', async () => {
|
||||||
|
const { status, body } = await request(server)
|
||||||
|
.put(`/asset/${asset1.id}`)
|
||||||
|
.set('Authorization', `Bearer ${user1.accessToken}`)
|
||||||
|
.send({ latitude: 12, longitude: 12 });
|
||||||
|
|
||||||
|
expect(body).toMatchObject({
|
||||||
|
id: asset1.id,
|
||||||
|
exifInfo: expect.objectContaining({ latitude: 12, longitude: 12 }),
|
||||||
|
});
|
||||||
|
expect(status).toEqual(200);
|
||||||
|
});
|
||||||
|
|
||||||
it('should set the description', async () => {
|
it('should set the description', async () => {
|
||||||
const { status, body } = await request(server)
|
const { status, body } = await request(server)
|
||||||
.put(`/asset/${asset1.id}`)
|
.put(`/asset/${asset1.id}`)
|
||||||
|
|
|
@ -22,11 +22,12 @@ export const newAccessRepositoryMock = (reset = true): IAccessRepositoryMock =>
|
||||||
hasAlbumOwnerAccess: jest.fn(),
|
hasAlbumOwnerAccess: jest.fn(),
|
||||||
hasCreateAccess: jest.fn(),
|
hasCreateAccess: jest.fn(),
|
||||||
},
|
},
|
||||||
|
|
||||||
asset: {
|
asset: {
|
||||||
hasOwnerAccess: jest.fn(),
|
checkOwnerAccess: jest.fn().mockResolvedValue(new Set()),
|
||||||
hasAlbumAccess: jest.fn(),
|
checkAlbumAccess: jest.fn().mockResolvedValue(new Set()),
|
||||||
hasPartnerAccess: jest.fn(),
|
checkPartnerAccess: jest.fn().mockResolvedValue(new Set()),
|
||||||
hasSharedLinkAccess: jest.fn(),
|
checkSharedLinkAccess: jest.fn().mockResolvedValue(new Set()),
|
||||||
},
|
},
|
||||||
|
|
||||||
album: {
|
album: {
|
||||||
|
|
|
@ -2,9 +2,10 @@ import { IMetadataRepository } from '@app/domain';
|
||||||
|
|
||||||
export const newMetadataRepositoryMock = (): jest.Mocked<IMetadataRepository> => {
|
export const newMetadataRepositoryMock = (): jest.Mocked<IMetadataRepository> => {
|
||||||
return {
|
return {
|
||||||
getExifTags: jest.fn(),
|
|
||||||
init: jest.fn(),
|
init: jest.fn(),
|
||||||
teardown: jest.fn(),
|
teardown: jest.fn(),
|
||||||
reverseGeocode: jest.fn(),
|
reverseGeocode: jest.fn(),
|
||||||
|
readTags: jest.fn(),
|
||||||
|
writeTags: jest.fn(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
36
web/src/api/open-api/api.ts
generated
36
web/src/api/open-api/api.ts
generated
|
@ -447,6 +447,12 @@ export interface AssetBulkDeleteDto {
|
||||||
* @interface AssetBulkUpdateDto
|
* @interface AssetBulkUpdateDto
|
||||||
*/
|
*/
|
||||||
export interface AssetBulkUpdateDto {
|
export interface AssetBulkUpdateDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof AssetBulkUpdateDto
|
||||||
|
*/
|
||||||
|
'dateTimeOriginal'?: string;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {Array<string>}
|
* @type {Array<string>}
|
||||||
|
@ -465,6 +471,18 @@ export interface AssetBulkUpdateDto {
|
||||||
* @memberof AssetBulkUpdateDto
|
* @memberof AssetBulkUpdateDto
|
||||||
*/
|
*/
|
||||||
'isFavorite'?: boolean;
|
'isFavorite'?: boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof AssetBulkUpdateDto
|
||||||
|
*/
|
||||||
|
'latitude'?: number;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof AssetBulkUpdateDto
|
||||||
|
*/
|
||||||
|
'longitude'?: number;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
|
@ -4137,6 +4155,12 @@ export interface UpdateAlbumDto {
|
||||||
* @interface UpdateAssetDto
|
* @interface UpdateAssetDto
|
||||||
*/
|
*/
|
||||||
export interface UpdateAssetDto {
|
export interface UpdateAssetDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof UpdateAssetDto
|
||||||
|
*/
|
||||||
|
'dateTimeOriginal'?: string;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @type {string}
|
* @type {string}
|
||||||
|
@ -4155,6 +4179,18 @@ export interface UpdateAssetDto {
|
||||||
* @memberof UpdateAssetDto
|
* @memberof UpdateAssetDto
|
||||||
*/
|
*/
|
||||||
'isFavorite'?: boolean;
|
'isFavorite'?: boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof UpdateAssetDto
|
||||||
|
*/
|
||||||
|
'latitude'?: number;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {number}
|
||||||
|
* @memberof UpdateAssetDto
|
||||||
|
*/
|
||||||
|
'longitude'?: number;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
import ThemeButton from '../shared-components/theme-button.svelte';
|
import ThemeButton from '../shared-components/theme-button.svelte';
|
||||||
import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
|
import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
|
||||||
import { mdiFileImagePlusOutline, mdiFolderDownloadOutline } from '@mdi/js';
|
import { mdiFileImagePlusOutline, mdiFolderDownloadOutline } from '@mdi/js';
|
||||||
|
import UpdatePanel from '../shared-components/update-panel.svelte';
|
||||||
|
|
||||||
export let sharedLink: SharedLinkResponseDto;
|
export let sharedLink: SharedLinkResponseDto;
|
||||||
export let user: UserResponseDto | undefined = undefined;
|
export let user: UserResponseDto | undefined = undefined;
|
||||||
|
@ -143,7 +144,7 @@
|
||||||
<main
|
<main
|
||||||
class="relative h-screen overflow-hidden bg-immich-bg px-6 pt-[var(--navbar-height)] dark:bg-immich-dark-bg sm:px-12 md:px-24 lg:px-40"
|
class="relative h-screen overflow-hidden bg-immich-bg px-6 pt-[var(--navbar-height)] dark:bg-immich-dark-bg sm:px-12 md:px-24 lg:px-40"
|
||||||
>
|
>
|
||||||
<AssetGrid {album} {user} {assetStore} {assetInteractionStore}>
|
<AssetGrid {album} {assetStore} {assetInteractionStore}>
|
||||||
<section class="pt-24">
|
<section class="pt-24">
|
||||||
<!-- ALBUM TITLE -->
|
<!-- ALBUM TITLE -->
|
||||||
<p
|
<p
|
||||||
|
@ -167,4 +168,5 @@
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
</AssetGrid>
|
</AssetGrid>
|
||||||
|
<UpdatePanel {assetStore} />
|
||||||
</main>
|
</main>
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
AssetTypeEnum,
|
AssetTypeEnum,
|
||||||
ReactionType,
|
ReactionType,
|
||||||
SharedLinkResponseDto,
|
SharedLinkResponseDto,
|
||||||
UserResponseDto,
|
|
||||||
} from '@api';
|
} from '@api';
|
||||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
|
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
|
@ -42,6 +41,7 @@
|
||||||
import { updateNumberOfComments } from '$lib/stores/activity.store';
|
import { updateNumberOfComments } from '$lib/stores/activity.store';
|
||||||
import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
|
import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
|
||||||
import SlideshowBar from './slideshow-bar.svelte';
|
import SlideshowBar from './slideshow-bar.svelte';
|
||||||
|
import { user } from '$lib/stores/user.store';
|
||||||
|
|
||||||
export let assetStore: AssetStore | null = null;
|
export let assetStore: AssetStore | null = null;
|
||||||
export let asset: AssetResponseDto;
|
export let asset: AssetResponseDto;
|
||||||
|
@ -51,7 +51,6 @@
|
||||||
export let force = false;
|
export let force = false;
|
||||||
export let withStacked = false;
|
export let withStacked = false;
|
||||||
export let isShared = false;
|
export let isShared = false;
|
||||||
export let user: UserResponseDto | null = null;
|
|
||||||
export let album: AlbumResponseDto | null = null;
|
export let album: AlbumResponseDto | null = null;
|
||||||
|
|
||||||
let reactions: ActivityResponseDto[] = [];
|
let reactions: ActivityResponseDto[] = [];
|
||||||
|
@ -143,10 +142,10 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFavorite = async () => {
|
const getFavorite = async () => {
|
||||||
if (album && user) {
|
if (album && $user) {
|
||||||
try {
|
try {
|
||||||
const { data } = await api.activityApi.getActivities({
|
const { data } = await api.activityApi.getActivities({
|
||||||
userId: user.id,
|
userId: $user.id,
|
||||||
assetId: asset.id,
|
assetId: asset.id,
|
||||||
albumId: album.id,
|
albumId: album.id,
|
||||||
type: ReactionType.Like,
|
type: ReactionType.Like,
|
||||||
|
@ -743,7 +742,7 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if isShared && album && isShowActivity && user}
|
{#if isShared && album && isShowActivity && $user}
|
||||||
<div
|
<div
|
||||||
transition:fly={{ duration: 150 }}
|
transition:fly={{ duration: 150 }}
|
||||||
id="activity-panel"
|
id="activity-panel"
|
||||||
|
@ -751,7 +750,7 @@
|
||||||
translate="yes"
|
translate="yes"
|
||||||
>
|
>
|
||||||
<ActivityViewer
|
<ActivityViewer
|
||||||
{user}
|
user={$user}
|
||||||
disabled={!album.isActivityEnabled}
|
disabled={!album.isActivityEnabled}
|
||||||
assetType={asset.type}
|
assetType={asset.type}
|
||||||
albumOwnerId={album.ownerId}
|
albumOwnerId={album.ownerId}
|
||||||
|
|
|
@ -5,22 +5,31 @@
|
||||||
import { getAssetFilename } from '$lib/utils/asset-utils';
|
import { getAssetFilename } from '$lib/utils/asset-utils';
|
||||||
import { AlbumResponseDto, AssetResponseDto, ThumbnailFormat, api } from '@api';
|
import { AlbumResponseDto, AssetResponseDto, ThumbnailFormat, api } from '@api';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher, onDestroy } from 'svelte';
|
||||||
import { slide } from 'svelte/transition';
|
import { slide } from 'svelte/transition';
|
||||||
import { asByteUnitString } from '../../utils/byte-units';
|
import { asByteUnitString } from '../../utils/byte-units';
|
||||||
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
||||||
import UserAvatar from '../shared-components/user-avatar.svelte';
|
import UserAvatar from '../shared-components/user-avatar.svelte';
|
||||||
|
import ChangeDate from '$lib/components/shared-components/change-date.svelte';
|
||||||
import {
|
import {
|
||||||
mdiCalendar,
|
mdiCalendar,
|
||||||
mdiCameraIris,
|
mdiCameraIris,
|
||||||
mdiClose,
|
mdiClose,
|
||||||
|
mdiPencil,
|
||||||
mdiImageOutline,
|
mdiImageOutline,
|
||||||
mdiMapMarkerOutline,
|
mdiMapMarkerOutline,
|
||||||
mdiInformationOutline,
|
mdiInformationOutline,
|
||||||
|
mdiEye,
|
||||||
|
mdiEyeOff,
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
|
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
||||||
import Map from '../shared-components/map/map.svelte';
|
import Map from '../shared-components/map/map.svelte';
|
||||||
|
import { websocketStore } from '$lib/stores/websocket';
|
||||||
import { AppRoute } from '$lib/constants';
|
import { AppRoute } from '$lib/constants';
|
||||||
|
import ChangeLocation from '../shared-components/change-location.svelte';
|
||||||
|
import { handleError } from '../../utils/handle-error';
|
||||||
|
import { user } from '$lib/stores/user.store';
|
||||||
|
|
||||||
export let asset: AssetResponseDto;
|
export let asset: AssetResponseDto;
|
||||||
export let albums: AlbumResponseDto[] = [];
|
export let albums: AlbumResponseDto[] = [];
|
||||||
|
@ -51,6 +60,17 @@
|
||||||
})();
|
})();
|
||||||
|
|
||||||
$: people = asset.people || [];
|
$: people = asset.people || [];
|
||||||
|
$: showingHiddenPeople = false;
|
||||||
|
|
||||||
|
const unsubscribe = websocketStore.onAssetUpdate.subscribe((assetUpdate) => {
|
||||||
|
if (assetUpdate && assetUpdate.id === asset.id) {
|
||||||
|
asset = assetUpdate;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
unsubscribe();
|
||||||
|
});
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
@ -79,9 +99,7 @@
|
||||||
try {
|
try {
|
||||||
await api.assetApi.updateAsset({
|
await api.assetApi.updateAsset({
|
||||||
id: asset.id,
|
id: asset.id,
|
||||||
updateAssetDto: {
|
updateAssetDto: { description },
|
||||||
description: description,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -90,6 +108,35 @@
|
||||||
|
|
||||||
let showAssetPath = false;
|
let showAssetPath = false;
|
||||||
const toggleAssetPath = () => (showAssetPath = !showAssetPath);
|
const toggleAssetPath = () => (showAssetPath = !showAssetPath);
|
||||||
|
|
||||||
|
let isShowChangeDate = false;
|
||||||
|
|
||||||
|
async function handleConfirmChangeDate(dateTimeOriginal: string) {
|
||||||
|
isShowChangeDate = false;
|
||||||
|
try {
|
||||||
|
await api.assetApi.updateAsset({ id: asset.id, updateAssetDto: { dateTimeOriginal } });
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, 'Unable to change date');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let isShowChangeLocation = false;
|
||||||
|
|
||||||
|
async function handleConfirmChangeLocation(gps: { lng: number; lat: number }) {
|
||||||
|
isShowChangeLocation = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.assetApi.updateAsset({
|
||||||
|
id: asset.id,
|
||||||
|
updateAssetDto: {
|
||||||
|
latitude: gps.lat,
|
||||||
|
longitude: gps.lng,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, 'Unable to change location');
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class="p-2 dark:bg-immich-dark-bg dark:text-immich-dark-fg">
|
<section class="p-2 dark:bg-immich-dark-bg dark:text-immich-dark-fg">
|
||||||
|
@ -134,25 +181,38 @@
|
||||||
|
|
||||||
{#if !api.isSharedLink && people.length > 0}
|
{#if !api.isSharedLink && people.length > 0}
|
||||||
<section class="px-4 py-4 text-sm">
|
<section class="px-4 py-4 text-sm">
|
||||||
<h2>PEOPLE</h2>
|
<div class="flex h-10 w-full items-center justify-between">
|
||||||
|
<h2>PEOPLE</h2>
|
||||||
|
{#if people.some((person) => person.isHidden)}
|
||||||
|
<CircleIconButton
|
||||||
|
title="Show hidden people"
|
||||||
|
icon={showingHiddenPeople ? mdiEyeOff : mdiEye}
|
||||||
|
padding="1"
|
||||||
|
on:click={() => (showingHiddenPeople = !showingHiddenPeople)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="mt-4 flex flex-wrap gap-2">
|
<div class="mt-2 flex flex-wrap gap-2">
|
||||||
{#each people as person (person.id)}
|
{#each people as person (person.id)}
|
||||||
<a
|
<a
|
||||||
href="/people/{person.id}?previousRoute={albumId ? `${AppRoute.ALBUMS}/${albumId}` : AppRoute.PHOTOS}"
|
href="/people/{person.id}?previousRoute={albumId ? `${AppRoute.ALBUMS}/${albumId}` : AppRoute.PHOTOS}"
|
||||||
class="w-[90px]"
|
class="w-[90px] {!showingHiddenPeople && person.isHidden ? 'hidden' : ''}"
|
||||||
on:click={() => dispatch('close-viewer')}
|
on:click={() => dispatch('close-viewer')}
|
||||||
>
|
>
|
||||||
<ImageThumbnail
|
<div class="relative">
|
||||||
curve
|
<ImageThumbnail
|
||||||
shadow
|
curve
|
||||||
url={api.getPeopleThumbnailUrl(person.id)}
|
shadow
|
||||||
altText={person.name}
|
url={api.getPeopleThumbnailUrl(person.id)}
|
||||||
title={person.name}
|
altText={person.name}
|
||||||
widthStyle="90px"
|
title={person.name}
|
||||||
heightStyle="90px"
|
widthStyle="90px"
|
||||||
thumbhash={null}
|
heightStyle="90px"
|
||||||
/>
|
thumbhash={null}
|
||||||
|
hidden={person.isHidden}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<p class="mt-1 truncate font-medium" title={person.name}>{person.name}</p>
|
<p class="mt-1 truncate font-medium" title={person.name}>{person.name}</p>
|
||||||
{#if person.birthDate}
|
{#if person.birthDate}
|
||||||
{@const personBirthDate = DateTime.fromISO(person.birthDate)}
|
{@const personBirthDate = DateTime.fromISO(person.birthDate)}
|
||||||
|
@ -191,41 +251,120 @@
|
||||||
<p class="text-sm">DETAILS</p>
|
<p class="text-sm">DETAILS</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if asset.exifInfo?.dateTimeOriginal}
|
{#if asset.exifInfo?.dateTimeOriginal && !asset.isReadOnly}
|
||||||
{@const assetDateTimeOriginal = DateTime.fromISO(asset.exifInfo.dateTimeOriginal, {
|
{@const assetDateTimeOriginal = DateTime.fromISO(asset.exifInfo.dateTimeOriginal, {
|
||||||
zone: asset.exifInfo.timeZone ?? undefined,
|
zone: asset.exifInfo.timeZone ?? undefined,
|
||||||
})}
|
})}
|
||||||
<div class="flex gap-4 py-4">
|
<div
|
||||||
<div>
|
class="flex justify-between place-items-start gap-4 py-4"
|
||||||
<Icon path={mdiCalendar} size="24" />
|
tabindex="0"
|
||||||
</div>
|
role="button"
|
||||||
|
on:click={() => (isOwner ? (isShowChangeDate = true) : null)}
|
||||||
|
on:keydown={(event) => (isOwner ? event.key === 'Enter' && (isShowChangeDate = true) : null)}
|
||||||
|
title={isOwner ? 'Edit date' : ''}
|
||||||
|
class:hover:dark:text-immich-dark-primary={isOwner}
|
||||||
|
class:hover:text-immich-primary={isOwner}
|
||||||
|
>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div>
|
||||||
|
<Icon path={mdiCalendar} size="24" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p>
|
|
||||||
{assetDateTimeOriginal.toLocaleString(
|
|
||||||
{
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric',
|
|
||||||
year: 'numeric',
|
|
||||||
},
|
|
||||||
{ locale: $locale },
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<div class="flex gap-2 text-sm">
|
|
||||||
<p>
|
<p>
|
||||||
{assetDateTimeOriginal.toLocaleString(
|
{assetDateTimeOriginal.toLocaleString(
|
||||||
{
|
{
|
||||||
weekday: 'short',
|
month: 'short',
|
||||||
hour: 'numeric',
|
day: 'numeric',
|
||||||
minute: '2-digit',
|
year: 'numeric',
|
||||||
timeZoneName: 'longOffset',
|
|
||||||
},
|
},
|
||||||
{ locale: $locale },
|
{ locale: $locale },
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
|
<div class="flex gap-2 text-sm">
|
||||||
|
<p>
|
||||||
|
{assetDateTimeOriginal.toLocaleString(
|
||||||
|
{
|
||||||
|
weekday: 'short',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: '2-digit',
|
||||||
|
timeZoneName: 'longOffset',
|
||||||
|
},
|
||||||
|
{ locale: $locale },
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>{/if}
|
|
||||||
|
{#if isOwner}
|
||||||
|
<button class="focus:outline-none">
|
||||||
|
<Icon path={mdiPencil} size="20" />
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{:else if !asset.exifInfo?.dateTimeOriginal && !asset.isReadOnly && $user && asset.ownerId === $user.id}
|
||||||
|
<div class="flex justify-between place-items-start gap-4 py-4">
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div>
|
||||||
|
<Icon path={mdiCalendar} size="24" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button class="focus:outline-none">
|
||||||
|
<Icon path={mdiPencil} size="20" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{:else if asset.exifInfo?.dateTimeOriginal && asset.isReadOnly}
|
||||||
|
{@const assetDateTimeOriginal = DateTime.fromISO(asset.exifInfo.dateTimeOriginal, {
|
||||||
|
zone: asset.exifInfo.timeZone ?? undefined,
|
||||||
|
})}
|
||||||
|
<div class="flex justify-between place-items-start gap-4 py-4">
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div>
|
||||||
|
<Icon path={mdiCalendar} size="24" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
{assetDateTimeOriginal.toLocaleString(
|
||||||
|
{
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric',
|
||||||
|
year: 'numeric',
|
||||||
|
},
|
||||||
|
{ locale: $locale },
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
<div class="flex gap-2 text-sm">
|
||||||
|
<p>
|
||||||
|
{assetDateTimeOriginal.toLocaleString(
|
||||||
|
{
|
||||||
|
weekday: 'short',
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: '2-digit',
|
||||||
|
timeZoneName: 'longOffset',
|
||||||
|
},
|
||||||
|
{ locale: $locale },
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
{#if isShowChangeDate}
|
||||||
|
{@const assetDateTimeOriginal = asset.exifInfo?.dateTimeOriginal
|
||||||
|
? DateTime.fromISO(asset.exifInfo.dateTimeOriginal, {
|
||||||
|
zone: asset.exifInfo.timeZone ?? undefined,
|
||||||
|
})
|
||||||
|
: DateTime.now()}
|
||||||
|
<ChangeDate
|
||||||
|
initialDate={assetDateTimeOriginal}
|
||||||
|
on:confirm={({ detail: date }) => handleConfirmChangeDate(date)}
|
||||||
|
on:cancel={() => (isShowChangeDate = false)}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if asset.exifInfo?.fileSizeInByte}
|
{#if asset.exifInfo?.fileSizeInByte}
|
||||||
<div class="flex gap-4 py-4">
|
<div class="flex gap-4 py-4">
|
||||||
|
@ -292,24 +431,88 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if asset.exifInfo?.city}
|
{#if asset.exifInfo?.city && !asset.isReadOnly}
|
||||||
<div class="flex gap-4 py-4">
|
<div
|
||||||
<div><Icon path={mdiMapMarkerOutline} size="24" /></div>
|
class="flex justify-between place-items-start gap-4 py-4"
|
||||||
|
on:click={() => (isOwner ? (isShowChangeLocation = true) : null)}
|
||||||
|
on:keydown={(event) => (isOwner ? event.key === 'Enter' && (isShowChangeLocation = true) : null)}
|
||||||
|
tabindex="0"
|
||||||
|
title={isOwner ? 'Edit location' : ''}
|
||||||
|
role="button"
|
||||||
|
class:hover:dark:text-immich-dark-primary={isOwner}
|
||||||
|
class:hover:text-immich-primary={isOwner}
|
||||||
|
>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div><Icon path={mdiMapMarkerOutline} size="24" /></div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p>{asset.exifInfo.city}</p>
|
<p>{asset.exifInfo.city}</p>
|
||||||
{#if asset.exifInfo?.state}
|
{#if asset.exifInfo?.state}
|
||||||
<div class="flex gap-2 text-sm">
|
<div class="flex gap-2 text-sm">
|
||||||
<p>{asset.exifInfo.state}</p>
|
<p>{asset.exifInfo.state}</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if asset.exifInfo?.country}
|
{#if asset.exifInfo?.country}
|
||||||
<div class="flex gap-2 text-sm">
|
<div class="flex gap-2 text-sm">
|
||||||
<p>{asset.exifInfo.country}</p>
|
<p>{asset.exifInfo.country}</p>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if isOwner}
|
||||||
|
<div>
|
||||||
|
<Icon path={mdiPencil} size="20" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{:else if !asset.exifInfo?.city && !asset.isReadOnly && $user && asset.ownerId === $user.id}
|
||||||
|
<div
|
||||||
|
class="flex justify-between place-items-start gap-4 py-4 rounded-lg pr-2 hover:dark:text-immich-dark-primary hover:text-immich-primary"
|
||||||
|
on:click={() => (isShowChangeLocation = true)}
|
||||||
|
on:keydown={(event) => event.key === 'Enter' && (isShowChangeLocation = true)}
|
||||||
|
tabindex="0"
|
||||||
|
role="button"
|
||||||
|
title="Add location"
|
||||||
|
>
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div>
|
||||||
|
<div><Icon path={mdiMapMarkerOutline} size="24" /></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>Add a location</p>
|
||||||
|
</div>
|
||||||
|
<div class="focus:outline-none">
|
||||||
|
<Icon path={mdiPencil} size="20" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{:else if asset.exifInfo?.city && asset.isReadOnly}
|
||||||
|
<div class="flex justify-between place-items-start gap-4 py-4">
|
||||||
|
<div class="flex gap-4">
|
||||||
|
<div><Icon path={mdiMapMarkerOutline} size="24" /></div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p>{asset.exifInfo.city}</p>
|
||||||
|
{#if asset.exifInfo?.state}
|
||||||
|
<div class="flex gap-2 text-sm">
|
||||||
|
<p>{asset.exifInfo.state}</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if asset.exifInfo?.country}
|
||||||
|
<div class="flex gap-2 text-sm">
|
||||||
|
<p>{asset.exifInfo.country}</p>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if isShowChangeLocation}
|
||||||
|
<ChangeLocation
|
||||||
|
{asset}
|
||||||
|
on:confirm={({ detail: gps }) => handleConfirmChangeLocation(gps)}
|
||||||
|
on:cancel={() => (isShowChangeLocation = false)}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
@ -7,8 +7,9 @@
|
||||||
|
|
||||||
export let color: Color = 'transparent-gray';
|
export let color: Color = 'transparent-gray';
|
||||||
export let disabled = false;
|
export let disabled = false;
|
||||||
|
export let fullwidth = false;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Button size="link" {color} shadow={false} rounded="lg" {disabled} on:click>
|
<Button size="link" {color} shadow={false} rounded="lg" {disabled} on:click {fullwidth}>
|
||||||
<slot />
|
<slot />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -29,10 +29,13 @@
|
||||||
icon?: string;
|
icon?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
let showMenu = false;
|
export let showMenu = false;
|
||||||
|
export let controlable = false;
|
||||||
|
|
||||||
const handleClickOutside = () => {
|
const handleClickOutside = () => {
|
||||||
showMenu = false;
|
if (!controlable) {
|
||||||
|
showMenu = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSelectOption = (option: T) => {
|
const handleSelectOption = (option: T) => {
|
||||||
|
@ -60,7 +63,7 @@
|
||||||
|
|
||||||
<div id="dropdown-button" use:clickOutside on:outclick={handleClickOutside} on:escape={handleClickOutside}>
|
<div id="dropdown-button" use:clickOutside on:outclick={handleClickOutside} on:escape={handleClickOutside}>
|
||||||
<!-- BUTTON TITLE -->
|
<!-- BUTTON TITLE -->
|
||||||
<LinkButton on:click={() => (showMenu = true)}>
|
<LinkButton on:click={() => (showMenu = true)} fullwidth>
|
||||||
<div class="flex place-items-center gap-2 text-sm">
|
<div class="flex place-items-center gap-2 text-sm">
|
||||||
{#if renderedSelectedOption?.icon}
|
{#if renderedSelectedOption?.icon}
|
||||||
<Icon path={renderedSelectedOption.icon} size="18" />
|
<Icon path={renderedSelectedOption.icon} size="18" />
|
||||||
|
@ -72,13 +75,13 @@
|
||||||
<!-- DROP DOWN MENU -->
|
<!-- DROP DOWN MENU -->
|
||||||
{#if showMenu}
|
{#if showMenu}
|
||||||
<div
|
<div
|
||||||
transition:fly={{ y: -30, x: 30, duration: 200 }}
|
transition:fly={{ y: -30, x: 30, duration: 100 }}
|
||||||
class="text-md absolute right-0 top-5 z-50 flex min-w-[250px] flex-col rounded-2xl bg-gray-100 py-4 text-black shadow-lg dark:bg-gray-700 dark:text-white"
|
class="text-md fixed z-50 flex min-w-[250px] max-h-[70vh] overflow-y-scroll immich-scrollbar flex-col rounded-2xl bg-gray-100 py-2 text-black shadow-lg dark:bg-gray-700 dark:text-white"
|
||||||
>
|
>
|
||||||
{#each options as option (option)}
|
{#each options as option (option)}
|
||||||
{@const renderedOption = renderOption(option)}
|
{@const renderedOption = renderOption(option)}
|
||||||
<button
|
<button
|
||||||
class="grid grid-cols-[20px,1fr] place-items-center gap-2 p-4 transition-all hover:bg-gray-300 dark:hover:bg-gray-800"
|
class="grid grid-cols-[20px,1fr] place-items-center p-2 transition-all hover:bg-gray-300 dark:hover:bg-gray-800"
|
||||||
on:click={() => handleSelectOption(option)}
|
on:click={() => handleSelectOption(option)}
|
||||||
>
|
>
|
||||||
{#if _.isEqual(selectedOption, option)}
|
{#if _.isEqual(selectedOption, option)}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||||
|
import { searchNameLocal } from '$lib/utils/person';
|
||||||
|
|
||||||
export let person: PersonResponseDto;
|
export let person: PersonResponseDto;
|
||||||
let people: PersonResponseDto[] = [];
|
let people: PersonResponseDto[] = [];
|
||||||
|
@ -56,12 +57,7 @@
|
||||||
}
|
}
|
||||||
if (!force) {
|
if (!force) {
|
||||||
if (people.length < 20 && name.startsWith(searchWord)) {
|
if (people.length < 20 && name.startsWith(searchWord)) {
|
||||||
people = peopleCopy
|
people = searchNameLocal(name, peopleCopy, 10);
|
||||||
.filter((person: PersonResponseDto) => {
|
|
||||||
const nameParts = person.name.split(' ');
|
|
||||||
return nameParts.some((splitName) => splitName.toLowerCase().startsWith(name.toLowerCase()));
|
|
||||||
})
|
|
||||||
.slice(0, 10);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import ChangeDate from '$lib/components/shared-components/change-date.svelte';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
import { api } from '@api';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
|
||||||
|
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
||||||
|
import { user } from '$lib/stores/user.store';
|
||||||
|
import { getSelectedAssets } from '$lib/utils/asset-utils';
|
||||||
|
export let menuItem = false;
|
||||||
|
const { clearSelect, getOwnedAssets } = getAssetControlContext();
|
||||||
|
|
||||||
|
let isShowChangeDate = false;
|
||||||
|
|
||||||
|
const handleConfirm = async (dateTimeOriginal: string) => {
|
||||||
|
isShowChangeDate = false;
|
||||||
|
const ids = getSelectedAssets(getOwnedAssets(), $user);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.assetApi.updateAssets({
|
||||||
|
assetBulkUpdateDto: { ids, dateTimeOriginal },
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, 'Unable to change date');
|
||||||
|
}
|
||||||
|
clearSelect();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if menuItem}
|
||||||
|
<MenuOption text="Change date" on:click={() => (isShowChangeDate = true)} />
|
||||||
|
{/if}
|
||||||
|
{#if isShowChangeDate}
|
||||||
|
<ChangeDate
|
||||||
|
initialDate={DateTime.now()}
|
||||||
|
on:confirm={({ detail: date }) => handleConfirm(date)}
|
||||||
|
on:cancel={() => (isShowChangeDate = false)}
|
||||||
|
/>
|
||||||
|
{/if}
|
|
@ -0,0 +1,42 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { api } from '@api';
|
||||||
|
import MenuOption from '../../shared-components/context-menu/menu-option.svelte';
|
||||||
|
import { getAssetControlContext } from '../asset-select-control-bar.svelte';
|
||||||
|
import ChangeLocation from '$lib/components/shared-components/change-location.svelte';
|
||||||
|
import { handleError } from '../../../utils/handle-error';
|
||||||
|
import { user } from '$lib/stores/user.store';
|
||||||
|
import { getSelectedAssets } from '$lib/utils/asset-utils';
|
||||||
|
|
||||||
|
export let menuItem = false;
|
||||||
|
const { clearSelect, getOwnedAssets } = getAssetControlContext();
|
||||||
|
|
||||||
|
let isShowChangeLocation = false;
|
||||||
|
|
||||||
|
async function handleConfirm(point: { lng: number; lat: number }) {
|
||||||
|
isShowChangeLocation = false;
|
||||||
|
const ids = getSelectedAssets(getOwnedAssets(), $user);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.assetApi.updateAssets({
|
||||||
|
assetBulkUpdateDto: {
|
||||||
|
ids,
|
||||||
|
latitude: point.lat,
|
||||||
|
longitude: point.lng,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, 'Unable to update location');
|
||||||
|
}
|
||||||
|
clearSelect();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if menuItem}
|
||||||
|
<MenuOption text="Change location" on:click={() => (isShowChangeLocation = true)} />
|
||||||
|
{/if}
|
||||||
|
{#if isShowChangeLocation}
|
||||||
|
<ChangeLocation
|
||||||
|
on:confirm={({ detail: point }) => handleConfirm(point)}
|
||||||
|
on:cancel={() => (isShowChangeLocation = false)}
|
||||||
|
/>
|
||||||
|
{/if}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue