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/**/*.ts -diff -merge
|
||||
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:
|
||||
ref: ${{ steps.get-ref.outputs.ref }}
|
||||
|
||||
- uses: actions/setup-java@v3
|
||||
- uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: "zulu"
|
||||
java-version: "12.x"
|
||||
|
|
25
.github/workflows/test.yml
vendored
25
.github/workflows/test.yml
vendored
|
@ -209,7 +209,7 @@ jobs:
|
|||
exit 1
|
||||
|
||||
generated-typeorm-migrations-up-to-date:
|
||||
name: TypeORM Migrations
|
||||
name: TypeORM Checks
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
postgres:
|
||||
|
@ -236,7 +236,7 @@ jobs:
|
|||
- name: Install server dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build the
|
||||
- name: Build the app
|
||||
run: npm run build
|
||||
|
||||
- name: Run existing migrations
|
||||
|
@ -252,13 +252,30 @@ jobs:
|
|||
with:
|
||||
files: |
|
||||
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'
|
||||
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 }}"
|
||||
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:
|
||||
# name: Run mobile end-to-end integration tests
|
||||
# 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
|
||||
|
||||
api:
|
||||
cd ./server && npm run api:generate
|
||||
npm --prefix server run api:generate
|
||||
|
||||
sql:
|
||||
npm --prefix server run sql:generate
|
||||
|
||||
attach-server:
|
||||
docker exec -it docker_immich-server_1 sh
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<p align="center">
|
||||
<img src="design/immich-logo.svg" width="150" title="Login mit eigener URL">
|
||||
</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/>
|
||||
<a href="https://immich.app">
|
||||
<img src="design/immich-screenshots.png" title="Haupt-Screenshot">
|
||||
|
@ -32,10 +32,10 @@
|
|||
|
||||
## Warnung
|
||||
|
||||
- ⚠️ Das Projekt befindet sich unter **sehr aktiver** Entwicklung.
|
||||
- ⚠️ Das Projekt befindet sich in **sehr aktiver** Entwicklung.
|
||||
- ⚠️ Erwarte Fehler und Änderungen mit Breaking-Changes.
|
||||
- ⚠️ **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
|
||||
|
||||
|
@ -54,9 +54,9 @@ Die Hauptdokumentation, inklusive Installationsanleitungen, ist unter https://im
|
|||
|
||||
## 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"
|
||||
Die Anmeldedaten
|
||||
|
@ -73,8 +73,8 @@ Spec: Free-tier Oracle VM - Amsterdam - 2.4Ghz quad-core ARM64 CPU, 24GB RAM
|
|||
| Funktionen | Mobil | Web |
|
||||
| ---------------------------------------------------- | ------ | ----- |
|
||||
| Fotos & Videos hochladen und ansehen | Ja | Ja |
|
||||
| Automatisches Backup wenn die App offen ist | Ja | n. a. |
|
||||
| Selektive Auswahl von Alben zum Backup | Ja | n. a. |
|
||||
| Automatisches Backup wenn die App geöffnet ist | Ja | n. a. |
|
||||
| Selektive Auswahl von Alben zum Sichern | Ja | n. a. |
|
||||
| Fotos und Videos auf das Gerät herunterladen | Ja | Ja |
|
||||
| Unterstützt mehrere Benutzer | 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 |
|
||||
| Metadaten anzeigen (EXIF, Karte) | 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. |
|
||||
| Virtuelles Scrollen | 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
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
- [Monatliche Spende](https://github.com/sponsors/alextran1502) via GitHub Sponsors
|
||||
- [Einmalige Spende](https://github.com/sponsors/alextran1502?frequency=one-time&sponsor=alextran1502) via GitHub Sponsors
|
||||
- [Monatliche Spende](https://github.com/sponsors/immich-app) 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/)
|
||||
- [buymeacoffee](https://www.buymeacoffee.com/altran1502)
|
||||
- Bitcoin: 1FvEp6P6NM8EZEkpGUFAN2LqJ1gxusNxZX
|
||||
- ZCash: u1smm4wvqegcp46zss2jf5xptchgeczp4rx7a0wu3mermf2wxahm26yyz5w9mw3f2p4emwlljxjumg774kgs8rntt9yags0whnzane4n67z4c7gppq4yyvcj404ne3r769prwzd9j8ntvqp44fa6d67sf7rmcfjmds3gmeceff4u8e92rh38nd30cr96xw6vfhk6scu4ws90ldzupr3sz
|
||||
|
||||
## Unterstützer
|
||||
## Mitwirkende
|
||||
<a href="https://github.com/alextran1502/immich/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=immich-app/immich" width="100%"/>
|
||||
</a>
|
||||
|
|
12
cli/package-lock.json
generated
12
cli/package-lock.json
generated
|
@ -1604,9 +1604,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.0.tgz",
|
||||
"integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==",
|
||||
"version": "20.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.1.tgz",
|
||||
"integrity": "sha512-T2qwhjWwGH81vUEx4EXmBKsTJRXFXNZTL4v0gi01+zyBmCwzE6TyHszqX01m+QHTEq+EZNo13NeJIdEqf+Myrg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
|
@ -7990,9 +7990,9 @@
|
|||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "20.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.0.tgz",
|
||||
"integrity": "sha512-D0WfRmU9TQ8I9PFx9Yc+EBHw+vSpIub4IDvQivcp26PtPrdMGAq5SDcpXEo/epqa/DXotVpekHiLNTg3iaKXBQ==",
|
||||
"version": "20.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.1.tgz",
|
||||
"integrity": "sha512-T2qwhjWwGH81vUEx4EXmBKsTJRXFXNZTL4v0gi01+zyBmCwzE6TyHszqX01m+QHTEq+EZNo13NeJIdEqf+Myrg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"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
|
||||
*/
|
||||
export interface AssetBulkUpdateDto {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AssetBulkUpdateDto
|
||||
*/
|
||||
'dateTimeOriginal'?: string;
|
||||
/**
|
||||
*
|
||||
* @type {Array<string>}
|
||||
|
@ -465,6 +471,18 @@ export interface AssetBulkUpdateDto {
|
|||
* @memberof AssetBulkUpdateDto
|
||||
*/
|
||||
'isFavorite'?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof AssetBulkUpdateDto
|
||||
*/
|
||||
'latitude'?: number;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof AssetBulkUpdateDto
|
||||
*/
|
||||
'longitude'?: number;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
|
@ -4137,6 +4155,12 @@ export interface UpdateAlbumDto {
|
|||
* @interface UpdateAssetDto
|
||||
*/
|
||||
export interface UpdateAssetDto {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof UpdateAssetDto
|
||||
*/
|
||||
'dateTimeOriginal'?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
|
@ -4155,6 +4179,18 @@ export interface UpdateAssetDto {
|
|||
* @memberof UpdateAssetDto
|
||||
*/
|
||||
'isFavorite'?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof UpdateAssetDto
|
||||
*/
|
||||
'latitude'?: number;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof UpdateAssetDto
|
||||
*/
|
||||
'longitude'?: number;
|
||||
}
|
||||
/**
|
||||
*
|
||||
|
|
|
@ -108,11 +108,11 @@ services:
|
|||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: redis:6.2-alpine@sha256:80cc8518800438c684a53ed829c621c94afd1087aaeb59b0d4343ed3e7bcf6c5
|
||||
image: redis:6.2-alpine@sha256:60e49e22fa5706cd8df7d5e0bc50ee9bab7c608039fa653c4d961014237cca46
|
||||
|
||||
database:
|
||||
container_name: immich_postgres
|
||||
image: postgres:14-alpine@sha256:50d9be76e9a90da4c781554955e0ffc79d9d5c4226838e64b36aacc97cbc35ad
|
||||
image: postgres:14-alpine@sha256:54916702f83d9330d355e078f69a7ba42f2cf2e530c8fe63423d6680d8da45b0
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
|
|
|
@ -65,12 +65,12 @@ services:
|
|||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: redis:6.2-alpine@sha256:80cc8518800438c684a53ed829c621c94afd1087aaeb59b0d4343ed3e7bcf6c5
|
||||
image: redis:6.2-alpine@sha256:60e49e22fa5706cd8df7d5e0bc50ee9bab7c608039fa653c4d961014237cca46
|
||||
restart: always
|
||||
|
||||
database:
|
||||
container_name: immich_postgres
|
||||
image: postgres:14-alpine@sha256:50d9be76e9a90da4c781554955e0ffc79d9d5c4226838e64b36aacc97cbc35ad
|
||||
image: postgres:14-alpine@sha256:54916702f83d9330d355e078f69a7ba42f2cf2e530c8fe63423d6680d8da45b0
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
|
|
|
@ -23,7 +23,7 @@ services:
|
|||
- database
|
||||
|
||||
database:
|
||||
image: postgres:14-alpine@sha256:50d9be76e9a90da4c781554955e0ffc79d9d5c4226838e64b36aacc97cbc35ad
|
||||
image: postgres:14-alpine@sha256:54916702f83d9330d355e078f69a7ba42f2cf2e530c8fe63423d6680d8da45b0
|
||||
command: -c fsync=off
|
||||
environment:
|
||||
POSTGRES_PASSWORD: postgres
|
||||
|
|
|
@ -69,12 +69,12 @@ services:
|
|||
|
||||
redis:
|
||||
container_name: immich_redis
|
||||
image: redis:6.2-alpine@sha256:80cc8518800438c684a53ed829c621c94afd1087aaeb59b0d4343ed3e7bcf6c5
|
||||
image: redis:6.2-alpine@sha256:60e49e22fa5706cd8df7d5e0bc50ee9bab7c608039fa653c4d961014237cca46
|
||||
restart: always
|
||||
|
||||
database:
|
||||
container_name: immich_postgres
|
||||
image: postgres:14-alpine@sha256:50d9be76e9a90da4c781554955e0ffc79d9d5c4226838e64b36aacc97cbc35ad
|
||||
image: postgres:14-alpine@sha256:54916702f83d9330d355e078f69a7ba42f2cf2e530c8fe63423d6680d8da45b0
|
||||
env_file:
|
||||
- .env
|
||||
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`.
|
||||
|
||||
```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
|
||||
|
@ -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:
|
||||
|
||||
```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
|
||||
|
|
|
@ -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 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
|
@ -13,7 +13,7 @@ ENV VIRTUAL_ENV="/opt/venv" PATH="/opt/venv/bin:${PATH}"
|
|||
COPY poetry.lock pyproject.toml ./
|
||||
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/*
|
||||
|
||||
|
|
3
mobile/openapi/doc/AssetBulkUpdateDto.md
generated
3
mobile/openapi/doc/AssetBulkUpdateDto.md
generated
|
@ -8,9 +8,12 @@ import 'package:openapi/api.dart';
|
|||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**dateTimeOriginal** | **String** | | [optional]
|
||||
**ids** | **List<String>** | | [default to const []]
|
||||
**isArchived** | **bool** | | [optional]
|
||||
**isFavorite** | **bool** | | [optional]
|
||||
**latitude** | **num** | | [optional]
|
||||
**longitude** | **num** | | [optional]
|
||||
**removeParent** | **bool** | | [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
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**dateTimeOriginal** | **String** | | [optional]
|
||||
**description** | **String** | | [optional]
|
||||
**isArchived** | **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)
|
||||
|
||||
|
|
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 {
|
||||
/// Returns a new [AssetBulkUpdateDto] instance.
|
||||
AssetBulkUpdateDto({
|
||||
this.dateTimeOriginal,
|
||||
this.ids = const [],
|
||||
this.isArchived,
|
||||
this.isFavorite,
|
||||
this.latitude,
|
||||
this.longitude,
|
||||
this.removeParent,
|
||||
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;
|
||||
|
||||
///
|
||||
|
@ -38,6 +49,22 @@ class AssetBulkUpdateDto {
|
|||
///
|
||||
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
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
@ -56,26 +83,37 @@ class AssetBulkUpdateDto {
|
|||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AssetBulkUpdateDto &&
|
||||
other.dateTimeOriginal == dateTimeOriginal &&
|
||||
other.ids == ids &&
|
||||
other.isArchived == isArchived &&
|
||||
other.isFavorite == isFavorite &&
|
||||
other.latitude == latitude &&
|
||||
other.longitude == longitude &&
|
||||
other.removeParent == removeParent &&
|
||||
other.stackParentId == stackParentId;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) +
|
||||
(ids.hashCode) +
|
||||
(isArchived == null ? 0 : isArchived!.hashCode) +
|
||||
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
||||
(latitude == null ? 0 : latitude!.hashCode) +
|
||||
(longitude == null ? 0 : longitude!.hashCode) +
|
||||
(removeParent == null ? 0 : removeParent!.hashCode) +
|
||||
(stackParentId == null ? 0 : stackParentId!.hashCode);
|
||||
|
||||
@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() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.dateTimeOriginal != null) {
|
||||
json[r'dateTimeOriginal'] = this.dateTimeOriginal;
|
||||
} else {
|
||||
// json[r'dateTimeOriginal'] = null;
|
||||
}
|
||||
json[r'ids'] = this.ids;
|
||||
if (this.isArchived != null) {
|
||||
json[r'isArchived'] = this.isArchived;
|
||||
|
@ -87,6 +125,16 @@ class AssetBulkUpdateDto {
|
|||
} else {
|
||||
// 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) {
|
||||
json[r'removeParent'] = this.removeParent;
|
||||
} else {
|
||||
|
@ -108,11 +156,18 @@ class AssetBulkUpdateDto {
|
|||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AssetBulkUpdateDto(
|
||||
dateTimeOriginal: mapValueOfType<String>(json, r'dateTimeOriginal'),
|
||||
ids: json[r'ids'] is List
|
||||
? (json[r'ids'] as List).cast<String>()
|
||||
: const [],
|
||||
isArchived: mapValueOfType<bool>(json, r'isArchived'),
|
||||
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'),
|
||||
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 {
|
||||
/// Returns a new [UpdateAssetDto] instance.
|
||||
UpdateAssetDto({
|
||||
this.dateTimeOriginal,
|
||||
this.description,
|
||||
this.isArchived,
|
||||
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
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
|
@ -42,24 +53,51 @@ class UpdateAssetDto {
|
|||
///
|
||||
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
|
||||
bool operator ==(Object other) => identical(this, other) || other is UpdateAssetDto &&
|
||||
other.dateTimeOriginal == dateTimeOriginal &&
|
||||
other.description == description &&
|
||||
other.isArchived == isArchived &&
|
||||
other.isFavorite == isFavorite;
|
||||
other.isFavorite == isFavorite &&
|
||||
other.latitude == latitude &&
|
||||
other.longitude == longitude;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) +
|
||||
(description == null ? 0 : description!.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
|
||||
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() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.dateTimeOriginal != null) {
|
||||
json[r'dateTimeOriginal'] = this.dateTimeOriginal;
|
||||
} else {
|
||||
// json[r'dateTimeOriginal'] = null;
|
||||
}
|
||||
if (this.description != null) {
|
||||
json[r'description'] = this.description;
|
||||
} else {
|
||||
|
@ -75,6 +113,16 @@ class UpdateAssetDto {
|
|||
} else {
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
@ -86,9 +134,16 @@ class UpdateAssetDto {
|
|||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return UpdateAssetDto(
|
||||
dateTimeOriginal: mapValueOfType<String>(json, r'dateTimeOriginal'),
|
||||
description: mapValueOfType<String>(json, r'description'),
|
||||
isArchived: mapValueOfType<bool>(json, r'isArchived'),
|
||||
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;
|
||||
|
|
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();
|
||||
|
||||
group('test AssetBulkUpdateDto', () {
|
||||
// String dateTimeOriginal
|
||||
test('to test the property `dateTimeOriginal`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// List<String> ids (default value: const [])
|
||||
test('to test the property `ids`', () async {
|
||||
// TODO
|
||||
|
@ -31,6 +36,16 @@ void main() {
|
|||
// TODO
|
||||
});
|
||||
|
||||
// num latitude
|
||||
test('to test the property `latitude`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// num longitude
|
||||
test('to test the property `longitude`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// bool removeParent
|
||||
test('to test the property `removeParent`', () async {
|
||||
// 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();
|
||||
|
||||
group('test UpdateAssetDto', () {
|
||||
// String dateTimeOriginal
|
||||
test('to test the property `dateTimeOriginal`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// String description
|
||||
test('to test the property `description`', () async {
|
||||
// TODO
|
||||
|
@ -31,6 +36,16 @@ void main() {
|
|||
// 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",
|
||||
"matchPackagePrefixes": ["ghcr.io/immich-app/base-server"]
|
||||
"matchPackagePrefixes": ["ghcr.io/immich-app/base-server"],
|
||||
"minimumReleaseAge": "0"
|
||||
},
|
||||
{
|
||||
"matchDatasources": ["docker"],
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
# 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
|
||||
COPY server/package.json server/package-lock.json ./
|
||||
|
@ -23,7 +23,7 @@ RUN npm run 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
|
||||
ENV NODE_ENV=production
|
||||
|
@ -37,6 +37,7 @@ COPY server/start*.sh ./
|
|||
RUN npm link && npm cache clean --force
|
||||
COPY LICENSE /licenses/LICENSE.txt
|
||||
COPY LICENSE /LICENSE
|
||||
ENV PATH="${PATH}:/usr/src/app/bin"
|
||||
VOLUME /usr/src/app/upload
|
||||
EXPOSE 3001
|
||||
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": {
|
||||
"properties": {
|
||||
"dateTimeOriginal": {
|
||||
"type": "string"
|
||||
},
|
||||
"ids": {
|
||||
"items": {
|
||||
"format": "uuid",
|
||||
|
@ -6462,6 +6465,12 @@
|
|||
"isFavorite": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"latitude": {
|
||||
"type": "number"
|
||||
},
|
||||
"longitude": {
|
||||
"type": "number"
|
||||
},
|
||||
"removeParent": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
@ -9343,6 +9352,9 @@
|
|||
},
|
||||
"UpdateAssetDto": {
|
||||
"properties": {
|
||||
"dateTimeOriginal": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -9351,6 +9363,12 @@
|
|||
},
|
||||
"isFavorite": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"latitude": {
|
||||
"type": "number"
|
||||
},
|
||||
"longitude": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
|
|
170
server/package-lock.json
generated
170
server/package-lock.json
generated
|
@ -53,10 +53,6 @@
|
|||
"typesense": "^1.7.1",
|
||||
"ua-parser-js": "^1.0.35"
|
||||
},
|
||||
"bin": {
|
||||
"immich": "bin/cli.sh",
|
||||
"immich-admin": "bin/admin-cli.sh"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.1.16",
|
||||
"@nestjs/schematics": "^10.0.2",
|
||||
|
@ -93,6 +89,7 @@
|
|||
"prettier-plugin-organize-imports": "^3.2.3",
|
||||
"rimraf": "^5.0.1",
|
||||
"source-map-support": "^0.5.21",
|
||||
"sql-formatter": "^14.0.0",
|
||||
"supertest": "^6.3.3",
|
||||
"testcontainers": "^10.2.1",
|
||||
"ts-jest": "^29.1.1",
|
||||
|
@ -5504,6 +5501,12 @@
|
|||
"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": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.2.tgz",
|
||||
|
@ -6740,6 +6743,18 @@
|
|||
"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": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
|
@ -8723,6 +8738,12 @@
|
|||
"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": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
|
@ -8895,6 +8916,34 @@
|
|||
"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": {
|
||||
"version": "0.6.3",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "2.1.0",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
|
||||
|
@ -10806,6 +10883,20 @@
|
|||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||
"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": {
|
||||
"version": "1.0.4",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "0.24.2",
|
||||
"resolved": "https://registry.npmjs.org/docker-compose/-/docker-compose-0.24.2.tgz",
|
||||
|
@ -17718,6 +17815,12 @@
|
|||
"integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==",
|
||||
"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": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
|
||||
|
@ -19188,6 +19291,12 @@
|
|||
"integrity": "sha512-2dF2R6YMSZbpip1V1WHKGLNjr/k48uQClqMVb5H3MOvwc9qhYis3/IWbj02qIg/Y8MDXKFF4c5v0rxx2o6xTZw==",
|
||||
"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": {
|
||||
"version": "2.1.2",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "0.6.3",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "2.1.0",
|
||||
"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": {
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
|
||||
|
@ -20747,6 +20898,17 @@
|
|||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
|
||||
"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": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/ssh-remote-port-forward/-/ssh-remote-port-forward-1.0.4.tgz",
|
||||
|
|
|
@ -5,10 +5,6 @@
|
|||
"author": "",
|
||||
"private": true,
|
||||
"license": "UNLICENSED",
|
||||
"bin": {
|
||||
"immich": "./bin/cli.sh",
|
||||
"immich-admin": "./bin/admin-cli.sh"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"format": "prettier --check .",
|
||||
|
@ -36,7 +32,8 @@
|
|||
"typeorm:schema:reset": "npm run typeorm:schema:drop && npm run typeorm:migrations:run",
|
||||
"api:typescript": "bash ./bin/generate-open-api.sh web",
|
||||
"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": {
|
||||
"@babel/runtime": "^7.22.11",
|
||||
|
@ -119,6 +116,7 @@
|
|||
"prettier-plugin-organize-imports": "^3.2.3",
|
||||
"rimraf": "^5.0.1",
|
||||
"source-map-support": "^0.5.21",
|
||||
"sql-formatter": "^14.0.0",
|
||||
"supertest": "^6.3.3",
|
||||
"testcontainers": "^10.2.1",
|
||||
"ts-jest": "^29.1.1",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
||||
import { AuthUserDto } from '../auth';
|
||||
import { setDifference, setUnion } from '../domain.util';
|
||||
import { setDifference, setIsEqual, setUnion } from '../domain.util';
|
||||
import { IAccessRepository } from '../repositories';
|
||||
|
||||
export enum Permission {
|
||||
|
@ -76,7 +76,7 @@ export class AccessCore {
|
|||
async requirePermission(authUser: AuthUserDto, permission: Permission, ids: string[] | string) {
|
||||
ids = Array.isArray(ids) ? ids : [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`);
|
||||
}
|
||||
}
|
||||
|
@ -106,9 +106,24 @@ export class AccessCore {
|
|||
}
|
||||
|
||||
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:
|
||||
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:
|
||||
return await this.repository.album.checkSharedLinkAccess(sharedLinkId, ids);
|
||||
|
||||
|
@ -116,46 +131,59 @@ export class AccessCore {
|
|||
return !!authUser.isAllowDownload
|
||||
? await this.repository.album.checkSharedLinkAccess(sharedLinkId, ids)
|
||||
: 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:
|
||||
return false;
|
||||
return new Set();
|
||||
}
|
||||
}
|
||||
|
||||
private async checkAccessOther(authUser: AuthUserDto, permission: Permission, ids: Set<string>) {
|
||||
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: {
|
||||
const isOwner = await this.repository.album.checkOwnerAccess(authUser.id, ids);
|
||||
const isShared = await this.repository.album.checkSharedAlbumAccess(authUser.id, setDifference(ids, isOwner));
|
||||
|
@ -163,13 +191,13 @@ export class AccessCore {
|
|||
}
|
||||
|
||||
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:
|
||||
return this.repository.album.checkOwnerAccess(authUser.id, ids);
|
||||
return await this.repository.album.checkOwnerAccess(authUser.id, ids);
|
||||
|
||||
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: {
|
||||
const isOwner = await this.repository.album.checkOwnerAccess(authUser.id, ids);
|
||||
|
@ -178,16 +206,16 @@ export class AccessCore {
|
|||
}
|
||||
|
||||
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:
|
||||
return this.repository.library.checkOwnerAccess(authUser.id, ids);
|
||||
return await this.repository.library.checkOwnerAccess(authUser.id, ids);
|
||||
|
||||
case Permission.ARCHIVE_READ:
|
||||
return ids.has(authUser.id) ? new Set([authUser.id]) : new Set();
|
||||
|
||||
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: {
|
||||
const isOwner = ids.has(authUser.id) ? new Set([authUser.id]) : new Set<string>();
|
||||
|
@ -205,22 +233,22 @@ export class AccessCore {
|
|||
}
|
||||
|
||||
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:
|
||||
return this.repository.library.checkOwnerAccess(authUser.id, ids);
|
||||
return await this.repository.library.checkOwnerAccess(authUser.id, ids);
|
||||
|
||||
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:
|
||||
return this.repository.person.checkOwnerAccess(authUser.id, ids);
|
||||
return await this.repository.person.checkOwnerAccess(authUser.id, ids);
|
||||
|
||||
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:
|
||||
return this.repository.partner.checkUpdateAccess(authUser.id, ids);
|
||||
return await this.repository.partner.checkUpdateAccess(authUser.id, ids);
|
||||
}
|
||||
|
||||
const allowedIds = new Set();
|
||||
|
@ -247,41 +275,6 @@ export class AccessCore {
|
|||
(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:
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -509,7 +509,7 @@ describe(AlbumService.name, () => {
|
|||
describe('addAssets', () => {
|
||||
it('should allow the owner to add assets', async () => {
|
||||
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.getAssetIds.mockResolvedValueOnce(new Set());
|
||||
|
||||
|
@ -534,7 +534,7 @@ describe(AlbumService.name, () => {
|
|||
|
||||
it('should not set the thumbnail if the album has one already', async () => {
|
||||
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.getAssetIds.mockResolvedValueOnce(new Set());
|
||||
|
||||
|
@ -552,7 +552,7 @@ describe(AlbumService.name, () => {
|
|||
|
||||
it('should allow a shared user to add assets', async () => {
|
||||
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.getAssetIds.mockResolvedValueOnce(new Set());
|
||||
|
||||
|
@ -577,7 +577,7 @@ describe(AlbumService.name, () => {
|
|||
|
||||
it('should allow a shared link user to add assets', async () => {
|
||||
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.getAssetIds.mockResolvedValueOnce(new Set());
|
||||
|
||||
|
@ -607,8 +607,7 @@ describe(AlbumService.name, () => {
|
|||
|
||||
it('should allow adding assets shared via partner sharing', async () => {
|
||||
accessMock.album.checkOwnerAccess.mockResolvedValue(new Set(['album-123']));
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
||||
accessMock.asset.hasPartnerAccess.mockResolvedValue(true);
|
||||
accessMock.asset.checkPartnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||
albumMock.getById.mockResolvedValue(_.cloneDeep(albumStub.oneAsset));
|
||||
albumMock.getAssetIds.mockResolvedValueOnce(new Set());
|
||||
|
||||
|
@ -621,12 +620,12 @@ describe(AlbumService.name, () => {
|
|||
updatedAt: expect.any(Date),
|
||||
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 () => {
|
||||
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.getAssetIds.mockResolvedValueOnce(new Set(['asset-id']));
|
||||
|
||||
|
@ -639,8 +638,6 @@ describe(AlbumService.name, () => {
|
|||
|
||||
it('should skip assets not shared with user', async () => {
|
||||
accessMock.album.checkOwnerAccess.mockResolvedValue(new Set(['album-123']));
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
||||
accessMock.asset.hasPartnerAccess.mockResolvedValue(false);
|
||||
albumMock.getById.mockResolvedValue(albumStub.oneAsset);
|
||||
albumMock.getAssetIds.mockResolvedValueOnce(new Set());
|
||||
|
||||
|
@ -648,8 +645,8 @@ describe(AlbumService.name, () => {
|
|||
{ success: false, id: 'asset-1', error: BulkIdErrorReason.NO_PERMISSION },
|
||||
]);
|
||||
|
||||
expect(accessMock.asset.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'asset-1');
|
||||
expect(accessMock.asset.hasPartnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'asset-1');
|
||||
expect(accessMock.asset.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['asset-1']));
|
||||
expect(accessMock.asset.checkPartnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['asset-1']));
|
||||
});
|
||||
|
||||
it('should not allow unauthorized access to the album', async () => {
|
||||
|
|
|
@ -457,19 +457,15 @@ describe(AssetService.name, () => {
|
|||
|
||||
describe('downloadFile', () => {
|
||||
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);
|
||||
|
||||
expect(accessMock.asset.hasOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'asset-1');
|
||||
expect(accessMock.asset.hasAlbumAccess).toHaveBeenCalledWith(authStub.admin.id, 'asset-1');
|
||||
expect(accessMock.asset.hasPartnerAccess).toHaveBeenCalledWith(authStub.admin.id, 'asset-1');
|
||||
expect(accessMock.asset.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['asset-1']));
|
||||
expect(accessMock.asset.checkAlbumAccess).toHaveBeenCalledWith(authStub.admin.id, new Set(['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 () => {
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
||||
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||
assetMock.getByIds.mockResolvedValue([]);
|
||||
|
||||
await expect(sut.downloadFile(authStub.admin, 'asset-1')).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
@ -480,7 +476,7 @@ describe(AssetService.name, () => {
|
|||
it('should download a file', async () => {
|
||||
const stream = new Readable();
|
||||
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
||||
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||
storageMock.createReadStream.mockResolvedValue({ stream });
|
||||
|
||||
|
@ -496,7 +492,7 @@ describe(AssetService.name, () => {
|
|||
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]);
|
||||
storageMock.createZipStream.mockReturnValue(archiveMock);
|
||||
|
||||
|
@ -516,7 +512,7 @@ describe(AssetService.name, () => {
|
|||
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]);
|
||||
storageMock.createZipStream.mockReturnValue(archiveMock);
|
||||
|
||||
|
@ -536,7 +532,7 @@ describe(AssetService.name, () => {
|
|||
});
|
||||
|
||||
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]);
|
||||
|
||||
const assetIds = ['asset-1', 'asset-2'];
|
||||
|
@ -602,7 +598,9 @@ describe(AssetService.name, () => {
|
|||
});
|
||||
|
||||
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)
|
||||
.calledWith([assetStub.livePhotoStillAsset.id])
|
||||
.mockResolvedValue([assetStub.livePhotoStillAsset]);
|
||||
|
@ -610,7 +608,6 @@ describe(AssetService.name, () => {
|
|||
.calledWith([assetStub.livePhotoMotionAsset.id])
|
||||
.mockResolvedValue([assetStub.livePhotoMotionAsset]);
|
||||
|
||||
const assetIds = [assetStub.livePhotoStillAsset.id];
|
||||
await expect(sut.getDownloadInfo(authStub.admin, { assetIds })).resolves.toEqual({
|
||||
totalSize: 125_000,
|
||||
archives: [
|
||||
|
@ -651,7 +648,6 @@ describe(AssetService.name, () => {
|
|||
|
||||
describe('update', () => {
|
||||
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(
|
||||
BadRequestException,
|
||||
);
|
||||
|
@ -659,14 +655,14 @@ describe(AssetService.name, () => {
|
|||
});
|
||||
|
||||
it('should update the asset', async () => {
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
||||
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||
assetMock.save.mockResolvedValue(assetStub.image);
|
||||
await sut.update(authStub.admin, 'asset-1', { isFavorite: true });
|
||||
expect(assetMock.save).toHaveBeenCalledWith({ id: 'asset-1', isFavorite: true });
|
||||
});
|
||||
|
||||
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);
|
||||
await sut.update(authStub.admin, 'asset-1', { description: 'Test description' });
|
||||
expect(assetMock.upsertExif).toHaveBeenCalledWith({ assetId: 'asset-1', description: 'Test description' });
|
||||
|
@ -675,7 +671,6 @@ describe(AssetService.name, () => {
|
|||
|
||||
describe('updateAll', () => {
|
||||
it('should require asset write access for all ids', async () => {
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
||||
await expect(
|
||||
sut.updateAll(authStub.admin, {
|
||||
ids: ['asset-1'],
|
||||
|
@ -685,7 +680,7 @@ describe(AssetService.name, () => {
|
|||
});
|
||||
|
||||
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 });
|
||||
expect(assetMock.updateAll).toHaveBeenCalledWith(['asset-1', 'asset-2'], { isArchived: true });
|
||||
});
|
||||
|
@ -693,8 +688,7 @@ describe(AssetService.name, () => {
|
|||
/// Stack related
|
||||
|
||||
it('should require asset update access for parent', async () => {
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(true);
|
||||
when(accessMock.asset.hasOwnerAccess).calledWith(authStub.user1.id, 'parent').mockResolvedValue(false);
|
||||
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-1']));
|
||||
await expect(
|
||||
sut.updateAll(authStub.user1, {
|
||||
ids: ['asset-1'],
|
||||
|
@ -704,7 +698,7 @@ describe(AssetService.name, () => {
|
|||
});
|
||||
|
||||
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, {
|
||||
ids: [],
|
||||
stackParentId: 'parent',
|
||||
|
@ -713,7 +707,7 @@ describe(AssetService.name, () => {
|
|||
});
|
||||
|
||||
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]);
|
||||
|
||||
await sut.updateAll(authStub.user1, {
|
||||
|
@ -724,7 +718,8 @@ describe(AssetService.name, () => {
|
|||
});
|
||||
|
||||
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, {
|
||||
stackParentId: 'parent',
|
||||
ids: ['child-1', 'child-2'],
|
||||
|
@ -734,7 +729,7 @@ describe(AssetService.name, () => {
|
|||
});
|
||||
|
||||
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, {
|
||||
removeParent: true,
|
||||
ids: ['child-1', 'child-2'],
|
||||
|
@ -744,7 +739,8 @@ describe(AssetService.name, () => {
|
|||
});
|
||||
|
||||
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([
|
||||
{ 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 () => {
|
||||
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, {
|
||||
ids: ['asset-1'],
|
||||
stackParentId: 'parent',
|
||||
|
@ -772,7 +770,6 @@ describe(AssetService.name, () => {
|
|||
|
||||
describe('deleteAll', () => {
|
||||
it('should require asset delete access for all ids', async () => {
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
||||
await expect(
|
||||
sut.deleteAll(authStub.user1, {
|
||||
ids: ['asset-1'],
|
||||
|
@ -781,7 +778,7 @@ describe(AssetService.name, () => {
|
|||
});
|
||||
|
||||
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 });
|
||||
|
||||
|
@ -792,7 +789,7 @@ describe(AssetService.name, () => {
|
|||
});
|
||||
|
||||
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 });
|
||||
|
||||
|
@ -810,7 +807,6 @@ describe(AssetService.name, () => {
|
|||
|
||||
describe('restoreAll', () => {
|
||||
it('should require asset restore access for all ids', async () => {
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
||||
await expect(
|
||||
sut.deleteAll(authStub.user1, {
|
||||
ids: ['asset-1'],
|
||||
|
@ -819,7 +815,7 @@ describe(AssetService.name, () => {
|
|||
});
|
||||
|
||||
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'] });
|
||||
|
||||
|
@ -984,19 +980,19 @@ describe(AssetService.name, () => {
|
|||
|
||||
describe('run', () => {
|
||||
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 }),
|
||||
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.METADATA_EXTRACTION, data: { id: 'asset-1' } });
|
||||
});
|
||||
|
||||
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 }),
|
||||
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.GENERATE_JPEG_THUMBNAIL, data: { id: 'asset-1' } });
|
||||
});
|
||||
|
||||
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 }),
|
||||
expect(jobMock.queue).toHaveBeenCalledWith({ name: JobName.VIDEO_CONVERSION, data: { id: 'asset-1' } });
|
||||
});
|
||||
|
@ -1004,9 +1000,7 @@ describe(AssetService.name, () => {
|
|||
|
||||
describe('updateStackParent', () => {
|
||||
it('should require asset update access for new parent', async () => {
|
||||
when(accessMock.asset.hasOwnerAccess).calledWith(authStub.user1.id, 'old').mockResolvedValue(true);
|
||||
when(accessMock.asset.hasOwnerAccess).calledWith(authStub.user1.id, 'new').mockResolvedValue(false);
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
||||
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['old']));
|
||||
await expect(
|
||||
sut.updateStackParent(authStub.user1, {
|
||||
oldParentId: 'old',
|
||||
|
@ -1016,8 +1010,7 @@ describe(AssetService.name, () => {
|
|||
});
|
||||
|
||||
it('should require asset read access for old parent', async () => {
|
||||
when(accessMock.asset.hasOwnerAccess).calledWith(authStub.user1.id, 'old').mockResolvedValue(false);
|
||||
when(accessMock.asset.hasOwnerAccess).calledWith(authStub.user1.id, 'new').mockResolvedValue(true);
|
||||
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['new']));
|
||||
await expect(
|
||||
sut.updateStackParent(authStub.user1, {
|
||||
oldParentId: 'old',
|
||||
|
@ -1027,7 +1020,9 @@ describe(AssetService.name, () => {
|
|||
});
|
||||
|
||||
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)
|
||||
.calledWith(assetStub.image.id)
|
||||
.mockResolvedValue(assetStub.image as AssetEntity);
|
||||
|
@ -1041,7 +1036,9 @@ describe(AssetService.name, () => {
|
|||
});
|
||||
|
||||
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, {
|
||||
oldParentId: assetStub.primaryImage.id,
|
||||
newParentId: 'new',
|
||||
|
@ -1051,7 +1048,8 @@ describe(AssetService.name, () => {
|
|||
});
|
||||
|
||||
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)
|
||||
.calledWith(assetStub.primaryImage.id)
|
||||
.mockResolvedValue(assetStub.primaryImage as AssetEntity);
|
||||
|
|
|
@ -8,7 +8,7 @@ import { AccessCore, Permission } from '../access';
|
|||
import { AuthUserDto } from '../auth';
|
||||
import { mimeTypes } from '../domain.constant';
|
||||
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 {
|
||||
CommunicationEvent,
|
||||
IAccessRepository,
|
||||
|
@ -393,10 +393,8 @@ export class AssetService {
|
|||
async update(authUser: AuthUserDto, id: string, dto: UpdateAssetDto): Promise<AssetResponseDto> {
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, id);
|
||||
|
||||
const { description, ...rest } = dto;
|
||||
if (description !== undefined) {
|
||||
await this.assetRepository.upsertExif({ assetId: id, description });
|
||||
}
|
||||
const { description, dateTimeOriginal, latitude, longitude, ...rest } = dto;
|
||||
await this.updateMetadata({ id, description, dateTimeOriginal, latitude, longitude });
|
||||
|
||||
const asset = await this.assetRepository.save({ id, ...rest });
|
||||
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> {
|
||||
const { ids, removeParent, ...options } = dto;
|
||||
const { ids, removeParent, dateTimeOriginal, latitude, longitude, ...options } = dto;
|
||||
await this.access.requirePermission(authUser, Permission.ASSET_UPDATE, ids);
|
||||
|
||||
if (removeParent) {
|
||||
|
@ -424,6 +422,10 @@ export class AssetService {
|
|||
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.assetRepository.updateAll(ids, options);
|
||||
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 { ApiProperty } from '@nestjs/swagger';
|
||||
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 { BulkIdsDto } from '../response-dto';
|
||||
|
||||
|
@ -10,6 +22,10 @@ export enum AssetOrder {
|
|||
DESC = 'desc',
|
||||
}
|
||||
|
||||
const hasGPS = (o: { latitude: undefined; longitude: undefined }) =>
|
||||
o.latitude !== undefined || o.longitude !== undefined;
|
||||
const ValidateGPS = () => ValidateIf(hasGPS);
|
||||
|
||||
export class AssetSearchDto {
|
||||
@ValidateUUID({ optional: true })
|
||||
id?: string;
|
||||
|
@ -172,6 +188,20 @@ export class AssetBulkUpdateDto extends BulkIdsDto {
|
|||
@Optional()
|
||||
@IsBoolean()
|
||||
removeParent?: boolean;
|
||||
|
||||
@Optional()
|
||||
@IsDateString()
|
||||
dateTimeOriginal?: string;
|
||||
|
||||
@ValidateGPS()
|
||||
@IsLatitude()
|
||||
@IsNotEmpty()
|
||||
latitude?: number;
|
||||
|
||||
@ValidateGPS()
|
||||
@IsLongitude()
|
||||
@IsNotEmpty()
|
||||
longitude?: number;
|
||||
}
|
||||
|
||||
export class UpdateAssetDto {
|
||||
|
@ -186,6 +216,20 @@ export class UpdateAssetDto {
|
|||
@Optional()
|
||||
@IsString()
|
||||
description?: string;
|
||||
|
||||
@Optional()
|
||||
@IsDateString()
|
||||
dateTimeOriginal?: string;
|
||||
|
||||
@ValidateGPS()
|
||||
@IsLatitude()
|
||||
@IsNotEmpty()
|
||||
latitude?: number;
|
||||
|
||||
@ValidateGPS()
|
||||
@IsLongitude()
|
||||
@IsNotEmpty()
|
||||
longitude?: number;
|
||||
}
|
||||
|
||||
export class RandomAssetsDto {
|
||||
|
|
|
@ -98,7 +98,7 @@ export function mapAsset(entity: AssetEntity, options: AssetMapOptions = {}): As
|
|||
tags: entity.tags?.map(mapTag),
|
||||
people: entity.faces
|
||||
?.map(mapFace)
|
||||
.filter((person): person is PersonResponseDto => person !== null && !person.isHidden)
|
||||
.filter((person): person is PersonResponseDto => person !== null)
|
||||
.reduce((people, person) => {
|
||||
const existingPerson = people.find((p) => p.id === person.id);
|
||||
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.
|
||||
// Proposal reference: https://github.com/tc39/proposal-set-methods
|
||||
|
||||
export const setUnion = <T>(setA: Set<T>, setB: Set<T>): Set<T> => {
|
||||
const union = new Set(setA);
|
||||
for (const elem of setB) {
|
||||
union.add(elem);
|
||||
export const setUnion = <T>(...sets: Set<T>[]): Set<T> => {
|
||||
const union = new Set(sets[0]);
|
||||
for (const set of sets.slice(1)) {
|
||||
for (const elem of set) {
|
||||
union.add(elem);
|
||||
}
|
||||
}
|
||||
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);
|
||||
for (const elem of setB) {
|
||||
difference.delete(elem);
|
||||
for (const set of sets) {
|
||||
for (const elem of set) {
|
||||
difference.delete(elem);
|
||||
}
|
||||
}
|
||||
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',
|
||||
SIDECAR_DISCOVERY = 'sidecar-discovery',
|
||||
SIDECAR_SYNC = 'sidecar-sync',
|
||||
SIDECAR_WRITE = 'sidecar-write',
|
||||
}
|
||||
|
||||
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.SIDECAR_DISCOVERY]: QueueName.SIDECAR,
|
||||
[JobName.SIDECAR_SYNC]: QueueName.SIDECAR,
|
||||
[JobName.SIDECAR_WRITE]: QueueName.SIDECAR,
|
||||
|
||||
// Library management
|
||||
[JobName.LIBRARY_SCAN_ASSET]: QueueName.LIBRARY,
|
||||
|
|
|
@ -9,7 +9,7 @@ export interface IAssetFaceJob extends IBaseJob {
|
|||
|
||||
export interface IEntityJob extends IBaseJob {
|
||||
id: string;
|
||||
source?: 'upload';
|
||||
source?: 'upload' | 'sidecar-write';
|
||||
}
|
||||
|
||||
export interface IAssetDeletionJob extends IEntityJob {
|
||||
|
@ -33,3 +33,10 @@ export interface IBulkEntityJob extends IBaseJob {
|
|||
export interface IDeleteFilesJob extends IBaseJob {
|
||||
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 });
|
||||
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:
|
||||
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 });
|
||||
break;
|
||||
|
||||
|
|
|
@ -58,7 +58,8 @@ describe(LibraryService.name, () => {
|
|||
ctime: new Date('2023-01-01'),
|
||||
} 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(
|
||||
accessMock,
|
||||
|
|
|
@ -218,11 +218,11 @@ describe(MetadataService.name, () => {
|
|||
const originalDate = new Date('2023-11-21T16:13:17.517Z');
|
||||
const sidecarDate = new Date('2022-01-01T00:00:00.000Z');
|
||||
assetMock.getByIds.mockResolvedValue([assetStub.sidecar]);
|
||||
when(metadataMock.getExifTags)
|
||||
when(metadataMock.readTags)
|
||||
.calledWith(assetStub.sidecar.originalPath)
|
||||
// higher priority tag
|
||||
.mockResolvedValue({ CreationDate: originalDate.toISOString() });
|
||||
when(metadataMock.getExifTags)
|
||||
when(metadataMock.readTags)
|
||||
.calledWith(assetStub.sidecar.sidecarPath as string)
|
||||
// lower priority tag, but in sidecar
|
||||
.mockResolvedValue({ CreateDate: sidecarDate.toISOString() });
|
||||
|
@ -240,7 +240,7 @@ describe(MetadataService.name, () => {
|
|||
|
||||
it('should handle lists of numbers', async () => {
|
||||
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 });
|
||||
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id]);
|
||||
|
@ -257,7 +257,7 @@ describe(MetadataService.name, () => {
|
|||
assetMock.getByIds.mockResolvedValue([assetStub.withLocation]);
|
||||
configMock.load.mockResolvedValue([{ key: SystemConfigKey.REVERSE_GEOCODING_ENABLED, value: true }]);
|
||||
metadataMock.reverseGeocode.mockResolvedValue({ city: 'City', state: 'State', country: 'Country' });
|
||||
metadataMock.getExifTags.mockResolvedValue({
|
||||
metadataMock.readTags.mockResolvedValue({
|
||||
GPSLatitude: assetStub.withLocation.exifInfo!.latitude!,
|
||||
GPSLongitude: assetStub.withLocation.exifInfo!.longitude!,
|
||||
});
|
||||
|
@ -289,7 +289,7 @@ describe(MetadataService.name, () => {
|
|||
|
||||
it('should apply motion photos', async () => {
|
||||
assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoStillAsset, livePhotoVideoId: null }]);
|
||||
metadataMock.getExifTags.mockResolvedValue({
|
||||
metadataMock.readTags.mockResolvedValue({
|
||||
Directory: 'foo/bar/',
|
||||
MotionPhoto: 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 () => {
|
||||
assetMock.getByIds.mockResolvedValue([{ ...assetStub.livePhotoStillAsset, livePhotoVideoId: null }]);
|
||||
metadataMock.getExifTags.mockResolvedValue({
|
||||
metadataMock.readTags.mockResolvedValue({
|
||||
Directory: 'foo/bar/',
|
||||
MotionPhoto: 1,
|
||||
MicroVideo: 1,
|
||||
|
@ -367,7 +367,7 @@ describe(MetadataService.name, () => {
|
|||
tz: '+02:00',
|
||||
};
|
||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||
metadataMock.getExifTags.mockResolvedValue(tags);
|
||||
metadataMock.readTags.mockResolvedValue(tags);
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
expect(assetMock.getByIds).toHaveBeenCalledWith([assetStub.image.id]);
|
||||
|
@ -406,7 +406,7 @@ describe(MetadataService.name, () => {
|
|||
|
||||
it('should handle duration', async () => {
|
||||
assetMock.getByIds.mockResolvedValue([assetStub.image]);
|
||||
metadataMock.getExifTags.mockResolvedValue({ Duration: 6.21 });
|
||||
metadataMock.readTags.mockResolvedValue({ Duration: 6.21 });
|
||||
|
||||
await sut.handleMetadataExtraction({ id: assetStub.image.id });
|
||||
|
||||
|
@ -422,7 +422,7 @@ describe(MetadataService.name, () => {
|
|||
|
||||
it('should handle duration as an object without Scale', async () => {
|
||||
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 });
|
||||
|
||||
|
@ -438,7 +438,7 @@ describe(MetadataService.name, () => {
|
|||
|
||||
it('should handle duration with scale', async () => {
|
||||
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 });
|
||||
|
||||
|
@ -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 { firstDateTime } from 'exiftool-vendored/dist/FirstDateTime';
|
||||
import { constants } from 'fs/promises';
|
||||
import _ from 'lodash';
|
||||
import { Duration } from 'luxon';
|
||||
import { Subscription } from 'rxjs';
|
||||
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 {
|
||||
ExifDuration,
|
||||
IAlbumRepository,
|
||||
|
@ -79,7 +80,6 @@ export class MetadataService {
|
|||
private logger = new Logger(MetadataService.name);
|
||||
private storageCore: StorageCore;
|
||||
private configCore: SystemConfigCore;
|
||||
private oldCities?: string;
|
||||
private subscription: Subscription | null = null;
|
||||
|
||||
constructor(
|
||||
|
@ -244,6 +244,37 @@ export class MetadataService {
|
|||
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) {
|
||||
const { latitude, longitude } = exifData;
|
||||
if (!(await this.configCore.hasFeature(FeatureFlag.REVERSE_GEOCODING)) || !longitude || !latitude) {
|
||||
|
@ -346,8 +377,8 @@ export class MetadataService {
|
|||
asset: AssetEntity,
|
||||
): Promise<{ exifData: ExifEntityWithoutGeocodeAndTypeOrm; tags: ImmichTags }> {
|
||||
const stats = await this.storageRepository.stat(asset.originalPath);
|
||||
const mediaTags = await this.repository.getExifTags(asset.originalPath);
|
||||
const sidecarTags = asset.sidecarPath ? await this.repository.getExifTags(asset.sidecarPath) : null;
|
||||
const mediaTags = await this.repository.readTags(asset.originalPath);
|
||||
const sidecarTags = asset.sidecarPath ? await this.repository.readTags(asset.sidecarPath) : null;
|
||||
|
||||
// ensure date from sidecar is used if present
|
||||
const hasDateOverride = !!this.getDateTimeOriginal(sidecarTags);
|
||||
|
|
|
@ -331,7 +331,7 @@ describe(PersonService.name, () => {
|
|||
personMock.getById.mockResolvedValue(personStub.withName);
|
||||
personMock.update.mockResolvedValue(personStub.withName);
|
||||
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']));
|
||||
|
||||
await expect(
|
||||
|
|
|
@ -6,11 +6,12 @@ export interface IAccessRepository {
|
|||
hasAlbumOwnerAccess(userId: string, activityId: string): Promise<boolean>;
|
||||
hasCreateAccess(userId: string, albumId: string): Promise<boolean>;
|
||||
};
|
||||
|
||||
asset: {
|
||||
hasOwnerAccess(userId: string, assetId: string): Promise<boolean>;
|
||||
hasAlbumAccess(userId: string, assetId: string): Promise<boolean>;
|
||||
hasPartnerAccess(userId: string, assetId: string): Promise<boolean>;
|
||||
hasSharedLinkAccess(sharedLinkId: string, assetId: string): Promise<boolean>;
|
||||
checkOwnerAccess(userId: string, assetIds: Set<string>): Promise<Set<string>>;
|
||||
checkAlbumAccess(userId: string, assetIds: Set<string>): Promise<Set<string>>;
|
||||
checkPartnerAccess(userId: string, assetIds: Set<string>): Promise<Set<string>>;
|
||||
checkSharedLinkAccess(sharedLinkId: string, assetIds: Set<string>): Promise<Set<string>>;
|
||||
};
|
||||
|
||||
authDevice: {
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
IEntityJob,
|
||||
ILibraryFileJob,
|
||||
ILibraryRefreshJob,
|
||||
ISidecarWriteJob,
|
||||
} from '../job/job.interface';
|
||||
|
||||
export interface JobCounts {
|
||||
|
@ -54,11 +55,11 @@ export type JobItem =
|
|||
| { name: JobName.QUEUE_METADATA_EXTRACTION; data: IBaseJob }
|
||||
| { name: JobName.METADATA_EXTRACTION; data: IEntityJob }
|
||||
| { name: JobName.LINK_LIVE_PHOTOS; data: IEntityJob }
|
||||
|
||||
// Sidecar Scanning
|
||||
| { name: JobName.QUEUE_SIDECAR; data: IBaseJob }
|
||||
| { name: JobName.SIDECAR_DISCOVERY; data: IEntityJob }
|
||||
| { name: JobName.SIDECAR_SYNC; data: IEntityJob }
|
||||
| { name: JobName.SIDECAR_WRITE; data: ISidecarWriteJob }
|
||||
|
||||
// Object Tagging
|
||||
| { name: JobName.QUEUE_OBJECT_TAGGING; data: IBaseJob }
|
||||
|
|
|
@ -33,5 +33,6 @@ export interface IMetadataRepository {
|
|||
init(): Promise<void>;
|
||||
teardown(): Promise<void>;
|
||||
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,
|
||||
sharedLinkStub,
|
||||
} from '@test';
|
||||
import { when } from 'jest-when';
|
||||
import _ from 'lodash';
|
||||
import { AssetIdErrorReason } from '../asset';
|
||||
import { ICryptoRepository, ISharedLinkRepository } from '../repositories';
|
||||
|
@ -109,7 +108,6 @@ describe(SharedLinkService.name, () => {
|
|||
});
|
||||
|
||||
it('should require asset ownership to make an individual shared link', async () => {
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
||||
await expect(
|
||||
sut.create(authStub.admin, { type: SharedLinkType.INDIVIDUAL, assetIds: ['asset-1'] }),
|
||||
).rejects.toBeInstanceOf(BadRequestException);
|
||||
|
@ -140,7 +138,7 @@ describe(SharedLinkService.name, () => {
|
|||
});
|
||||
|
||||
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);
|
||||
|
||||
await sut.create(authStub.admin, {
|
||||
|
@ -151,7 +149,7 @@ describe(SharedLinkService.name, () => {
|
|||
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({
|
||||
type: SharedLinkType.INDIVIDUAL,
|
||||
userId: authStub.admin.id,
|
||||
|
@ -215,9 +213,7 @@ describe(SharedLinkService.name, () => {
|
|||
it('should add assets to a shared link', async () => {
|
||||
shareMock.get.mockResolvedValue(_.cloneDeep(sharedLinkStub.individual));
|
||||
shareMock.create.mockResolvedValue(sharedLinkStub.individual);
|
||||
|
||||
when(accessMock.asset.hasOwnerAccess).calledWith(authStub.admin.id, 'asset-2').mockResolvedValue(false);
|
||||
when(accessMock.asset.hasOwnerAccess).calledWith(authStub.admin.id, 'asset-3').mockResolvedValue(true);
|
||||
accessMock.asset.checkOwnerAccess.mockResolvedValue(new Set(['asset-3']));
|
||||
|
||||
await expect(
|
||||
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 },
|
||||
]);
|
||||
|
||||
expect(accessMock.asset.hasOwnerAccess).toHaveBeenCalledTimes(2);
|
||||
expect(accessMock.asset.checkOwnerAccess).toHaveBeenCalledTimes(1);
|
||||
expect(shareMock.update).toHaveBeenCalledWith({
|
||||
...sharedLinkStub.individual,
|
||||
assets: [assetStub.image, { id: 'asset-3' }],
|
||||
|
|
|
@ -232,54 +232,49 @@ describe('AssetService', () => {
|
|||
|
||||
describe('getAssetById', () => {
|
||||
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);
|
||||
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 () => {
|
||||
accessMock.asset.hasSharedLinkAccess.mockResolvedValue(true);
|
||||
accessMock.asset.checkSharedLinkAccess.mockResolvedValue(new Set([assetStub.image.id]));
|
||||
assetRepositoryMock.getById.mockResolvedValue(assetStub.image);
|
||||
await sut.getAssetById(authStub.adminSharedLink, assetStub.image.id);
|
||||
expect(accessMock.asset.hasSharedLinkAccess).toHaveBeenCalledWith(
|
||||
expect(accessMock.asset.checkSharedLinkAccess).toHaveBeenCalledWith(
|
||||
authStub.adminSharedLink.sharedLinkId,
|
||||
assetStub.image.id,
|
||||
new Set([assetStub.image.id]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should allow partner sharing access', async () => {
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
||||
accessMock.asset.hasPartnerAccess.mockResolvedValue(true);
|
||||
accessMock.asset.checkPartnerAccess.mockResolvedValue(new Set([assetStub.image.id]));
|
||||
assetRepositoryMock.getById.mockResolvedValue(assetStub.image);
|
||||
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 () => {
|
||||
accessMock.asset.hasOwnerAccess.mockResolvedValue(false);
|
||||
accessMock.asset.hasPartnerAccess.mockResolvedValue(false);
|
||||
accessMock.asset.hasAlbumAccess.mockResolvedValue(true);
|
||||
accessMock.asset.checkAlbumAccess.mockResolvedValue(new Set([assetStub.image.id]));
|
||||
assetRepositoryMock.getById.mockResolvedValue(assetStub.image);
|
||||
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 () => {
|
||||
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);
|
||||
expect(assetRepositoryMock.getById).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
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(
|
||||
BadRequestException,
|
||||
);
|
||||
expect(accessMock.asset.hasOwnerAccess).not.toHaveBeenCalled();
|
||||
expect(accessMock.asset.checkOwnerAccess).not.toHaveBeenCalled();
|
||||
expect(assetRepositoryMock.getById).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -36,10 +36,10 @@ import { databaseConfig } from './database.config';
|
|||
import { databaseEntities } from './entities';
|
||||
import { bullConfig, bullQueues } from './infra.config';
|
||||
import {
|
||||
APIKeyRepository,
|
||||
AccessRepository,
|
||||
ActivityRepository,
|
||||
AlbumRepository,
|
||||
ApiKeyRepository,
|
||||
AssetRepository,
|
||||
AuditRepository,
|
||||
CommunicationRepository,
|
||||
|
@ -74,7 +74,7 @@ const providers: Provider[] = [
|
|||
{ provide: ICryptoRepository, useClass: CryptoRepository },
|
||||
{ provide: IJobRepository, useClass: JobRepository },
|
||||
{ provide: ILibraryRepository, useClass: LibraryRepository },
|
||||
{ provide: IKeyRepository, useClass: APIKeyRepository },
|
||||
{ provide: IKeyRepository, useClass: ApiKeyRepository },
|
||||
{ provide: IMachineLearningRepository, useClass: MachineLearningRepository },
|
||||
{ provide: IMetadataRepository, useClass: MetadataRepository },
|
||||
{ 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 { InjectRepository } from '@nestjs/typeorm';
|
||||
import { In, Repository } from 'typeorm';
|
||||
import { Brackets, In, Repository } from 'typeorm';
|
||||
import {
|
||||
ActivityEntity,
|
||||
AlbumEntity,
|
||||
|
@ -112,107 +112,120 @@ export class AccessRepository implements IAccessRepository {
|
|||
};
|
||||
|
||||
asset = {
|
||||
hasAlbumAccess: (userId: string, assetId: string): Promise<boolean> => {
|
||||
return this.albumRepository.exist({
|
||||
where: [
|
||||
{
|
||||
checkAlbumAccess: async (userId: string, assetIds: Set<string>): Promise<Set<string>> => {
|
||||
if (assetIds.size === 0) {
|
||||
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,
|
||||
assets: {
|
||||
id: assetId,
|
||||
},
|
||||
},
|
||||
{
|
||||
sharedUsers: {
|
||||
id: userId,
|
||||
},
|
||||
assets: {
|
||||
id: assetId,
|
||||
},
|
||||
},
|
||||
// still part of a live photo is in an album
|
||||
{
|
||||
ownerId: userId,
|
||||
assets: {
|
||||
livePhotoVideoId: assetId,
|
||||
},
|
||||
},
|
||||
{
|
||||
sharedUsers: {
|
||||
id: userId,
|
||||
},
|
||||
assets: {
|
||||
livePhotoVideoId: assetId,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
withDeleted: true,
|
||||
})
|
||||
.then((assets) => new Set(assets.map((asset) => asset.id)));
|
||||
},
|
||||
|
||||
hasOwnerAccess: (userId: string, assetId: string): Promise<boolean> => {
|
||||
return this.assetRepository.exist({
|
||||
where: {
|
||||
id: assetId,
|
||||
ownerId: userId,
|
||||
},
|
||||
withDeleted: true,
|
||||
});
|
||||
checkPartnerAccess: async (userId: string, assetIds: Set<string>): Promise<Set<string>> => {
|
||||
if (assetIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
return this.partnerRepository
|
||||
.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> => {
|
||||
return this.partnerRepository.exist({
|
||||
where: {
|
||||
sharedWith: {
|
||||
id: userId,
|
||||
},
|
||||
sharedBy: {
|
||||
assets: {
|
||||
id: assetId,
|
||||
},
|
||||
},
|
||||
},
|
||||
relations: {
|
||||
sharedWith: true,
|
||||
sharedBy: {
|
||||
assets: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
checkSharedLinkAccess: async (sharedLinkId: string, assetIds: Set<string>): Promise<Set<string>> => {
|
||||
if (assetIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
|
||||
hasSharedLinkAccess: async (sharedLinkId: string, assetId: string): Promise<boolean> => {
|
||||
return this.sharedLinkRepository.exist({
|
||||
where: [
|
||||
{
|
||||
id: sharedLinkId,
|
||||
album: {
|
||||
assets: {
|
||||
id: assetId,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: sharedLinkId,
|
||||
assets: {
|
||||
id: assetId,
|
||||
},
|
||||
},
|
||||
// still part of a live photo is in a shared link
|
||||
{
|
||||
id: sharedLinkId,
|
||||
album: {
|
||||
assets: {
|
||||
livePhotoVideoId: assetId,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: sharedLinkId,
|
||||
assets: {
|
||||
livePhotoVideoId: assetId,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
return this.sharedLinkRepository
|
||||
.createQueryBuilder('sharedLink')
|
||||
.leftJoin('sharedLink.album', 'album')
|
||||
.leftJoin('sharedLink.assets', 'assets')
|
||||
.leftJoin('album.assets', 'albumAssets')
|
||||
.select('assets.id', 'assetId')
|
||||
.addSelect('albumAssets.id', 'albumAssetId')
|
||||
.addSelect('assets.livePhotoVideoId', 'assetLivePhotoVideoId')
|
||||
.addSelect('albumAssets.livePhotoVideoId', 'albumAssetLivePhotoVideoId')
|
||||
.where('sharedLink.id = :sharedLinkId', { sharedLinkId })
|
||||
.andWhere(
|
||||
new Brackets((qb) => {
|
||||
qb.where('assets.id IN (:...assetIds)', { assetIds: [...assetIds] })
|
||||
.orWhere('albumAssets.id IN (:...assetIds)', { assetIds: [...assetIds] })
|
||||
// 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] });
|
||||
}),
|
||||
)
|
||||
.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.assetLivePhotoVideoId && assetIds.has(row.assetLivePhotoVideoId)) {
|
||||
allowedIds.add(row.assetLivePhotoVideoId);
|
||||
}
|
||||
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 { IsNull, Repository } from 'typeorm';
|
||||
import { ActivityEntity } from '../entities/activity.entity';
|
||||
import { DummyValue, GenerateSql } from '../infra.util';
|
||||
|
||||
export interface ActivitySearch {
|
||||
albumId?: string;
|
||||
|
@ -15,6 +16,7 @@ export interface ActivitySearch {
|
|||
export class ActivityRepository implements IActivityRepository {
|
||||
constructor(@InjectRepository(ActivityEntity) private repository: Repository<ActivityEntity>) {}
|
||||
|
||||
@GenerateSql({ params: [{ albumId: DummyValue.UUID }] })
|
||||
search(options: ActivitySearch): Promise<ActivityEntity[]> {
|
||||
const { userId, assetId, albumId, isLiked } = options;
|
||||
return this.repository.find({
|
||||
|
@ -41,6 +43,7 @@ export class ActivityRepository implements IActivityRepository {
|
|||
await this.repository.delete(id);
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
|
||||
getStatistics(assetId: string, albumId: string): Promise<number> {
|
||||
return this.repository.count({
|
||||
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 } from '../database.config';
|
||||
import { AlbumEntity, AssetEntity } from '../entities';
|
||||
import { DummyValue, GenerateSql } from '../infra.util';
|
||||
|
||||
@Injectable()
|
||||
export class AlbumRepository implements IAlbumRepository {
|
||||
|
@ -13,6 +14,7 @@ export class AlbumRepository implements IAlbumRepository {
|
|||
@InjectDataSource() private dataSource: DataSource,
|
||||
) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, {}] })
|
||||
getById(id: string, options: AlbumInfoOptions): Promise<AlbumEntity | null> {
|
||||
const relations: FindOptionsRelations<AlbumEntity> = {
|
||||
owner: true,
|
||||
|
@ -36,6 +38,7 @@ export class AlbumRepository implements IAlbumRepository {
|
|||
return this.repository.findOne({ where: { id }, relations, order });
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [[DummyValue.UUID]] })
|
||||
getByIds(ids: string[]): Promise<AlbumEntity[]> {
|
||||
return this.repository.find({
|
||||
where: {
|
||||
|
@ -48,6 +51,7 @@ export class AlbumRepository implements IAlbumRepository {
|
|||
});
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
|
||||
getByAssetId(ownerId: string, assetId: string): Promise<AlbumEntity[]> {
|
||||
return this.repository.find({
|
||||
where: [
|
||||
|
@ -59,6 +63,7 @@ export class AlbumRepository implements IAlbumRepository {
|
|||
});
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [[DummyValue.UUID]] })
|
||||
async getMetadataForIds(ids: string[]): Promise<AlbumAssetCount[]> {
|
||||
// Guard against running invalid query when ids list is empty.
|
||||
if (!ids.length) {
|
||||
|
@ -91,6 +96,7 @@ export class AlbumRepository implements IAlbumRepository {
|
|||
* - Thumbnail references an asset outside the album
|
||||
* - Empty album still has a thumbnail set
|
||||
*/
|
||||
@GenerateSql()
|
||||
async getInvalidThumbnail(): Promise<string[]> {
|
||||
// Using dataSource, because there is no direct access to albums_assets_assets.
|
||||
const albumHasAssets = this.dataSource
|
||||
|
@ -113,6 +119,7 @@ export class AlbumRepository implements IAlbumRepository {
|
|||
return albums.map((album) => album.id);
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getOwned(ownerId: string): Promise<AlbumEntity[]> {
|
||||
return this.repository.find({
|
||||
relations: { sharedUsers: true, sharedLinks: true, owner: true },
|
||||
|
@ -124,6 +131,7 @@ export class AlbumRepository implements IAlbumRepository {
|
|||
/**
|
||||
* Get albums shared with and shared by owner.
|
||||
*/
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getShared(ownerId: string): Promise<AlbumEntity[]> {
|
||||
return this.repository.find({
|
||||
relations: { sharedUsers: true, sharedLinks: true, owner: true },
|
||||
|
@ -139,6 +147,7 @@ export class AlbumRepository implements IAlbumRepository {
|
|||
/**
|
||||
* Get albums of owner that are _not_ shared
|
||||
*/
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getNotShared(ownerId: string): Promise<AlbumEntity[]> {
|
||||
return this.repository.find({
|
||||
relations: { sharedUsers: true, sharedLinks: true, owner: true },
|
||||
|
@ -159,6 +168,7 @@ export class AlbumRepository implements IAlbumRepository {
|
|||
await this.repository.delete({ ownerId: userId });
|
||||
}
|
||||
|
||||
@GenerateSql()
|
||||
getAll(): Promise<AlbumEntity[]> {
|
||||
return this.repository.find({
|
||||
relations: {
|
||||
|
@ -167,6 +177,7 @@ export class AlbumRepository implements IAlbumRepository {
|
|||
});
|
||||
}
|
||||
|
||||
// @GenerateSql({ params: [DummyValue.UUID] })
|
||||
async removeAsset(assetId: string): Promise<void> {
|
||||
// Using dataSource, because there is no direct access to albums_assets_assets.
|
||||
await this.dataSource
|
||||
|
@ -176,6 +187,7 @@ export class AlbumRepository implements IAlbumRepository {
|
|||
.where('"albums_assets_assets"."assetsId" = :assetId', { assetId });
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [{ albumId: DummyValue.UUID, assetIds: [DummyValue.UUID] }] })
|
||||
async removeAssets(asset: AlbumAssets): Promise<void> {
|
||||
await this.dataSource
|
||||
.createQueryBuilder()
|
||||
|
@ -195,6 +207,7 @@ export class AlbumRepository implements IAlbumRepository {
|
|||
* @param assetIds Optional list of asset IDs to filter on.
|
||||
* @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>> {
|
||||
const query = this.dataSource
|
||||
.createQueryBuilder()
|
||||
|
@ -210,6 +223,7 @@ export class AlbumRepository implements IAlbumRepository {
|
|||
return new Set(result.map((row) => row['assetId']));
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [{ albumId: DummyValue.UUID, assetId: DummyValue.UUID }] })
|
||||
hasAsset(asset: AlbumAsset): Promise<boolean> {
|
||||
return this.repository.exist({
|
||||
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> {
|
||||
await this.dataSource
|
||||
.createQueryBuilder()
|
||||
|
@ -266,6 +281,7 @@ export class AlbumRepository implements IAlbumRepository {
|
|||
*
|
||||
* @returns Amount of updated album thumbnails or undefined when unknown
|
||||
*/
|
||||
@GenerateSql()
|
||||
async updateThumbnails(): Promise<number | undefined> {
|
||||
// Subquery for getting a new thumbnail.
|
||||
const newThumbnail = this.assetRepository
|
||||
|
|
|
@ -3,9 +3,10 @@ import { Injectable } from '@nestjs/common';
|
|||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { APIKeyEntity } from '../entities';
|
||||
import { DummyValue, GenerateSql } from '../infra.util';
|
||||
|
||||
@Injectable()
|
||||
export class APIKeyRepository implements IKeyRepository {
|
||||
export class ApiKeyRepository implements IKeyRepository {
|
||||
constructor(@InjectRepository(APIKeyEntity) private repository: Repository<APIKeyEntity>) {}
|
||||
|
||||
async create(dto: Partial<APIKeyEntity>): Promise<APIKeyEntity> {
|
||||
|
@ -21,6 +22,7 @@ export class APIKeyRepository implements IKeyRepository {
|
|||
await this.repository.delete({ userId, id });
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.STRING] })
|
||||
getKey(hashedToken: string): Promise<APIKeyEntity | null> {
|
||||
return this.repository.findOne({
|
||||
select: {
|
||||
|
@ -35,10 +37,12 @@ export class APIKeyRepository implements IKeyRepository {
|
|||
});
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
|
||||
getById(userId: string, id: string): Promise<APIKeyEntity | null> {
|
||||
return this.repository.findOne({ where: { userId, id } });
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.STRING] })
|
||||
getByUserId(userId: string): Promise<APIKeyEntity[]> {
|
||||
return this.repository.find({ where: { userId }, order: { createdAt: 'DESC' } });
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import _ from 'lodash';
|
|||
import { DateTime } from 'luxon';
|
||||
import { And, FindOptionsRelations, FindOptionsWhere, In, IsNull, LessThan, Not, Repository } from 'typeorm';
|
||||
import { AssetEntity, AssetJobStatusEntity, AssetType, ExifEntity } from '../entities';
|
||||
import { DummyValue, GenerateSql } from '../infra.util';
|
||||
import OptionalBetween from '../utils/optional-between.util';
|
||||
import { paginate } from '../utils/pagination.util';
|
||||
|
||||
|
@ -185,6 +186,7 @@ export class AssetRepository implements IAssetRepository {
|
|||
return this.repository.save(asset);
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.DATE] })
|
||||
getByDate(ownerId: string, date: Date): Promise<AssetEntity[]> {
|
||||
// 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[]> {
|
||||
return this.repository
|
||||
.createQueryBuilder('entity')
|
||||
|
@ -240,6 +243,7 @@ export class AssetRepository implements IAssetRepository {
|
|||
.getMany();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [[DummyValue.UUID]] })
|
||||
getByIds(ids: string[], relations?: FindOptionsRelations<AssetEntity>): Promise<AssetEntity[]> {
|
||||
if (!relations) {
|
||||
relations = {
|
||||
|
@ -259,6 +263,7 @@ export class AssetRepository implements IAssetRepository {
|
|||
});
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
async deleteAll(ownerId: string): Promise<void> {
|
||||
await this.repository.delete({ ownerId });
|
||||
}
|
||||
|
@ -291,12 +296,14 @@ export class AssetRepository implements IAssetRepository {
|
|||
});
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [[DummyValue.UUID]] })
|
||||
getByLibraryId(libraryIds: string[]): Promise<AssetEntity[]> {
|
||||
return this.repository.find({
|
||||
where: { library: { id: In(libraryIds) } },
|
||||
});
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] })
|
||||
getByLibraryIdAndOriginalPath(libraryId: string, originalPath: string): Promise<AssetEntity | null> {
|
||||
return this.repository.findOne({
|
||||
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
|
||||
*/
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] })
|
||||
async getAllByDeviceId(ownerId: string, deviceId: string): Promise<string[]> {
|
||||
const items = await this.repository.find({
|
||||
select: { deviceAssetId: true },
|
||||
|
@ -347,6 +355,7 @@ export class AssetRepository implements IAssetRepository {
|
|||
return items.map((asset) => asset.deviceAssetId);
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getById(id: string): Promise<AssetEntity | null> {
|
||||
return this.repository.findOne({
|
||||
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> {
|
||||
await this.repository.update({ id: In(ids) }, options);
|
||||
}
|
||||
|
@ -395,6 +405,7 @@ export class AssetRepository implements IAssetRepository {
|
|||
await this.repository.remove(asset);
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [[DummyValue.UUID], DummyValue.BUFFER] })
|
||||
getByChecksum(userId: string, checksum: Buffer): Promise<AssetEntity | null> {
|
||||
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> {
|
||||
let relations: FindOptionsRelations<AssetEntity> = {};
|
||||
let where: FindOptionsWhere<AssetEntity> | FindOptionsWhere<AssetEntity>[] = {};
|
||||
|
|
|
@ -4,11 +4,13 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||
import { IsNull, Not } from 'typeorm';
|
||||
import { Repository } from 'typeorm/repository/Repository';
|
||||
import { LibraryEntity, LibraryType } from '../entities';
|
||||
import { DummyValue, GenerateSql } from '../infra.util';
|
||||
|
||||
@Injectable()
|
||||
export class LibraryRepository implements ILibraryRepository {
|
||||
constructor(@InjectRepository(LibraryEntity) private repository: Repository<LibraryEntity>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
get(id: string, withDeleted = false): Promise<LibraryEntity | null> {
|
||||
return this.repository.findOneOrFail({
|
||||
where: {
|
||||
|
@ -19,6 +21,7 @@ export class LibraryRepository implements ILibraryRepository {
|
|||
});
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.STRING] })
|
||||
existsByName(name: string, withDeleted = false): Promise<boolean> {
|
||||
return this.repository.exist({
|
||||
where: {
|
||||
|
@ -28,10 +31,12 @@ export class LibraryRepository implements ILibraryRepository {
|
|||
});
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getCountForUser(ownerId: string): Promise<number> {
|
||||
return this.repository.countBy({ ownerId });
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getDefaultUploadLibrary(ownerId: string): Promise<LibraryEntity | null> {
|
||||
return this.repository.findOne({
|
||||
where: {
|
||||
|
@ -44,6 +49,7 @@ export class LibraryRepository implements ILibraryRepository {
|
|||
});
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getUploadLibraryCount(ownerId: string): Promise<number> {
|
||||
return this.repository.count({
|
||||
where: {
|
||||
|
@ -53,6 +59,7 @@ export class LibraryRepository implements ILibraryRepository {
|
|||
});
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getAllByUserId(ownerId: string, type?: LibraryType): Promise<LibraryEntity[]> {
|
||||
return this.repository.find({
|
||||
where: {
|
||||
|
@ -69,6 +76,7 @@ export class LibraryRepository implements ILibraryRepository {
|
|||
});
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [] })
|
||||
getAll(withDeleted = false, type?: LibraryType): Promise<LibraryEntity[]> {
|
||||
return this.repository.find({
|
||||
where: { type },
|
||||
|
@ -82,6 +90,7 @@ export class LibraryRepository implements ILibraryRepository {
|
|||
});
|
||||
}
|
||||
|
||||
@GenerateSql()
|
||||
getAllDeleted(): Promise<LibraryEntity[]> {
|
||||
return this.repository.find({
|
||||
where: {
|
||||
|
@ -114,6 +123,7 @@ export class LibraryRepository implements ILibraryRepository {
|
|||
return this.save(library);
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
async getStatistics(id: string): Promise<LibraryStatsResponseDto> {
|
||||
const stats = await this.repository
|
||||
.createQueryBuilder('libraries')
|
||||
|
@ -134,6 +144,7 @@ export class LibraryRepository implements ILibraryRepository {
|
|||
};
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
async getOnlineAssetPaths(libraryId: string): Promise<string[]> {
|
||||
// Return all non-offline asset paths for a given library
|
||||
const rawResults = await this.repository
|
||||
|
@ -153,6 +164,7 @@ export class LibraryRepository implements ILibraryRepository {
|
|||
return results;
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
async getAssetIds(libraryId: string, withDeleted = false): Promise<string[]> {
|
||||
let query = await this.repository
|
||||
.createQueryBuilder('library')
|
||||
|
|
|
@ -9,7 +9,7 @@ import { GeodataAdmin1Entity, GeodataAdmin2Entity, GeodataPlacesEntity, SystemMe
|
|||
import { DatabaseLock } from '@app/infra/utils/database-locks';
|
||||
import { Inject, Logger } from '@nestjs/common';
|
||||
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 { readFile } from 'fs/promises';
|
||||
import * as geotz from 'geo-tz';
|
||||
|
@ -181,7 +181,7 @@ export class MetadataRepository implements IMetadataRepository {
|
|||
return { country, state, city };
|
||||
}
|
||||
|
||||
getExifTags(path: string): Promise<ImmichTags | null> {
|
||||
readTags(path: string): Promise<ImmichTags | null> {
|
||||
return exiftool
|
||||
.read(path, undefined, {
|
||||
...DefaultReadTaskOptions,
|
||||
|
@ -198,4 +198,12 @@ export class MetadataRepository implements IMetadataRepository {
|
|||
return 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 { Repository } from 'typeorm';
|
||||
import { MoveEntity, PathType } from '../entities';
|
||||
import { DummyValue, GenerateSql } from '../infra.util';
|
||||
|
||||
@Injectable()
|
||||
export class MoveRepository implements IMoveRepository {
|
||||
|
@ -12,6 +13,7 @@ export class MoveRepository implements IMoveRepository {
|
|||
return this.repository.save(entity);
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING] })
|
||||
getByEntity(entityId: string, pathType: PathType): Promise<MoveEntity | null> {
|
||||
return this.repository.findOne({ where: { entityId, pathType } });
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { In, Repository } from 'typeorm';
|
||||
import { AssetEntity, AssetFaceEntity, PersonEntity } from '../entities';
|
||||
import { DummyValue, GenerateSql } from '../infra.util';
|
||||
|
||||
export class PersonRepository implements IPersonRepository {
|
||||
constructor(
|
||||
|
@ -36,6 +37,7 @@ export class PersonRepository implements IPersonRepository {
|
|||
return assetIds;
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [{ oldPersonId: DummyValue.UUID, newPersonId: DummyValue.UUID }] })
|
||||
async reassignFaces({ oldPersonId, newPersonId }: UpdateFacesData): Promise<number> {
|
||||
const result = await this.assetFaceRepository
|
||||
.createQueryBuilder()
|
||||
|
@ -57,18 +59,22 @@ export class PersonRepository implements IPersonRepository {
|
|||
return people.length;
|
||||
}
|
||||
|
||||
@GenerateSql()
|
||||
getAllFaces(): Promise<AssetFaceEntity[]> {
|
||||
return this.assetFaceRepository.find({ relations: { asset: true }, withDeleted: true });
|
||||
}
|
||||
|
||||
@GenerateSql()
|
||||
getAll(): Promise<PersonEntity[]> {
|
||||
return this.personRepository.find();
|
||||
}
|
||||
|
||||
@GenerateSql()
|
||||
getAllWithoutThumbnail(): Promise<PersonEntity[]> {
|
||||
return this.personRepository.findBy({ thumbnailPath: '' });
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getAllForUser(userId: string, options?: PersonSearchOptions): Promise<PersonEntity[]> {
|
||||
const queryBuilder = this.personRepository
|
||||
.createQueryBuilder('person')
|
||||
|
@ -89,6 +95,7 @@ export class PersonRepository implements IPersonRepository {
|
|||
return queryBuilder.getMany();
|
||||
}
|
||||
|
||||
@GenerateSql()
|
||||
getAllWithoutFaces(): Promise<PersonEntity[]> {
|
||||
return this.personRepository
|
||||
.createQueryBuilder('person')
|
||||
|
@ -99,10 +106,12 @@ export class PersonRepository implements IPersonRepository {
|
|||
.getMany();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getById(personId: string): Promise<PersonEntity | null> {
|
||||
return this.personRepository.findOne({ where: { id: personId } });
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.STRING, { withHidden: true }] })
|
||||
getByName(userId: string, personName: string, { withHidden }: PersonNameSearchOptions): Promise<PersonEntity[]> {
|
||||
const queryBuilder = this.personRepository
|
||||
.createQueryBuilder('person')
|
||||
|
@ -121,6 +130,7 @@ export class PersonRepository implements IPersonRepository {
|
|||
return queryBuilder.getMany();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
async getStatistics(personId: string): Promise<PersonStatistics> {
|
||||
return {
|
||||
assets: await this.assetFaceRepository
|
||||
|
@ -135,6 +145,7 @@ export class PersonRepository implements IPersonRepository {
|
|||
};
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getAssets(personId: string): Promise<AssetEntity[]> {
|
||||
return this.assetRepository.find({
|
||||
where: {
|
||||
|
@ -171,10 +182,12 @@ export class PersonRepository implements IPersonRepository {
|
|||
return this.personRepository.findOneByOrFail({ id });
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [[{ assetId: DummyValue.UUID, personId: DummyValue.UUID }]] })
|
||||
async getFacesByIds(ids: AssetFaceId[]): Promise<AssetFaceEntity[]> {
|
||||
return this.assetFaceRepository.find({ where: ids, relations: { asset: true }, withDeleted: true });
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
async getRandomFace(personId: string): Promise<AssetFaceEntity | null> {
|
||||
return this.assetFaceRepository.findOneBy({ personId });
|
||||
}
|
||||
|
|
|
@ -3,11 +3,13 @@ import { Injectable } from '@nestjs/common';
|
|||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { SharedLinkEntity } from '../entities';
|
||||
import { DummyValue, GenerateSql } from '../infra.util';
|
||||
|
||||
@Injectable()
|
||||
export class SharedLinkRepository implements ISharedLinkRepository {
|
||||
constructor(@InjectRepository(SharedLinkEntity) private repository: Repository<SharedLinkEntity>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID] })
|
||||
get(userId: string, id: string): Promise<SharedLinkEntity | null> {
|
||||
return this.repository.findOne({
|
||||
where: {
|
||||
|
@ -39,6 +41,7 @@ export class SharedLinkRepository implements ISharedLinkRepository {
|
|||
});
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getAll(userId: string): Promise<SharedLinkEntity[]> {
|
||||
return this.repository.find({
|
||||
where: {
|
||||
|
@ -56,6 +59,7 @@ export class SharedLinkRepository implements ISharedLinkRepository {
|
|||
});
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.BUFFER] })
|
||||
async getByKey(key: Buffer): Promise<SharedLinkEntity | null> {
|
||||
return await this.repository.findOne({
|
||||
where: {
|
||||
|
|
|
@ -4,6 +4,7 @@ import axios from 'axios';
|
|||
import { readFile } from 'fs/promises';
|
||||
import { In, Repository } from 'typeorm';
|
||||
import { SystemConfigEntity } from '../entities';
|
||||
import { DummyValue, GenerateSql } from '../infra.util';
|
||||
|
||||
export class SystemConfigRepository implements ISystemConfigRepository {
|
||||
constructor(
|
||||
|
@ -14,6 +15,7 @@ export class SystemConfigRepository implements ISystemConfigRepository {
|
|||
return axios.get(url).then((response) => response.data);
|
||||
}
|
||||
|
||||
@GenerateSql()
|
||||
load(): Promise<SystemConfigEntity[]> {
|
||||
return this.repository.find();
|
||||
}
|
||||
|
@ -26,6 +28,7 @@ export class SystemConfigRepository implements ISystemConfigRepository {
|
|||
return this.repository.save(items);
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.STRING] })
|
||||
async deleteKeys(keys: string[]): Promise<void> {
|
||||
await this.repository.delete({ key: In(keys) });
|
||||
}
|
||||
|
|
|
@ -3,11 +3,13 @@ import { Injectable } from '@nestjs/common';
|
|||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { UserTokenEntity } from '../entities';
|
||||
import { DummyValue, GenerateSql } from '../infra.util';
|
||||
|
||||
@Injectable()
|
||||
export class UserTokenRepository implements IUserTokenRepository {
|
||||
constructor(@InjectRepository(UserTokenEntity) private repository: Repository<UserTokenEntity>) {}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.STRING] })
|
||||
getByToken(token: string): Promise<UserTokenEntity | null> {
|
||||
return this.repository.findOne({ where: { token }, relations: { user: true } });
|
||||
}
|
||||
|
@ -35,6 +37,7 @@ export class UserTokenRepository implements IUserTokenRepository {
|
|||
return this.repository.save(userToken);
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
async delete(id: string): Promise<void> {
|
||||
await this.repository.delete({ id });
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
|
|||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { IsNull, Not, Repository } from 'typeorm';
|
||||
import { UserEntity } from '../entities';
|
||||
import { DummyValue, GenerateSql } from '../infra.util';
|
||||
|
||||
@Injectable()
|
||||
export class UserRepository implements IUserRepository {
|
||||
|
@ -16,14 +17,17 @@ export class UserRepository implements IUserRepository {
|
|||
});
|
||||
}
|
||||
|
||||
@GenerateSql()
|
||||
async getAdmin(): Promise<UserEntity | null> {
|
||||
return this.userRepository.findOne({ where: { isAdmin: true } });
|
||||
}
|
||||
|
||||
@GenerateSql()
|
||||
async hasAdmin(): Promise<boolean> {
|
||||
return this.userRepository.exist({ where: { isAdmin: true } });
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.EMAIL] })
|
||||
async getByEmail(email: string, withPassword?: boolean): Promise<UserEntity | null> {
|
||||
let builder = this.userRepository.createQueryBuilder('user').where({ email });
|
||||
|
||||
|
@ -34,10 +38,12 @@ export class UserRepository implements IUserRepository {
|
|||
return builder.getOne();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.STRING] })
|
||||
async getByStorageLabel(storageLabel: string): Promise<UserEntity | null> {
|
||||
return this.userRepository.findOne({ where: { storageLabel } });
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.STRING] })
|
||||
async getByOAuthId(oauthId: string): Promise<UserEntity | null> {
|
||||
return this.userRepository.findOne({ where: { oauthId } });
|
||||
}
|
||||
|
@ -76,6 +82,7 @@ export class UserRepository implements IUserRepository {
|
|||
return this.userRepository.recover(user);
|
||||
}
|
||||
|
||||
@GenerateSql()
|
||||
async getUserStats(): Promise<UserStatsQueryResponse[]> {
|
||||
const stats = await this.userRepository
|
||||
.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 immich } from './immich/main';
|
||||
import { bootstrap as admin } from './immich-admin/main';
|
||||
import { bootstrap as server } from './immich/main';
|
||||
import { bootstrap as microservices } from './microservices/main';
|
||||
|
||||
const immichApp = process.argv[2] || process.env.IMMICH_APP;
|
||||
|
@ -12,13 +12,13 @@ function bootstrap() {
|
|||
switch (immichApp) {
|
||||
case 'immich':
|
||||
process.title = 'immich_server';
|
||||
return immich();
|
||||
return server();
|
||||
case 'microservices':
|
||||
process.title = 'immich_microservices';
|
||||
return microservices();
|
||||
case 'admin-cli':
|
||||
case 'immich-admin':
|
||||
process.title = 'immich_admin_cli';
|
||||
return adminCli();
|
||||
return admin();
|
||||
default:
|
||||
console.log(`Invalid app name: ${immichApp}. Expected one of immich|microservices|cli`);
|
||||
process.exit(1);
|
||||
|
|
|
@ -84,6 +84,7 @@ export class AppService {
|
|||
[JobName.QUEUE_SIDECAR]: (data) => this.metadataService.handleQueueSidecar(data),
|
||||
[JobName.SIDECAR_DISCOVERY]: (data) => this.metadataService.handleSidecarDiscovery(data),
|
||||
[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]: (data) => this.libraryService.handleQueueAssetRefresh(data),
|
||||
[JobName.LIBRARY_DELETE]: (data) => this.libraryService.handleDeleteLibrary(data),
|
||||
|
|
|
@ -32,4 +32,4 @@ if [ "$REDIS_PASSWORD_FILE" ]; then
|
|||
unset REDIS_PASSWORD_FILE
|
||||
fi
|
||||
|
||||
exec node dist/main $@
|
||||
exec node /usr/src/app/dist/main $@
|
||||
|
|
|
@ -700,6 +700,54 @@ describe(`${AssetController.name} (e2e)`, () => {
|
|||
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 () => {
|
||||
const { status, body } = await request(server)
|
||||
.put(`/asset/${asset1.id}`)
|
||||
|
|
|
@ -22,11 +22,12 @@ export const newAccessRepositoryMock = (reset = true): IAccessRepositoryMock =>
|
|||
hasAlbumOwnerAccess: jest.fn(),
|
||||
hasCreateAccess: jest.fn(),
|
||||
},
|
||||
|
||||
asset: {
|
||||
hasOwnerAccess: jest.fn(),
|
||||
hasAlbumAccess: jest.fn(),
|
||||
hasPartnerAccess: jest.fn(),
|
||||
hasSharedLinkAccess: jest.fn(),
|
||||
checkOwnerAccess: jest.fn().mockResolvedValue(new Set()),
|
||||
checkAlbumAccess: jest.fn().mockResolvedValue(new Set()),
|
||||
checkPartnerAccess: jest.fn().mockResolvedValue(new Set()),
|
||||
checkSharedLinkAccess: jest.fn().mockResolvedValue(new Set()),
|
||||
},
|
||||
|
||||
album: {
|
||||
|
|
|
@ -2,9 +2,10 @@ import { IMetadataRepository } from '@app/domain';
|
|||
|
||||
export const newMetadataRepositoryMock = (): jest.Mocked<IMetadataRepository> => {
|
||||
return {
|
||||
getExifTags: jest.fn(),
|
||||
init: jest.fn(),
|
||||
teardown: 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
|
||||
*/
|
||||
export interface AssetBulkUpdateDto {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AssetBulkUpdateDto
|
||||
*/
|
||||
'dateTimeOriginal'?: string;
|
||||
/**
|
||||
*
|
||||
* @type {Array<string>}
|
||||
|
@ -465,6 +471,18 @@ export interface AssetBulkUpdateDto {
|
|||
* @memberof AssetBulkUpdateDto
|
||||
*/
|
||||
'isFavorite'?: boolean;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof AssetBulkUpdateDto
|
||||
*/
|
||||
'latitude'?: number;
|
||||
/**
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof AssetBulkUpdateDto
|
||||
*/
|
||||
'longitude'?: number;
|
||||
/**
|
||||
*
|
||||
* @type {boolean}
|
||||
|
@ -4137,6 +4155,12 @@ export interface UpdateAlbumDto {
|
|||
* @interface UpdateAssetDto
|
||||
*/
|
||||
export interface UpdateAssetDto {
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof UpdateAssetDto
|
||||
*/
|
||||
'dateTimeOriginal'?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
|
@ -4155,6 +4179,18 @@ export interface UpdateAssetDto {
|
|||
* @memberof UpdateAssetDto
|
||||
*/
|
||||
'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 { shouldIgnoreShortcut } from '$lib/utils/shortcut';
|
||||
import { mdiFileImagePlusOutline, mdiFolderDownloadOutline } from '@mdi/js';
|
||||
import UpdatePanel from '../shared-components/update-panel.svelte';
|
||||
|
||||
export let sharedLink: SharedLinkResponseDto;
|
||||
export let user: UserResponseDto | undefined = undefined;
|
||||
|
@ -143,7 +144,7 @@
|
|||
<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"
|
||||
>
|
||||
<AssetGrid {album} {user} {assetStore} {assetInteractionStore}>
|
||||
<AssetGrid {album} {assetStore} {assetInteractionStore}>
|
||||
<section class="pt-24">
|
||||
<!-- ALBUM TITLE -->
|
||||
<p
|
||||
|
@ -167,4 +168,5 @@
|
|||
</p>
|
||||
</section>
|
||||
</AssetGrid>
|
||||
<UpdatePanel {assetStore} />
|
||||
</main>
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
AssetTypeEnum,
|
||||
ReactionType,
|
||||
SharedLinkResponseDto,
|
||||
UserResponseDto,
|
||||
} from '@api';
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
|
||||
import { fly } from 'svelte/transition';
|
||||
|
@ -42,6 +41,7 @@
|
|||
import { updateNumberOfComments } from '$lib/stores/activity.store';
|
||||
import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
|
||||
import SlideshowBar from './slideshow-bar.svelte';
|
||||
import { user } from '$lib/stores/user.store';
|
||||
|
||||
export let assetStore: AssetStore | null = null;
|
||||
export let asset: AssetResponseDto;
|
||||
|
@ -51,7 +51,6 @@
|
|||
export let force = false;
|
||||
export let withStacked = false;
|
||||
export let isShared = false;
|
||||
export let user: UserResponseDto | null = null;
|
||||
export let album: AlbumResponseDto | null = null;
|
||||
|
||||
let reactions: ActivityResponseDto[] = [];
|
||||
|
@ -143,10 +142,10 @@
|
|||
};
|
||||
|
||||
const getFavorite = async () => {
|
||||
if (album && user) {
|
||||
if (album && $user) {
|
||||
try {
|
||||
const { data } = await api.activityApi.getActivities({
|
||||
userId: user.id,
|
||||
userId: $user.id,
|
||||
assetId: asset.id,
|
||||
albumId: album.id,
|
||||
type: ReactionType.Like,
|
||||
|
@ -743,7 +742,7 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
{#if isShared && album && isShowActivity && user}
|
||||
{#if isShared && album && isShowActivity && $user}
|
||||
<div
|
||||
transition:fly={{ duration: 150 }}
|
||||
id="activity-panel"
|
||||
|
@ -751,7 +750,7 @@
|
|||
translate="yes"
|
||||
>
|
||||
<ActivityViewer
|
||||
{user}
|
||||
user={$user}
|
||||
disabled={!album.isActivityEnabled}
|
||||
assetType={asset.type}
|
||||
albumOwnerId={album.ownerId}
|
||||
|
|
|
@ -5,22 +5,31 @@
|
|||
import { getAssetFilename } from '$lib/utils/asset-utils';
|
||||
import { AlbumResponseDto, AssetResponseDto, ThumbnailFormat, api } from '@api';
|
||||
import { DateTime } from 'luxon';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { createEventDispatcher, onDestroy } from 'svelte';
|
||||
import { slide } from 'svelte/transition';
|
||||
import { asByteUnitString } from '../../utils/byte-units';
|
||||
import ImageThumbnail from '../assets/thumbnail/image-thumbnail.svelte';
|
||||
import UserAvatar from '../shared-components/user-avatar.svelte';
|
||||
import ChangeDate from '$lib/components/shared-components/change-date.svelte';
|
||||
import {
|
||||
mdiCalendar,
|
||||
mdiCameraIris,
|
||||
mdiClose,
|
||||
mdiPencil,
|
||||
mdiImageOutline,
|
||||
mdiMapMarkerOutline,
|
||||
mdiInformationOutline,
|
||||
mdiEye,
|
||||
mdiEyeOff,
|
||||
} from '@mdi/js';
|
||||
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 { websocketStore } from '$lib/stores/websocket';
|
||||
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 albums: AlbumResponseDto[] = [];
|
||||
|
@ -51,6 +60,17 @@
|
|||
})();
|
||||
|
||||
$: people = asset.people || [];
|
||||
$: showingHiddenPeople = false;
|
||||
|
||||
const unsubscribe = websocketStore.onAssetUpdate.subscribe((assetUpdate) => {
|
||||
if (assetUpdate && assetUpdate.id === asset.id) {
|
||||
asset = assetUpdate;
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
unsubscribe();
|
||||
});
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
|
@ -79,9 +99,7 @@
|
|||
try {
|
||||
await api.assetApi.updateAsset({
|
||||
id: asset.id,
|
||||
updateAssetDto: {
|
||||
description: description,
|
||||
},
|
||||
updateAssetDto: { description },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
@ -90,6 +108,35 @@
|
|||
|
||||
let showAssetPath = false;
|
||||
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>
|
||||
|
||||
<section class="p-2 dark:bg-immich-dark-bg dark:text-immich-dark-fg">
|
||||
|
@ -134,25 +181,38 @@
|
|||
|
||||
{#if !api.isSharedLink && people.length > 0}
|
||||
<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)}
|
||||
<a
|
||||
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')}
|
||||
>
|
||||
<ImageThumbnail
|
||||
curve
|
||||
shadow
|
||||
url={api.getPeopleThumbnailUrl(person.id)}
|
||||
altText={person.name}
|
||||
title={person.name}
|
||||
widthStyle="90px"
|
||||
heightStyle="90px"
|
||||
thumbhash={null}
|
||||
/>
|
||||
<div class="relative">
|
||||
<ImageThumbnail
|
||||
curve
|
||||
shadow
|
||||
url={api.getPeopleThumbnailUrl(person.id)}
|
||||
altText={person.name}
|
||||
title={person.name}
|
||||
widthStyle="90px"
|
||||
heightStyle="90px"
|
||||
thumbhash={null}
|
||||
hidden={person.isHidden}
|
||||
/>
|
||||
</div>
|
||||
<p class="mt-1 truncate font-medium" title={person.name}>{person.name}</p>
|
||||
{#if person.birthDate}
|
||||
{@const personBirthDate = DateTime.fromISO(person.birthDate)}
|
||||
|
@ -191,41 +251,120 @@
|
|||
<p class="text-sm">DETAILS</p>
|
||||
{/if}
|
||||
|
||||
{#if asset.exifInfo?.dateTimeOriginal}
|
||||
{#if asset.exifInfo?.dateTimeOriginal && !asset.isReadOnly}
|
||||
{@const assetDateTimeOriginal = DateTime.fromISO(asset.exifInfo.dateTimeOriginal, {
|
||||
zone: asset.exifInfo.timeZone ?? undefined,
|
||||
})}
|
||||
<div class="flex gap-4 py-4">
|
||||
<div>
|
||||
<Icon path={mdiCalendar} size="24" />
|
||||
</div>
|
||||
<div
|
||||
class="flex justify-between place-items-start gap-4 py-4"
|
||||
tabindex="0"
|
||||
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>
|
||||
<p>
|
||||
{assetDateTimeOriginal.toLocaleString(
|
||||
{
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
year: 'numeric',
|
||||
},
|
||||
{ locale: $locale },
|
||||
)}
|
||||
</p>
|
||||
<div class="flex gap-2 text-sm">
|
||||
<div>
|
||||
<p>
|
||||
{assetDateTimeOriginal.toLocaleString(
|
||||
{
|
||||
weekday: 'short',
|
||||
hour: 'numeric',
|
||||
minute: '2-digit',
|
||||
timeZoneName: 'longOffset',
|
||||
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 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}
|
||||
<div class="flex gap-4 py-4">
|
||||
|
@ -292,24 +431,88 @@
|
|||
</div>
|
||||
{/if}
|
||||
|
||||
{#if asset.exifInfo?.city}
|
||||
<div class="flex gap-4 py-4">
|
||||
<div><Icon path={mdiMapMarkerOutline} size="24" /></div>
|
||||
{#if asset.exifInfo?.city && !asset.isReadOnly}
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
|
||||
{#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>
|
||||
{: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}
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
|
||||
export let color: Color = 'transparent-gray';
|
||||
export let disabled = false;
|
||||
export let fullwidth = false;
|
||||
</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 />
|
||||
</Button>
|
||||
|
|
|
@ -29,10 +29,13 @@
|
|||
icon?: string;
|
||||
};
|
||||
|
||||
let showMenu = false;
|
||||
export let showMenu = false;
|
||||
export let controlable = false;
|
||||
|
||||
const handleClickOutside = () => {
|
||||
showMenu = false;
|
||||
if (!controlable) {
|
||||
showMenu = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectOption = (option: T) => {
|
||||
|
@ -60,7 +63,7 @@
|
|||
|
||||
<div id="dropdown-button" use:clickOutside on:outclick={handleClickOutside} on:escape={handleClickOutside}>
|
||||
<!-- BUTTON TITLE -->
|
||||
<LinkButton on:click={() => (showMenu = true)}>
|
||||
<LinkButton on:click={() => (showMenu = true)} fullwidth>
|
||||
<div class="flex place-items-center gap-2 text-sm">
|
||||
{#if renderedSelectedOption?.icon}
|
||||
<Icon path={renderedSelectedOption.icon} size="18" />
|
||||
|
@ -72,13 +75,13 @@
|
|||
<!-- DROP DOWN MENU -->
|
||||
{#if showMenu}
|
||||
<div
|
||||
transition:fly={{ y: -30, x: 30, duration: 200 }}
|
||||
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"
|
||||
transition:fly={{ y: -30, x: 30, duration: 100 }}
|
||||
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)}
|
||||
{@const renderedOption = renderOption(option)}
|
||||
<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)}
|
||||
>
|
||||
{#if _.isEqual(selectedOption, option)}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import LoadingSpinner from '../shared-components/loading-spinner.svelte';
|
||||
import { searchNameLocal } from '$lib/utils/person';
|
||||
|
||||
export let person: PersonResponseDto;
|
||||
let people: PersonResponseDto[] = [];
|
||||
|
@ -56,12 +57,7 @@
|
|||
}
|
||||
if (!force) {
|
||||
if (people.length < 20 && name.startsWith(searchWord)) {
|
||||
people = peopleCopy
|
||||
.filter((person: PersonResponseDto) => {
|
||||
const nameParts = person.name.split(' ');
|
||||
return nameParts.some((splitName) => splitName.toLowerCase().startsWith(name.toLowerCase()));
|
||||
})
|
||||
.slice(0, 10);
|
||||
people = searchNameLocal(name, peopleCopy, 10);
|
||||
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