From 78190a0acb96e6e917686642e340672e46d7c935 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Mon, 4 Apr 2022 19:37:31 +0200 Subject: [PATCH] migrating to eslint --- .browserslistrc | 1 - .eslintrc.json | 43 + angular.json | 56 +- benchmark/BMConfig.ts | 37 +- gulpfile.ts | 303 +- package-lock.json | 3427 ++++++++++++++++- package.json | 13 +- src/backend/Logger.ts | 23 +- src/backend/ProjectPath.ts | 3 +- .../exceptions/LocationLookupException.ts | 1 - src/backend/index.ts | 5 +- src/backend/middlewares/AlbumMWs.ts | 82 +- src/backend/middlewares/GalleryMWs.ts | 266 +- src/backend/middlewares/NotificationMWs.ts | 13 +- src/backend/middlewares/PersonMWs.ts | 101 +- src/backend/middlewares/RenderingMWs.ts | 122 +- src/backend/middlewares/ServerTimingMWs.ts | 42 +- src/backend/middlewares/SharingMWs.ts | 186 +- src/backend/middlewares/VersionMWs.ts | 31 +- src/backend/middlewares/admin/AdminMWs.ts | 184 +- .../customtypings/ExtendedRequest.d.ts | 8 +- .../thumbnail/PhotoConverterMWs.ts | 18 +- .../thumbnail/ThumbnailGeneratorMWs.ts | 181 +- .../middlewares/user/AuthenticationMWs.ts | 238 +- src/backend/middlewares/user/UserMWs.ts | 98 +- .../user/UserRequestConstrainsMWs.ts | 57 +- src/backend/model/DiskManger.ts | 43 +- src/backend/model/FFmpegFactory.ts | 3 +- src/backend/model/Localizations.ts | 20 +- src/backend/model/NotifocationManager.ts | 30 +- src/backend/model/ObjectManagers.ts | 73 +- src/backend/model/PasswordHelper.ts | 8 +- src/backend/model/database/LocationManager.ts | 16 +- .../database/interfaces/IAlbumManager.ts | 19 +- .../database/interfaces/IGalleryManager.ts | 14 +- .../database/interfaces/IIndexingManager.ts | 4 +- .../model/database/interfaces/IJobManager.ts | 16 +- .../database/interfaces/IObjectManager.ts | 2 +- .../database/interfaces/IPersonManager.ts | 10 +- .../database/interfaces/IPreviewManager.ts | 21 +- .../database/interfaces/ISearchManager.ts | 20 +- .../database/interfaces/ISharingManager.ts | 6 +- .../model/database/interfaces/IUserManager.ts | 6 +- .../database/interfaces/IVersionManager.ts | 2 +- .../model/database/memory/AlbumManager.ts | 18 +- .../model/database/memory/GalleryManager.ts | 40 +- .../model/database/memory/IndexingManager.ts | 6 +- .../model/database/memory/PersonManager.ts | 8 +- .../model/database/memory/PreviewManager.ts | 12 +- .../model/database/memory/SearchManager.ts | 18 +- .../model/database/memory/SharingManager.ts | 12 +- .../model/database/memory/UserManager.ts | 25 +- .../model/database/memory/VersionManager.ts | 4 +- .../model/database/sql/AlbumManager.ts | 76 +- .../model/database/sql/GalleryManager.ts | 323 +- .../model/database/sql/IGalleryManager.ts | 25 +- .../model/database/sql/IPersonManager.ts | 2 +- .../model/database/sql/ISearchManager.ts | 25 +- .../model/database/sql/IndexingManager.ts | 381 +- .../model/database/sql/PersonManager.ts | 79 +- .../model/database/sql/PreviewManager.ts | 197 +- .../model/database/sql/SQLConnection.ts | 138 +- .../model/database/sql/SearchManager.ts | 772 ++-- .../model/database/sql/SharingManager.ts | 43 +- src/backend/model/database/sql/UserManager.ts | 28 +- .../model/database/sql/VersionManager.ts | 46 +- .../database/sql/enitites/DirectoryEntity.ts | 67 +- .../database/sql/enitites/EntityUtils.ts | 16 +- .../database/sql/enitites/FaceRegionEntry.ts | 24 +- .../model/database/sql/enitites/FileEntity.ts | 23 +- .../database/sql/enitites/MediaEntity.ts | 123 +- .../database/sql/enitites/PersonEntry.ts | 33 +- .../database/sql/enitites/PhotoEntity.ts | 20 +- .../database/sql/enitites/SharingEntity.ts | 32 +- .../model/database/sql/enitites/UserEntity.ts | 8 +- .../database/sql/enitites/VersionEntity.ts | 4 +- .../database/sql/enitites/VideoEntity.ts | 30 +- .../sql/enitites/album/AlbumBaseEntity.ts | 27 +- .../sql/enitites/album/SavedSearchEntity.ts | 17 +- .../model/diagnostics/ConfigDiagnostics.ts | 358 +- .../model/fileprocessing/PhotoProcessing.ts | 197 +- .../model/fileprocessing/VideoProcessing.ts | 83 +- src/backend/model/jobs/JobManager.ts | 114 +- src/backend/model/jobs/JobProgressManager.ts | 43 +- src/backend/model/jobs/JobRepository.ts | 22 +- src/backend/model/jobs/jobs/DBResetJob.ts | 19 +- src/backend/model/jobs/jobs/FileJob.ts | 86 +- src/backend/model/jobs/jobs/IJob.ts | 6 +- src/backend/model/jobs/jobs/IJobListener.ts | 12 +- src/backend/model/jobs/jobs/IndexingJob.ts | 68 +- src/backend/model/jobs/jobs/Job.ts | 62 +- src/backend/model/jobs/jobs/JobProgress.ts | 29 +- .../model/jobs/jobs/PhotoConvertingJob.ts | 20 +- .../model/jobs/jobs/PreviewFillingJob.ts | 34 +- .../model/jobs/jobs/PreviewResetJob.ts | 19 +- .../model/jobs/jobs/TempFolderCleaningJob.ts | 47 +- .../model/jobs/jobs/ThumbnailGenerationJob.ts | 52 +- .../model/jobs/jobs/VideoConvertingJob.ts | 13 +- .../model/threading/DiskMangerWorker.ts | 155 +- src/backend/model/threading/MetadataLoader.ts | 543 +-- src/backend/model/threading/PhotoWorker.ts | 75 +- src/backend/model/threading/TaskExecuter.ts | 8 +- src/backend/model/threading/TaskQue.ts | 38 +- src/backend/model/threading/ThreadPool.ts | 66 +- .../model/threading/VideoConverterWorker.ts | 46 +- src/backend/model/threading/Worker.ts | 30 +- src/backend/routes/AlbumRouter.ts | 26 +- src/backend/routes/ErrorRouter.ts | 40 +- src/backend/routes/GalleryRouter.ts | 96 +- src/backend/routes/LoggerRouter.ts | 23 +- src/backend/routes/NotificationRouter.ts | 19 +- src/backend/routes/PersonRouter.ts | 30 +- src/backend/routes/PublicRouter.ts | 174 +- src/backend/routes/Router.ts | 24 +- src/backend/routes/SharingRouter.ts | 33 +- src/backend/routes/UserRouter.ts | 45 +- src/backend/routes/admin/AdminRouter.ts | 31 +- src/backend/routes/admin/SettingsRouter.ts | 68 +- src/backend/server.ts | 98 +- src/common/BackendTexts.ts | 6 +- src/common/PG2ConfMap.ts | 10 +- src/common/QueryParams.ts | 8 +- src/common/SearchQueryParser.ts | 302 +- src/common/SupportedFormats.ts | 62 +- src/common/Utils.ts | 39 +- src/common/config/private/Config.ts | 47 +- src/common/config/private/PrivateConfig.ts | 299 +- src/common/config/private/WebConfig.ts | 2 +- src/common/config/public/ClientConfig.ts | 101 +- src/common/config/public/Config.ts | 14 +- src/common/entities/AutoCompleteItem.ts | 5 +- src/common/entities/ConentWrapper.ts | 13 +- src/common/entities/DirectoryDTO.ts | 26 +- src/common/entities/DuplicatesDTO.ts | 2 +- src/common/entities/Error.ts | 21 +- src/common/entities/FileDTO.ts | 3 +- src/common/entities/LoginCredential.ts | 10 +- src/common/entities/MediaDTO.ts | 24 +- src/common/entities/Message.ts | 2 +- src/common/entities/NotificationDTO.ts | 10 +- src/common/entities/PasswordChangeRequest.ts | 9 +- src/common/entities/PersonDTO.ts | 2 +- src/common/entities/PhotoDTO.ts | 7 +- src/common/entities/SearchQueryDTO.ts | 60 +- src/common/entities/SearchResultDTO.ts | 8 +- src/common/entities/SharingDTO.ts | 2 +- src/common/entities/SortingMethods.ts | 8 +- src/common/entities/UserDTO.ts | 27 +- .../entities/UserModificationRequest.ts | 3 +- src/common/entities/VideoDTO.ts | 5 +- src/common/entities/album/AlbumBaseDTO.ts | 2 +- src/common/entities/album/SavedSearchDTO.ts | 6 +- src/common/entities/job/JobDTO.ts | 7 +- src/common/entities/job/JobProgressDTO.ts | 17 +- src/common/entities/job/JobScheduleDTO.ts | 51 +- .../entities/settings/BasicConfigDTO.ts | 7 +- .../entities/settings/OtherConfigDTO.ts | 5 +- src/common/event/Event.ts | 13 +- src/common/event/EventLimit.ts | 17 +- src/frontend/app/app.component.ts | 43 +- src/frontend/app/app.module.ts | 287 +- src/frontend/app/app.routing.ts | 71 +- src/frontend/app/model/IRenderable.ts | 9 +- .../app/model/backendtext.service.spec.ts | 19 +- src/frontend/app/model/backendtext.service.ts | 11 +- src/frontend/app/model/navigation.service.ts | 22 +- .../network/autehentication.service.spec.ts | 53 +- .../model/network/authentication.service.ts | 29 +- .../app/model/network/helper/auth.guard.ts | 27 +- .../model/network/helper/csrf.interceptor.ts | 26 +- .../model/network/helper/error.interceptor.ts | 41 +- .../app/model/network/network.service.spec.ts | 324 +- .../app/model/network/network.service.ts | 63 +- .../app/model/network/user.service.spec.ts | 55 +- .../app/model/network/user.service.ts | 39 +- .../app/model/notification.service.ts | 61 +- src/frontend/app/model/page.helper.ts | 31 +- src/frontend/app/model/query.service.ts | 54 +- .../app/model/seededRandom.service.ts | 6 +- src/frontend/app/model/version.service.ts | 5 +- src/frontend/app/pipes/DurationPipe.ts | 12 +- src/frontend/app/pipes/FileDTOToPathPipe.ts | 16 +- src/frontend/app/pipes/FileSizePipe.ts | 7 +- src/frontend/app/pipes/GPXFilesFilterPipe.ts | 13 +- .../app/pipes/IconizeSortingMethod.ts | 7 +- src/frontend/app/pipes/MDFilesFilterPipe.ts | 11 +- src/frontend/app/pipes/PhotoFilterPipe.ts | 13 +- src/frontend/app/pipes/StringifyRolePipe.ts | 7 +- .../app/pipes/StringifySearchQuery.ts | 13 +- .../app/pipes/StringifySortingMethod.ts | 10 +- .../app/ui/admin/admin.component.html | 8 +- src/frontend/app/ui/admin/admin.component.ts | 71 +- .../app/ui/albums/album/album.component.css | 2 +- .../app/ui/albums/album/album.component.ts | 53 +- .../app/ui/albums/albums.component.ts | 51 +- src/frontend/app/ui/albums/albums.service.ts | 29 +- .../saved-search-popup.component.ts | 19 +- .../ui/duplicates/duplicates.component.css | 8 +- .../app/ui/duplicates/duplicates.component.ts | 171 +- .../app/ui/duplicates/duplicates.service.ts | 15 +- .../photo/photo.duplicates.component.ts | 20 +- src/frontend/app/ui/faces/Person.ts | 17 +- .../app/ui/faces/face/face.component.ts | 60 +- src/frontend/app/ui/faces/faces.component.css | 4 +- .../app/ui/faces/faces.component.html | 3 +- src/frontend/app/ui/faces/faces.component.ts | 34 +- src/frontend/app/ui/faces/faces.service.ts | 26 +- src/frontend/app/ui/frame/frame.component.css | 2 +- .../app/ui/frame/frame.component.html | 6 +- src/frontend/app/ui/frame/frame.component.ts | 36 +- src/frontend/app/ui/gallery/Media.ts | 62 +- src/frontend/app/ui/gallery/MediaIcon.ts | 75 +- .../gallery/blog/blog.gallery.component.css | 2 +- .../ui/gallery/blog/blog.gallery.component.ts | 22 +- .../app/ui/gallery/blog/blog.service.ts | 24 +- .../app/ui/gallery/cache.gallery.service.ts | 142 +- .../app/ui/gallery/content.service.ts | 94 +- .../directories/directories.component.ts | 19 +- .../directory/directory.gallery.component.ts | 48 +- .../filter/filter.gallery.component.ts | 84 +- .../app/ui/gallery/filter/filter.service.ts | 251 +- .../app/ui/gallery/fullscreen.service.ts | 13 +- .../app/ui/gallery/gallery.component.html | 7 +- .../app/ui/gallery/gallery.component.ts | 140 +- src/frontend/app/ui/gallery/grid/GridMedia.ts | 20 +- .../app/ui/gallery/grid/GridRowBuilder.ts | 34 +- .../ui/gallery/grid/grid.gallery.component.ts | 188 +- .../loading.photo.grid.gallery.component.ts | 5 +- .../photo/photo.grid.gallery.component.html | 5 +- .../photo/photo.grid.gallery.component.ts | 118 +- .../controls.lightbox.gallery.component.css | 6 +- .../controls.lightbox.gallery.component.html | 2 +- .../controls.lightbox.gallery.component.ts | 122 +- .../info-panel.lightbox.gallery.component.ts | 152 +- .../lightbox/lightbox.gallery.component.ts | 329 +- .../media/media.lightbox.gallery.component.ts | 89 +- .../lightbox.map.gallery.component.css | 14 +- .../lightbox.map.gallery.component.ts | 403 +- .../ui/gallery/map/map.gallery.component.ts | 90 +- .../app/ui/gallery/map/map.service.ts | 81 +- .../navigator/navigator.gallery.component.ts | 93 +- .../ui/gallery/navigator/sorting.service.ts | 282 +- .../app/ui/gallery/overlay.service.ts | 8 +- ...random-query-builder.gallery.component.css | 4 +- ...andom-query-builder.gallery.component.html | 2 +- .../random-query-builder.gallery.component.ts | 75 +- .../gallery/search/AutoCompleteRenderItem.ts | 15 +- .../ui/gallery/search/autocomplete.service.ts | 218 +- .../query-builder.gallery.component.css | 1 + .../query-bulder.gallery.component.ts | 78 +- .../query-entry.search.gallery.component.html | 10 +- .../query-entry.search.gallery.component.ts | 73 +- .../search-field-base.gallery.component.css | 1 + .../search-field-base.gallery.component.ts | 204 +- .../search-field.gallery.component.css | 1 + .../search-field.gallery.component.html | 2 - .../search-field.gallery.component.ts | 93 +- .../search/search-query-parser.service.ts | 15 +- .../search/search.gallery.component.html | 30 +- .../search/search.gallery.component.ts | 110 +- src/frontend/app/ui/gallery/share.service.ts | 83 +- .../gallery/share/share.gallery.component.css | 2 +- .../gallery/share/share.gallery.component.ts | 77 +- .../app/ui/gallery/thumbnailLoader.service.ts | 109 +- .../ui/gallery/thumbnailManager.service.ts | 123 +- .../app/ui/language/language.component.ts | 10 +- src/frontend/app/ui/login/login.component.ts | 18 +- .../settings/_abstract/ISettingsComponent.ts | 1 - .../_abstract/abstract.settings.component.ts | 96 +- .../_abstract/abstract.settings.service.ts | 12 +- .../settings-entry.component.html | 6 +- .../settings-entry.component.ts | 122 +- .../settings-entry.settings.component.css | 3 +- .../albums/albums.settings.component.html | 6 +- .../albums/albums.settings.component.ts | 44 +- .../albums/albums.settings.service.ts | 21 +- .../basic/basic.settings.component.html | 10 +- .../basic/basic.settings.component.ts | 55 +- .../settings/basic/basic.settings.service.ts | 20 +- .../database/database.settings.component.html | 14 +- .../database/database.settings.component.ts | 54 +- .../database/database.settings.service.ts | 20 +- .../faces/faces.settings.component.html | 6 +- .../faces/faces.settings.component.ts | 55 +- .../settings/faces/faces.settings.service.ts | 28 +- .../indexing/indexing.settings.component.css | 2 +- .../indexing/indexing.settings.component.html | 8 +- .../indexing/indexing.settings.component.ts | 94 +- .../indexing/indexing.settings.service.ts | 52 +- .../button/job-button.settings.component.css | 2 +- .../button/job-button.settings.component.html | 6 +- .../button/job-button.settings.component.ts | 60 +- .../settings/jobs/jobs.settings.component.css | 4 +- .../jobs/jobs.settings.component.html | 19 +- .../settings/jobs/jobs.settings.component.ts | 134 +- .../ui/settings/jobs/jobs.settings.service.ts | 31 +- .../job-progress.settings.component.html | 19 +- .../job-progress.settings.component.ts | 74 +- .../settings/map/map.settings.component.html | 13 +- .../ui/settings/map/map.settings.component.ts | 58 +- .../ui/settings/map/map.settings.service.ts | 20 +- .../metafiles/metafile.settings.component.ts | 44 +- .../metafiles/metafile.settings.service.ts | 21 +- .../other/other.settings.component.html | 17 +- .../other/other.settings.component.ts | 73 +- .../settings/other/other.settings.service.ts | 20 +- .../photo/photo.settings.component.html | 2 +- .../photo/photo.settings.component.ts | 82 +- .../settings/photo/photo.settings.service.ts | 31 +- .../preview/preview.settings.component.html | 6 +- .../preview/preview.settings.component.ts | 62 +- .../preview/preview.settings.service.ts | 23 +- .../random-photo.settings.component.html | 6 +- .../random-photo.settings.component.ts | 43 +- .../random-photo.settings.service.ts | 28 +- .../app/ui/settings/scheduled-jobs.service.ts | 87 +- .../search/search.settings.component.html | 11 +- .../search/search.settings.component.ts | 43 +- .../search/search.settings.service.ts | 27 +- .../app/ui/settings/settings.service.ts | 14 +- .../share/share.settings.component.css | 2 +- .../share/share.settings.component.html | 8 +- .../share/share.settings.component.ts | 50 +- .../settings/share/share.settings.service.ts | 33 +- .../thumbnail.settings.component.html | 6 +- .../thumbnail/thumbnail.settings.component.ts | 77 +- .../thumbnail/thumbnail.settings.service.ts | 33 +- .../usermanager.settings.component.html | 8 +- .../usermanager.settings.component.ts | 78 +- .../usermanager.settings.service.ts | 28 +- .../video/video.settings.component.html | 22 +- .../video/video.settings.component.ts | 129 +- .../settings/video/video.settings.service.ts | 32 +- .../ui/sharelogin/share-login.component.html | 3 +- .../ui/sharelogin/share-login.component.ts | 23 +- .../datepicker.component.ts | 7 +- .../timepicker.component.ts | 6 +- src/frontend/main.ts | 12 +- src/frontend/polyfills.ts | 8 +- src/frontend/test.ts | 10 +- .../middlewares/user/AuthenticationMWs.ts | 10 +- .../fileprocessing/PhotoProcessing.spec.ts | 4 +- .../fileprocessing/VideoProcessing.spec.ts | 2 +- .../unit/model/sql/SearchManager.spec.ts | 4 +- tsconfig.json | 12 +- tslint.json | 153 - 346 files changed, 14497 insertions(+), 7238 deletions(-) create mode 100644 .eslintrc.json delete mode 100644 tslint.json diff --git a/.browserslistrc b/.browserslistrc index 427441dc..4f9ac269 100644 --- a/.browserslistrc +++ b/.browserslistrc @@ -14,4 +14,3 @@ last 2 Edge major versions last 2 Safari major versions last 2 iOS major versions Firefox ESR -not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line. diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..00824f15 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,43 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "plugins": [ + "@typescript-eslint" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "plugin:@angular-eslint/recommended" + ], + "overrides": [ + { + "files": [ + "*.component.ts" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "project": "./src/frontend/tsconfig.app.json", + "ecmaVersion": 2020, + "sourceType": "module" + }, + "plugins": [ + "@angular-eslint/template" + ], + "processor": "@angular-eslint/template/extract-inline-html" + }, + { + "files": [ + "*.component.html" + ], + "parser": "@angular-eslint/template-parser", + "parserOptions": { + "project": "./src/frontend/tsconfig.app.json", + "ecmaVersion": 2020, + "sourceType": "module" + }, + "plugins": [ + "@angular-eslint/template" + ] + } + ] +} diff --git a/angular.json b/angular.json index 931993e2..fcf31810 100644 --- a/angular.json +++ b/angular.json @@ -1,5 +1,8 @@ { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", + "cli": { + "defaultCollection": "@angular-eslint/schematics" + }, "version": 1, "newProjectRoot": "projects", "projects": { @@ -7,7 +10,11 @@ "root": "", "sourceRoot": "src/frontend", "projectType": "application", - "schematics": {}, + "schematics": { + "@schematics/angular:application": { + "strict": true + } + }, "prefix": "app", "i18n": { "sourceLocale": { @@ -15,7 +22,7 @@ "baseHref": "" }, "locales": { - "cn": { + "zh": { "baseHref": "", "translation": "src/frontend/translate/messages.cn.xlf" }, @@ -185,49 +192,16 @@ } }, "lint": { - "builder": "@angular-devkit/build-angular:tslint", + "builder": "@angular-eslint/builder:lint", "options": { - "tsConfig": [ - "src/tsconfig.app.json", - "src/tsconfig.spec.json" - ], - "exclude": [] - } - } - } - }, - "pigallery2-e2e": { - "root": "", - "sourceRoot": "", - "projectType": "application", - "architect": { - "e2e": { - "builder": "@angular-devkit/build-angular:protractor", - "options": { - "protractorConfig": "./protractor.conf.js", - "devServerTarget": "pigallery2:serve" - } - }, - "lint": { - "builder": "@angular-devkit/build-angular:tslint", - "options": { - "tsConfig": [ - "test/e2e/tsconfig.e2e.json" - ], - "exclude": [] + "lintFilePatterns": [ + "src/**/*.ts", + "src/**/*.html" + ] } } } } }, - "defaultProject": "pigallery2", - "schematics": { - "@schematics/angular:component": { - "prefix": "app", - "styleext": "css" - }, - "@schematics/angular:directive": { - "prefix": "app" - } - } + "defaultProject": "pigallery2" } diff --git a/benchmark/BMConfig.ts b/benchmark/BMConfig.ts index 57c2940c..5006967e 100644 --- a/benchmark/BMConfig.ts +++ b/benchmark/BMConfig.ts @@ -1,14 +1,10 @@ -/* tslint:disable:no-inferrable-types */ +/* eslint-disable @typescript-eslint/no-inferrable-types */ import * as path from 'path'; -import {ConfigClass, ConfigClassBuilder} from 'typeconfig/node'; -import {ConfigProperty, SubConfigClass} from 'typeconfig/common'; -import {JobTrigger, JobTriggerType} from '../src/common/entities/job/JobScheduleDTO'; -import {ServerVideoConfig} from '../src/common/config/private/PrivateConfig'; - - +import { ConfigClass, ConfigClassBuilder } from 'typeconfig/node'; +import { ConfigProperty, SubConfigClass } from 'typeconfig/common'; @SubConfigClass() -export class BenchmarksConfig { +export class BenchmarksConfig { @ConfigProperty() bmScanDirectory: boolean = true; @ConfigProperty() @@ -39,25 +35,28 @@ export class BenchmarksConfig { rewriteENVConfig: true, enumsAsString: true, saveIfNotExist: true, - exitOnConfig: true + exitOnConfig: true, }, defaults: { - enabled: true - } - } + enabled: true, + }, + }, }) export class PrivateConfigClass { - @ConfigProperty({description: 'Images are loaded from this folder (read permission required)'}) + @ConfigProperty({ + description: + 'Images are loaded from this folder (read permission required)', + }) path: string = '/app/data/images'; - @ConfigProperty({description: 'Describe your system setup'}) + @ConfigProperty({ description: 'Describe your system setup' }) system: string = ''; - @ConfigProperty({description: 'Number of times to run the benchmark'}) + @ConfigProperty({ description: 'Number of times to run the benchmark' }) RUNS: number = 50; - @ConfigProperty({description: 'Enables / disables benchmarks'}) + @ConfigProperty({ description: 'Enables / disables benchmarks' }) Benchmarks: BenchmarksConfig = new BenchmarksConfig(); - - } -export const BMConfig = ConfigClassBuilder.attachInterface(new PrivateConfigClass()); +export const BMConfig = ConfigClassBuilder.attachInterface( + new PrivateConfigClass() +); BMConfig.loadSync(); diff --git a/gulpfile.ts b/gulpfile.ts index 08509f99..2b25b364 100644 --- a/gulpfile.ts +++ b/gulpfile.ts @@ -1,23 +1,24 @@ import * as gulp from 'gulp'; import * as fs from 'fs'; -import {promises as fsp} from 'fs'; +import { promises as fsp } from 'fs'; import * as path from 'path'; import * as util from 'util'; import * as zip from 'gulp-zip'; import * as ts from 'gulp-typescript'; import * as xml2js from 'xml2js'; import * as child_process from 'child_process'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import * as jeditor from 'gulp-json-editor'; -import {XLIFF} from 'xlf-google-translate'; -import {PrivateConfigClass} from './src/common/config/private/Config'; -import {ConfigClassBuilder} from 'typeconfig/src/decorators/builders/ConfigClassBuilder'; +import { XLIFF } from 'xlf-google-translate'; +import { PrivateConfigClass } from './src/common/config/private/Config'; +import { ConfigClassBuilder } from 'typeconfig/src/decorators/builders/ConfigClassBuilder'; const execPr = util.promisify(child_process.exec); const translationFolder = 'translate'; const tsBackendProject = ts.createProject('tsconfig.json'); -declare var process: NodeJS.Process; +declare const process: NodeJS.Process; const getSwitch = (name: string, def: string = null): string => { name = '--' + name; @@ -36,26 +37,35 @@ const getSwitch = (name: string, def: string = null): string => { }; gulp.task('build-backend', (): any => - gulp.src([ - 'src/common/**/*.ts', - 'src/backend/**/*.ts', - 'benchmark/**/*.ts'], {base: '.'}) + gulp + .src(['src/common/**/*.ts', 'src/backend/**/*.ts', 'benchmark/**/*.ts'], { + base: '.', + }) .pipe(tsBackendProject()) - .js - .pipe(gulp.dest('./release'))); + .js.pipe(gulp.dest('./release')) +); - -const createDynamicTranslationFile = async (language: string): Promise => { +const createDynamicTranslationFile = async ( + language: string +): Promise => { // load const folder = './src/frontend/' + translationFolder; - const data: string = await fsp.readFile(path.join(folder, `messages.${language}.xlf`), 'utf-8'); + const data: string = await fsp.readFile( + path.join(folder, `messages.${language}.xlf`), + 'utf-8' + ); const translationXml: XLIFF.Root = await xml2js.parseStringPromise(data); // clean translations, keep only .ts transaltions const hasTsTranslation = (cg: XLIFF.ContextGroup): boolean => - cg.context.findIndex((c: any): boolean => c.$['context-type'] === 'sourcefile' && c._.endsWith('.ts')) !== -1; + cg.context.findIndex( + (c: any): boolean => + c.$['context-type'] === 'sourcefile' && c._.endsWith('.ts') + ) !== -1; const translations = translationXml.xliff.file[0].body[0]['trans-unit']; - const filtered = translations.filter((tr): boolean => tr['context-group'].findIndex(hasTsTranslation) !== -1); + const filtered = translations.filter( + (tr): boolean => tr['context-group'].findIndex(hasTsTranslation) !== -1 + ); filtered.forEach((tr): boolean => delete tr['context-group']); translationXml.xliff.file[0].body[0]['trans-unit'] = filtered; @@ -63,15 +73,19 @@ const createDynamicTranslationFile = async (language: string): Promise => const builder = new xml2js.Builder(); const xml = builder.buildObject(translationXml); await fsp.writeFile(path.join(folder, `ts-only-msg.${language}.xlf`), xml); - }; -const removeDynamicTranslationFile = async (language: string): Promise => { - const translationFile = path.join('./src/frontend/', translationFolder, `ts-only-msg.${language}.xlf`); +const removeDynamicTranslationFile = async ( + language: string +): Promise => { + const translationFile = path.join( + './src/frontend/', + translationFolder, + `ts-only-msg.${language}.xlf` + ); fsp.unlink(translationFile); }; - const setDynTransFileAtAppModule = async (language: string): Promise => { const file = './src/frontend/app/app.module.ts'; let data: string = await fsp.readFile(file, 'utf-8'); @@ -90,11 +104,14 @@ const resetAppModule = async (language: string): Promise => { await fsp.writeFile(file, data); }; - -const createFrontendTask = (type: string, language: string, script: string): void => { +const createFrontendTask = ( + type: string, + language: string, + script: string +): void => { gulp.task(type, async (cb): Promise => { try { - const {stdout, stderr} = await execPr(script); + const { stdout, stderr } = await execPr(script); console.log(stdout); console.error(stderr); } catch (e) { @@ -104,14 +121,13 @@ const createFrontendTask = (type: string, language: string, script: string): voi }); }; - const getLanguages = (): any[] | string[] => { if (!fs.existsSync('./src/frontend/' + translationFolder)) { return []; } const dirCont = fs.readdirSync('./src/frontend/' + translationFolder); const files: string[] = dirCont.filter((elm): any => { - return elm.match(/.*\.[a-zA-Z]+\.(xlf)/ig); + return elm.match(/.*\.[a-zA-Z]+\.(xlf)/gi); }); // get languages to filter @@ -132,76 +148,111 @@ const getLanguages = (): any[] | string[] => { return languages; }; -gulp.task('build-frontend', ((): any => { - const tasks = []; - createFrontendTask('build-frontend-release default', 'all', - 'ng build --prod --no-progress --output-path=./release/dist'); - tasks.push('build-frontend-release default'); - return gulp.series(...tasks); -})()); +gulp.task( + 'build-frontend', + ((): any => { + const tasks = []; + createFrontendTask( + 'build-frontend-release default', + 'all', + 'ng build --prod --no-progress --output-path=./release/dist' + ); + tasks.push('build-frontend-release default'); + return gulp.series(...tasks); + })() +); -gulp.task('copy-static', (): any => gulp.src([ - 'src/backend/model/diagnostics/blank.jpg', - 'README.md', - // 'package-lock.json', should not add, it keeps optional packages optional even with --force-opt-packages. - 'LICENSE'], {base: '.'}) - .pipe(gulp.dest('./release'))); +gulp.task('copy-static', (): any => + gulp + .src( + [ + 'src/backend/model/diagnostics/blank.jpg', + 'README.md', + // 'package-lock.json', should not add, it keeps optional packages optional even with --force-opt-packages. + 'LICENSE', + ], + { base: '.' } + ) + .pipe(gulp.dest('./release')) +); -gulp.task('copy-package', (): any => gulp.src([ - 'package.json'], {base: '.'}) - .pipe(jeditor((json: { - devDependencies: { [key: string]: string }, - scripts: { [key: string]: string }, - dependencies: { [key: string]: string }, - optionalDependencies: { [key: string]: string }, - buildTime: string, - buildCommitHash: string - }): { - devDependencies: { [p: string]: string }; - scripts: { [p: string]: string }; - dependencies: { [p: string]: string }; - optionalDependencies: { [p: string]: string }; - buildTime: string; buildCommitHash: string - } => { - delete json.devDependencies; - json.scripts = {start: 'node ./src/backend/index.js'}; +gulp.task('copy-package', (): any => + gulp + .src(['package.json'], { base: '.' }) + .pipe( + jeditor( + (json: { + devDependencies: { [key: string]: string }; + scripts: { [key: string]: string }; + dependencies: { [key: string]: string }; + optionalDependencies: { [key: string]: string }; + buildTime: string; + buildCommitHash: string; + }): { + devDependencies: { [p: string]: string }; + scripts: { [p: string]: string }; + dependencies: { [p: string]: string }; + optionalDependencies: { [p: string]: string }; + buildTime: string; + buildCommitHash: string; + } => { + delete json.devDependencies; + json.scripts = { start: 'node ./src/backend/index.js' }; - if (getSwitch('skip-opt-packages')) { - const skipPackages = getSwitch('skip-opt-packages').replace(new RegExp(' ', 'g'), ',').split(','); - for (const pkg of skipPackages) { - for (const key of Object.keys(json.optionalDependencies)) { - if (key.indexOf(pkg) !== -1) { - delete json.optionalDependencies[key]; + if (getSwitch('skip-opt-packages')) { + const skipPackages = getSwitch('skip-opt-packages') + .replace(new RegExp(' ', 'g'), ',') + .split(','); + for (const pkg of skipPackages) { + for (const key of Object.keys(json.optionalDependencies)) { + if (key.indexOf(pkg) !== -1) { + delete json.optionalDependencies[key]; + } + } + } } + + if (getSwitch('force-opt-packages')) { + for (const key of Object.keys(json.optionalDependencies)) { + json.dependencies[key] = json.optionalDependencies[key]; + } + delete json.optionalDependencies; + } + json.buildTime = new Date().toISOString(); + + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + json.buildCommitHash = require('child_process') + .execSync('git rev-parse HEAD') + .toString() + .trim(); + // eslint-disable-next-line no-empty + } catch (e) {} + + return json; } - } - } - - if (!!getSwitch('force-opt-packages')) { - for (const key of Object.keys(json.optionalDependencies)) { - json.dependencies[key] = json.optionalDependencies[key]; - } - delete json.optionalDependencies; - } - json.buildTime = (new Date()).toISOString(); - - try { - json.buildCommitHash = require('child_process').execSync('git rev-parse HEAD').toString().trim(); - } catch (e) { - } - - return json; - })) - .pipe(gulp.dest('./release'))); - + ) + ) + .pipe(gulp.dest('./release')) +); gulp.task('zip-release', (): any => - gulp.src(['release/**/*'], {base: './release'}) + gulp + .src(['release/**/*'], { base: './release' }) .pipe(zip('pigallery2.zip')) - .pipe(gulp.dest('.'))); - -gulp.task('create-release', gulp.series('build-frontend', 'build-backend', 'copy-static', 'copy-package', 'zip-release')); + .pipe(gulp.dest('.')) +); +gulp.task( + 'create-release', + gulp.series( + 'build-frontend', + 'build-backend', + 'copy-static', + 'copy-package', + 'zip-release' + ) +); const simpleBuild = (isProd: boolean): any => { const tasks = []; @@ -209,10 +260,14 @@ const simpleBuild = (isProd: boolean): any => { if (isProd) { cmd += ' --prod --no-extract-licenses '; } - if (!process.env.CI) { + if (!process.env['CI']) { createFrontendTask('build-frontend default', 'all', cmd); } else { - createFrontendTask('build-frontend default', 'all', cmd + '--localize=false'); + createFrontendTask( + 'build-frontend default', + 'all', + cmd + '--localize=false' + ); } tasks.push('build-frontend default'); return gulp.series(...tasks); @@ -222,8 +277,10 @@ gulp.task('extract-locale', async (cb): Promise => { console.log('creating source translation file: locale.source.xlf'); try { { - const {stdout, stderr} = await execPr('ng extract-i18n --out-file=locale.source.xlf --format=xlf', - {maxBuffer: 1024 * 1024}); + const { stdout, stderr } = await execPr( + 'ng extract-i18n --out-file=locale.source.xlf --format=xlf', + { maxBuffer: 1024 * 1024 } + ); console.log(stdout); console.error(stderr); } @@ -234,14 +291,22 @@ gulp.task('extract-locale', async (cb): Promise => { } }); -const translate = async (list: any[], cb: (err?: any) => void): Promise => { +const translate = async ( + list: any[], + cb: (err?: any) => void +): Promise => { try { const localsStr = '"[\\"' + list.join('\\",\\"') + '\\"]"'; - const {stdout, stderr} = await execPr('xlf-google-translate ' + - '--source-lang="en" ' + - '--source-file="./locale.source.xlf" ' + - '--destination-filename="messages" ' + - '--destination-folder="./src/frontend/"' + translationFolder + ' --destination-languages=' + localsStr); + const { stdout, stderr } = await execPr( + 'xlf-google-translate ' + + '--source-lang="en" ' + + '--source-file="./locale.source.xlf" ' + + '--destination-filename="messages" ' + + '--destination-folder="./src/frontend/"' + + translationFolder + + ' --destination-languages=' + + localsStr + ); console.log(stdout); console.error(stderr); cb(); @@ -253,15 +318,19 @@ const translate = async (list: any[], cb: (err?: any) => void): Promise => const merge = async (list: any[], cb: (err?: any) => void): Promise => { try { const localsStr = '"[\\"' + list.join('\\",\\"') + '\\"]"'; - const command = 'xlf-google-translate ' + + const command = + 'xlf-google-translate ' + '--method="extend-only" ' + '--source-lang="en" ' + '--source-file="./locale.source.xlf" ' + '--destination-filename="messages" ' + - '--destination-folder="./src/frontend/' + translationFolder + '" ' + - '--destination-languages=' + localsStr; + '--destination-folder="./src/frontend/' + + translationFolder + + '" ' + + '--destination-languages=' + + localsStr; console.log(command); - const {stdout, stderr} = await execPr(command); + const { stdout, stderr } = await execPr(command); console.log(stdout); console.error(stderr); cb(); @@ -278,10 +347,15 @@ gulp.task('merge-translation-only', (cb): void => { merge(getLanguages(), cb).catch(console.error); }); -gulp.task('update-translation', gulp.series('extract-locale', 'update-translation-only')); - -gulp.task('merge-new-translation', gulp.series('extract-locale', 'merge-translation-only')); +gulp.task( + 'update-translation', + gulp.series('extract-locale', 'update-translation-only') +); +gulp.task( + 'merge-new-translation', + gulp.series('extract-locale', 'merge-translation-only') +); gulp.task('add-translation-only', (cb): any => { const languages = getLanguages(); @@ -292,11 +366,16 @@ gulp.task('add-translation-only', (cb): any => { } } if (lng == null) { - console.error('Error: set language with \'--\' e.g: npm run add-translation -- --en'); + console.error( + "Error: set language with '--' e.g: npm run add-translation -- --en" + ); return cb(); } if (languages.indexOf(lng) !== -1) { - console.error('Error: language already exists, can\'t add. These language(s) already exist(s): ' + languages); + console.error( + "Error: language already exists, can't add. These language(s) already exist(s): " + + languages + ); return cb(); } translate([lng], cb); @@ -306,17 +385,23 @@ gulp.task('generate-man', async (cb): Promise => { const defCFG = ConfigClassBuilder.attachInterface(new PrivateConfigClass()); defCFG.Server.sessionSecret = []; let txt = '# Pigallery 2 man page\n'; - txt += 'pigallery2 uses [typeconfig](https://github.com/bpatrik/typeconfig) for configuration\n\n'; + txt += + 'pigallery2 uses [typeconfig](https://github.com/bpatrik/typeconfig) for configuration\n\n'; txt += '`npm start -- --help` prints the following:\n\n'; - txt += '```\n' + ConfigClassBuilder.attachPrivateInterface(defCFG).__printMan() + '```'; + txt += + '```\n' + + ConfigClassBuilder.attachPrivateInterface(defCFG).__printMan() + + '```'; txt += '\n\n ### `config.json` sample:\n'; txt += '```json\n' + JSON.stringify(defCFG, null, 4) + '```'; await fsp.writeFile('MANPAGE.md', txt); cb(); }); -gulp.task('add-translation', gulp.series('extract-locale', 'add-translation-only')); - +gulp.task( + 'add-translation', + gulp.series('extract-locale', 'add-translation-only') +); gulp.task('build-dev', simpleBuild(false)); gulp.task('build-prod', simpleBuild(true)); diff --git a/package-lock.json b/package-lock.json index 0afe76fe..2037a5bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,11 @@ "devDependencies": { "@angular-devkit/build-angular": "13.3.1", "@angular-devkit/build-optimizer": "0.1302.1", + "@angular-eslint/builder": "13.1.0", + "@angular-eslint/eslint-plugin": "13.1.0", + "@angular-eslint/eslint-plugin-template": "13.1.0", + "@angular-eslint/schematics": "13.1.0", + "@angular-eslint/template-parser": "13.1.0", "@angular/animations": "13.3.1", "@angular/cli": "13.3.1", "@angular/common": "13.3.1", @@ -73,6 +78,8 @@ "@types/node-geocoder": "3.24.4", "@types/sharp": "0.30.0", "@types/xml2js": "0.4.9", + "@typescript-eslint/eslint-plugin": "5.11.0", + "@typescript-eslint/parser": "5.11.0", "bootstrap": "4.6.1", "chai": "4.3.6", "chai-http": "4.3.0", @@ -81,6 +88,7 @@ "coveralls": "3.1.1", "deep-equal-in-any-order": "1.1.15", "ejs-loader": "0.5.0", + "eslint": "7.32.0", "gulp": "4.0.2", "gulp-json-editor": "2.5.6", "gulp-typescript": "5.0.1", @@ -108,7 +116,6 @@ "rxjs": "7.5.5", "ts-helpers": "1.1.2", "ts-node": "10.7.0", - "tslint": "6.1.3", "webpack-bundle-analyzer": "4.5.0", "xlf-google-translate": "1.0.0-beta.22", "xml2js": "0.4.23", @@ -467,6 +474,130 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/@angular-eslint/builder": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-13.1.0.tgz", + "integrity": "sha512-RdyeetctnipVdCBrU/mipJ2XKiLC1yGmK1Sfbbgwu0s49CAdOArY/b+b8OU3yyy4EO1EGKQMlzs6F3wTYgiZCA==", + "dev": true, + "dependencies": { + "@nrwl/devkit": "13.1.3" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/bundled-angular-compiler": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-13.1.0.tgz", + "integrity": "sha512-0VSAZ3zrHkKdbvogQ4GLSf+lKojtPL3LXLlvXU9xNgNsqw68+gSNwaWd595tXoQCmpwWpTerKUbyIsGnPA7EYA==", + "dev": true + }, + "node_modules/@angular-eslint/eslint-plugin": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-13.1.0.tgz", + "integrity": "sha512-WiggBWPhhpSjcYVHJiawCduCsydM/RPudUE8zxv0Nmj/APuzNgvUr6E//uYTrhNv9EIJoZutRovw7R4Y/yXj4Q==", + "dev": true, + "dependencies": { + "@angular-eslint/utils": "13.1.0", + "@typescript-eslint/experimental-utils": "5.11.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-13.1.0.tgz", + "integrity": "sha512-ceZAMjufE58aduOW/UwjrbCRWocYC0zOEJ2jUkPt6jlL8yzc+SC6UitO0VmMgUsCizHueav5/3gKy05xqwk/CA==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "13.1.0", + "@typescript-eslint/experimental-utils": "5.11.0", + "aria-query": "^4.2.2", + "axobject-query": "^2.2.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template/node_modules/aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "dependencies": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/@angular-eslint/eslint-plugin-template/node_modules/axobject-query": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", + "dev": true + }, + "node_modules/@angular-eslint/schematics": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-13.1.0.tgz", + "integrity": "sha512-/gVtkRP09cGhnUF3tr0phwNA5/ml94V3cqO8X4Z4QmyAaIwxuOJ0mJvWrVN7aurURxh9WoeWD/HXOvtC5igtpQ==", + "dev": true, + "dependencies": { + "@angular-eslint/eslint-plugin": "13.1.0", + "@angular-eslint/eslint-plugin-template": "13.1.0", + "ignore": "5.2.0", + "strip-json-comments": "3.1.1", + "tmp": "0.2.1" + }, + "peerDependencies": { + "@angular/cli": ">= 13.0.0 < 14.0.0" + } + }, + "node_modules/@angular-eslint/schematics/node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/@angular-eslint/template-parser": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-13.1.0.tgz", + "integrity": "sha512-gKV+kms+kYm1HdToH3j4HL1RrjvMHscVkhwjoV1WbJColnfDivVAZ6/5/J92/A/8r7vJptQkftsbiaGKDyg47w==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "13.1.0", + "eslint-scope": "^5.1.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0", + "typescript": "*" + } + }, + "node_modules/@angular-eslint/utils": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-13.1.0.tgz", + "integrity": "sha512-iLmYMXNk+1sFMIlYXN8/Z5UcNAOno38v10lzo4bMjCpzXKkMa0O2b+4qi+469iUJAU6RAZ5weUL+S2Wtlr0ooA==", + "dev": true, + "dependencies": { + "@angular-eslint/bundled-angular-compiler": "13.1.0", + "@typescript-eslint/experimental-utils": "5.11.0" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0", + "typescript": "*" + } + }, "node_modules/@angular/animations": { "version": "13.3.1", "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-13.3.1.tgz", @@ -2486,6 +2617,19 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/runtime-corejs3": { + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.17.8.tgz", + "integrity": "sha512-ZbYSUvoSF6dXZmMl/CYTMOvzIFnbGfv4W3SEHYgMvNsFTeLaF2gkGAF4K2ddmtSK4Emej+0aYcnSC6N5dPCXUQ==", + "dev": true, + "dependencies": { + "core-js-pure": "^3.20.2", + "regenerator-runtime": "^0.13.4" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", @@ -2626,12 +2770,110 @@ "node": ">=10.0.0" } }, + "node_modules/@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.13.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", + "integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "dev": true }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -3294,6 +3536,437 @@ "read-package-json-fast": "^2.0.1" } }, + "node_modules/@nrwl/cli": { + "version": "13.9.6", + "resolved": "https://registry.npmjs.org/@nrwl/cli/-/cli-13.9.6.tgz", + "integrity": "sha512-TWsk0gqzGUzpKr6yd6YtnKAMYHqazhejNR0y/bIVqi3xgStQQ+O7SdHHyOe0ocZSyczg+61I0FHJNyTq7+D6PQ==", + "dev": true, + "dependencies": { + "nx": "13.9.6" + } + }, + "node_modules/@nrwl/cli/node_modules/@nrwl/tao": { + "version": "13.9.6", + "resolved": "https://registry.npmjs.org/@nrwl/tao/-/tao-13.9.6.tgz", + "integrity": "sha512-TcOV9fCwRA+JUX3WK1OTUM1WTa0DZnyz970Q/AJogpqbbuzjUzCzCVHbJVbt3rVNqjoVYeDoUroQ9SDHMez6tQ==", + "dev": true, + "dependencies": { + "nx": "13.9.6" + }, + "bin": { + "tao": "index.js" + } + }, + "node_modules/@nrwl/cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@nrwl/cli/node_modules/chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@nrwl/cli/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@nrwl/cli/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@nrwl/cli/node_modules/fast-glob": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", + "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nrwl/cli/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nrwl/cli/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nrwl/cli/node_modules/nx": { + "version": "13.9.6", + "resolved": "https://registry.npmjs.org/nx/-/nx-13.9.6.tgz", + "integrity": "sha512-mb1tPxpc8LnnRlY2qXPR07X5kMrnUtfwOGJGP12xxauSXIgrqThIt0AGjZXU5UepFeo5Ejb7HpiggALKjqGRgg==", + "dev": true, + "dependencies": { + "@nrwl/cli": "13.9.6", + "@nrwl/tao": "13.9.6", + "@swc-node/register": "^1.4.2", + "@swc/core": "^1.2.146", + "chalk": "4.1.0", + "enquirer": "~2.3.6", + "fast-glob": "3.2.7", + "fs-extra": "^9.1.0", + "ignore": "^5.0.4", + "jsonc-parser": "3.0.0", + "rxjs": "^6.5.4", + "rxjs-for-await": "0.0.2", + "semver": "7.3.4", + "tmp": "~0.2.1", + "tsconfig-paths": "^3.9.0", + "tslib": "^2.3.0", + "v8-compile-cache": "2.3.0", + "yargs-parser": "20.0.0" + }, + "bin": { + "nx": "bin/nx.js" + } + }, + "node_modules/@nrwl/cli/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@nrwl/cli/node_modules/rxjs-for-await": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/rxjs-for-await/-/rxjs-for-await-0.0.2.tgz", + "integrity": "sha512-IJ8R/ZCFMHOcDIqoABs82jal00VrZx8Xkgfe7TOKoaRPAW5nH/VFlG23bXpeGdrmtqI9UobFPgUKgCuFc7Lncw==", + "dev": true, + "peerDependencies": { + "rxjs": "^6.0.0" + } + }, + "node_modules/@nrwl/cli/node_modules/rxjs/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@nrwl/cli/node_modules/semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nrwl/cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nrwl/cli/node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/@nrwl/cli/node_modules/yargs-parser": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.0.0.tgz", + "integrity": "sha512-8eblPHTL7ZWRkyjIZJjnGf+TijiKJSwA24svzLRVvtgoi/RZiKa9fFQTrlx0OKLnyHSdt/enrdadji6WFfESVA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nrwl/devkit": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-13.1.3.tgz", + "integrity": "sha512-TAAsZJvVc/obeH0rZKY6miVhyM2GHGb8qIWp9MAIdLlXf4VDcNC7rxwb5OrGVSwuTTjqGYBGPUx0yEogOOJthA==", + "dev": true, + "dependencies": { + "@nrwl/tao": "13.1.3", + "ejs": "^3.1.5", + "ignore": "^5.0.4", + "rxjs": "^6.5.4", + "semver": "7.3.4", + "tslib": "^2.0.0" + } + }, + "node_modules/@nrwl/devkit/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@nrwl/devkit/node_modules/rxjs/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@nrwl/devkit/node_modules/semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nrwl/tao": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/@nrwl/tao/-/tao-13.1.3.tgz", + "integrity": "sha512-/IwJgSgCBD1SaF+n8RuXX2OxDAh8ut/+P8pMswjm8063ac30UlAHjQ4XTYyskLH8uoUmNi2hNaGgHUrkwt7tQA==", + "dev": true, + "dependencies": { + "chalk": "4.1.0", + "enquirer": "~2.3.6", + "fs-extra": "^9.1.0", + "jsonc-parser": "3.0.0", + "nx": "13.1.3", + "rxjs": "^6.5.4", + "rxjs-for-await": "0.0.2", + "semver": "7.3.4", + "tmp": "~0.2.1", + "tslib": "^2.0.0", + "yargs-parser": "20.0.0" + }, + "bin": { + "tao": "index.js" + } + }, + "node_modules/@nrwl/tao/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@nrwl/tao/node_modules/chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@nrwl/tao/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@nrwl/tao/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@nrwl/tao/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nrwl/tao/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nrwl/tao/node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/@nrwl/tao/node_modules/rxjs-for-await": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/rxjs-for-await/-/rxjs-for-await-0.0.2.tgz", + "integrity": "sha512-IJ8R/ZCFMHOcDIqoABs82jal00VrZx8Xkgfe7TOKoaRPAW5nH/VFlG23bXpeGdrmtqI9UobFPgUKgCuFc7Lncw==", + "dev": true, + "peerDependencies": { + "rxjs": "^6.0.0" + } + }, + "node_modules/@nrwl/tao/node_modules/rxjs/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@nrwl/tao/node_modules/semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@nrwl/tao/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@nrwl/tao/node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/@nrwl/tao/node_modules/yargs-parser": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.0.0.tgz", + "integrity": "sha512-8eblPHTL7ZWRkyjIZJjnGf+TijiKJSwA24svzLRVvtgoi/RZiKa9fFQTrlx0OKLnyHSdt/enrdadji6WFfESVA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.21", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", @@ -3330,6 +4003,359 @@ "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.3.tgz", "integrity": "sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg==" }, + "node_modules/@swc-node/core": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@swc-node/core/-/core-1.8.2.tgz", + "integrity": "sha512-IoJ7tGHQ6JOMSmFe4VhP64uLmFKMNasS0QEgUrLFQ0h/dTvpQMynnoGBEJoPL6LfsebZ/q4uKqbpWrth6/yrAA==", + "dev": true, + "dependencies": { + "@swc/core": "^1.2.119" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@swc-node/register": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@swc-node/register/-/register-1.4.2.tgz", + "integrity": "sha512-wLZz0J7BTO//1Eq7e4eBQjKF380Hr2eVemz849msQSKcVM1D7UJUt/dP2TinEVGx++/BXJ/0q37i6n9Iw0EM0w==", + "dev": true, + "dependencies": { + "@swc-node/core": "^1.8.2", + "@swc-node/sourcemap-support": "^0.1.11", + "chalk": "4", + "debug": "^4.3.3", + "pirates": "^4.0.4", + "tslib": "^2.3.1", + "typescript": "^4.5.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@swc-node/register/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@swc-node/register/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@swc-node/register/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/@swc-node/register/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/@swc-node/register/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@swc-node/register/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@swc-node/sourcemap-support": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@swc-node/sourcemap-support/-/sourcemap-support-0.1.11.tgz", + "integrity": "sha512-b+Mn3oQl+7nUSt7hPzIbY9B30YhcFo1PT4kd9P4QmD6raycmIealOAhAdZID/JevphzsOXHQB4OqJm7Yi5tMcA==", + "dev": true, + "dependencies": { + "source-map-support": "^0.5.21" + } + }, + "node_modules/@swc/core": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.2.162.tgz", + "integrity": "sha512-MFBmoV2qgGvi5bPX1tH3NLtWV4exa5jTCkC/30mdP5PTwEsxKJ5u+m1fuYOlgzDiBlytx8AihVZy2TmXhWZByw==", + "dev": true, + "bin": { + "swcx": "run_swcx.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-android-arm-eabi": "1.2.162", + "@swc/core-android-arm64": "1.2.162", + "@swc/core-darwin-arm64": "1.2.162", + "@swc/core-darwin-x64": "1.2.162", + "@swc/core-freebsd-x64": "1.2.162", + "@swc/core-linux-arm-gnueabihf": "1.2.162", + "@swc/core-linux-arm64-gnu": "1.2.162", + "@swc/core-linux-arm64-musl": "1.2.162", + "@swc/core-linux-x64-gnu": "1.2.162", + "@swc/core-linux-x64-musl": "1.2.162", + "@swc/core-win32-arm64-msvc": "1.2.162", + "@swc/core-win32-ia32-msvc": "1.2.162", + "@swc/core-win32-x64-msvc": "1.2.162" + } + }, + "node_modules/@swc/core-android-arm-eabi": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-android-arm-eabi/-/core-android-arm-eabi-1.2.162.tgz", + "integrity": "sha512-mQSuLspB1qBAYXyDP0Da60tPumhwD0CIm7tMjAFiOplEJN+9YKBlZ3EV9Xc1wF5bdWzJpmzmqEdN9FEfOQqlwQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-android-arm64": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-android-arm64/-/core-android-arm64-1.2.162.tgz", + "integrity": "sha512-9TuuTrsrxbw1W1xUfcmRuEIKImJC725S/4McSFpoylYRIoHzD1DPpgP4fquU0/fzq7rldVD1tu4tg3xvEL8auw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.2.162.tgz", + "integrity": "sha512-gWJjD7NqKVxGFSJ4BeTXfBpRRRkxaQcWmmkwoXDQ1tmLRUX6K3V8MXp41mTdg7jJWDyKq4VTN6D8zLQcCUEhmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.2.162.tgz", + "integrity": "sha512-+3foKCmxiMuPp1UCIPUg3N8CuzFRDPoPEQagz3TKT8W7Bkv9SXeIL8LPuwfH970rIcx1Ie/Q2UWXJwbckVmMHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-freebsd-x64": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-freebsd-x64/-/core-freebsd-x64-1.2.162.tgz", + "integrity": "sha512-YdfQgALPwJ6ZCvfqLlytCvZG/r/ZgBlOa0gaZvMGl6WMpnWgoVPA5OYBA5qzstg/OEWjMu6fldi+lElsvq8v2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.2.162.tgz", + "integrity": "sha512-4elULEP2JWvSpEEI7JmhoI25cRQ2/ffBtf3+4vLlcAgJCdCrkYvHJO2fbWlN1fpydj34QabMsOROYS4ff4p0Og==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.2.162.tgz", + "integrity": "sha512-ZgR1J8H4qI7EuADgHEeDBtiiF8yt6vrznVtaBvEInDPdV9W10QNKsTqhuFkTfOqaHAO2u1+MkZRuvALGahdDaQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.2.162.tgz", + "integrity": "sha512-1Egev+v8wlr7zPaS715sG7flzbGE0OLtcCR7p7oUqD/NbKwlA6czMch5JwNWvdRMjLThTYEeJ/ID+/xG8BqXUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.2.162.tgz", + "integrity": "sha512-LFWV+8h6S3KmzVgHXRYpGYsaytGt+Vrbm8554ugUdzk465JnHyKzw3e6VRcJTxAGgXa+o1qUEkeBg7Wc/WWkmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.2.162.tgz", + "integrity": "sha512-q5insucuYBVCjpDp8/EG3dbt2PFwGAo2vFzofr/lOlOo9p90jCzFRL0+eXg4Ar1YG6BL+T9o5LhFRggY+YHIBg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.2.162.tgz", + "integrity": "sha512-xzksaPOqB3a8gxLoE0ZMi5w2NX9zzYDylmM3qbCVqft6IZid2XFG2lPFIwxJV1xfW68xMgAe0IECnjp/nQsS8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.2.162.tgz", + "integrity": "sha512-hUvS7UaSW+h16SSH7GwH571L2GnqWHPsiSKIDUvv1b/lca7dLcCY8RzsKafB/GLU+5EBQIN3nab3nH0vOWRkvw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.2.162.tgz", + "integrity": "sha512-Eb0SehVYWO5TpYeaPAD3T3iIPpgJa1q/rmvgMDvL0hi4UnOJlvj43kC4Dhuor6opLd6fJkCS7gBq9SxtGtb8bQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, "node_modules/@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -3639,6 +4665,12 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, "node_modules/@types/jsonwebtoken": { "version": "8.5.8", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.8.tgz", @@ -3832,6 +4864,310 @@ "@types/node": "*" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.11.0.tgz", + "integrity": "sha512-HJh33bgzXe6jGRocOj4FmefD7hRY4itgjzOrSs3JPrTNXsX7j5+nQPciAUj/1nZtwo2kAc3C75jZO+T23gzSGw==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.11.0", + "@typescript-eslint/type-utils": "5.11.0", + "@typescript-eslint/utils": "5.11.0", + "debug": "^4.3.2", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.1.8", + "regexpp": "^3.2.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/@typescript-eslint/experimental-utils": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.11.0.tgz", + "integrity": "sha512-EPvC/bU2n1LKtzKWP1AjGWkp7r8tJ8giVlZHIODo6q7SAd6J+/9vjtEKHK2G/Qp+D2IGPsQge+oadDR3CZcFtQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "5.11.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.11.0.tgz", + "integrity": "sha512-x0DCjetHZYBRovJdr3U0zG9OOdNXUaFLJ82ehr1AlkArljJuwEsgnud+Q7umlGDFLFrs8tU8ybQDFocp/eX8mQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.11.0", + "@typescript-eslint/types": "5.11.0", + "@typescript-eslint/typescript-estree": "5.11.0", + "debug": "^4.3.2" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.11.0.tgz", + "integrity": "sha512-z+K4LlahDFVMww20t/0zcA7gq/NgOawaLuxgqGRVKS0PiZlCTIUtX0EJbC0BK1JtR4CelmkPK67zuCgpdlF4EA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.11.0", + "@typescript-eslint/visitor-keys": "5.11.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.11.0.tgz", + "integrity": "sha512-wDqdsYO6ofLaD4DsGZ0jGwxp4HrzD2YKulpEZXmgN3xo4BHJwf7kq49JTRpV0Gx6bxkSUmc9s0EIK1xPbFFpIA==", + "dev": true, + "dependencies": { + "@typescript-eslint/utils": "5.11.0", + "debug": "^4.3.2", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@typescript-eslint/type-utils/node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.11.0.tgz", + "integrity": "sha512-cxgBFGSRCoBEhvSVLkKw39+kMzUKHlJGVwwMbPcTZX3qEhuXhrjwaZXWMxVfxDgyMm+b5Q5b29Llo2yow8Y7xQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.11.0.tgz", + "integrity": "sha512-yVH9hKIv3ZN3lw8m/Jy5I4oXO4ZBMqijcXCdA4mY8ull6TPTAoQnKKrcZ0HDXg7Bsl0Unwwx7jcXMuNZc0m4lg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.11.0", + "@typescript-eslint/visitor-keys": "5.11.0", + "debug": "^4.3.2", + "globby": "^11.0.4", + "is-glob": "^4.0.3", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.11.0.tgz", + "integrity": "sha512-g2I480tFE1iYRDyMhxPAtLQ9HAn0jjBtipgTCZmd9I9s11OV8CTsG+YfFciuNDcHqm4csbAgC2aVZCHzLxMSUw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.11.0", + "@typescript-eslint/types": "5.11.0", + "@typescript-eslint/typescript-estree": "5.11.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.11.0.tgz", + "integrity": "sha512-E8w/vJReMGuloGxJDkpPlGwhxocxOpSVgSvjiLO5IxZPmxZF30weOeJYyPSEACwM+X4NziYS9q+WkN/2DHYQwA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.11.0", + "eslint-visitor-keys": "^3.0.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@ungap/promise-all-settled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", @@ -4055,6 +5391,15 @@ "acorn": "^8" } }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/acorn-walk": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", @@ -4631,6 +5976,15 @@ "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", "dev": true }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/async": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", @@ -4675,6 +6029,15 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", @@ -5265,6 +6628,7 @@ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", "dev": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -6495,6 +7859,17 @@ "semver": "bin/semver.js" } }, + "node_modules/core-js-pure": { + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.1.tgz", + "integrity": "sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ==", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -7041,6 +8416,12 @@ "node": ">=4.0.0" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "node_modules/deepmerge": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", @@ -7327,6 +8708,18 @@ "buffer-indexof": "^1.0.0" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dom-serialize": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", @@ -7673,6 +9066,18 @@ "node": ">=10.13.0" } }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/ent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", @@ -8158,6 +9563,63 @@ "node": ">=0.8.0" } }, + "node_modules/eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -8171,6 +9633,259 @@ "node": ">=8.0.0" } }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.10.4" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^1.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/eslint/node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.13.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", + "integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "dependencies": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/espree/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", @@ -8184,6 +9899,27 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -8783,6 +10519,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/file-type": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-9.0.0.tgz", @@ -9076,6 +10824,19 @@ "flat": "cli.js" } }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/flatted": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", @@ -9384,6 +11145,12 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "node_modules/gauge": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", @@ -12433,6 +14200,19 @@ "node": ">=0.10.0" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/license-webpack-plugin": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", @@ -12635,6 +14415,18 @@ "integrity": "sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw=", "dev": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, "node_modules/lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", @@ -13755,6 +15547,12 @@ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, "node_modules/needle": { "version": "2.9.1", "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz", @@ -14416,6 +16214,18 @@ "node": ">=0.10.0" } }, + "node_modules/nx": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/nx/-/nx-13.1.3.tgz", + "integrity": "sha512-clM0NQhQKYkqcNz2E3uYRMLwhp2L/9dBhJhQi9XBX4IAyA2gWAomhRIlLm5Xxg3g4h1xwSpP3eJ5t89VikY8Pw==", + "dev": true, + "dependencies": { + "@nrwl/cli": "*" + }, + "bin": { + "nx": "bin/nx.js" + } + }, "node_modules/nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -14908,6 +16718,29 @@ "opener": "bin/opener-bin.js" } }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/optionator/node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, "node_modules/ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -15463,6 +17296,15 @@ "node": ">=0.10.0" } }, + "node_modules/pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/piscina": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/piscina/-/piscina-3.2.0.tgz", @@ -16260,6 +18102,15 @@ "node": ">=0.10.0" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -16330,7 +18181,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "optional": true, + "devOptional": true, "engines": { "node": ">=0.4.0" } @@ -16784,6 +18635,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, "node_modules/regexpu-core": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", @@ -17839,6 +19702,56 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -18777,6 +20690,22 @@ "node": ">=0.10" } }, + "node_modules/table": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", + "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -19318,6 +21247,39 @@ "node": ">=0.3.1" } }, + "node_modules/tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", @@ -19329,6 +21291,7 @@ "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", "dev": true, + "peer": true, "dependencies": { "@babel/code-frame": "^7.0.0", "builtin-modules": "^1.1.1", @@ -19359,6 +21322,7 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "dev": true, + "peer": true, "engines": { "node": ">=0.3.1" } @@ -19368,6 +21332,7 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, + "peer": true, "dependencies": { "minimist": "^1.2.6" }, @@ -19380,6 +21345,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true, + "peer": true, "bin": { "semver": "bin/semver" } @@ -19388,7 +21354,8 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/tsscmp": { "version": "1.0.6", @@ -19403,6 +21370,7 @@ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", "dev": true, + "peer": true, "dependencies": { "tslib": "^1.8.1" }, @@ -19414,7 +21382,8 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/tunnel-agent": { "version": "0.6.0", @@ -19439,6 +21408,18 @@ "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", "dev": true }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -19717,7 +21698,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", "devOptional": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -20016,6 +21996,12 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", @@ -20711,6 +22697,15 @@ "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", "dev": true }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/workerpool": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", @@ -21272,6 +23267,105 @@ } } }, + "@angular-eslint/builder": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/builder/-/builder-13.1.0.tgz", + "integrity": "sha512-RdyeetctnipVdCBrU/mipJ2XKiLC1yGmK1Sfbbgwu0s49CAdOArY/b+b8OU3yyy4EO1EGKQMlzs6F3wTYgiZCA==", + "dev": true, + "requires": { + "@nrwl/devkit": "13.1.3" + } + }, + "@angular-eslint/bundled-angular-compiler": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-13.1.0.tgz", + "integrity": "sha512-0VSAZ3zrHkKdbvogQ4GLSf+lKojtPL3LXLlvXU9xNgNsqw68+gSNwaWd595tXoQCmpwWpTerKUbyIsGnPA7EYA==", + "dev": true + }, + "@angular-eslint/eslint-plugin": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin/-/eslint-plugin-13.1.0.tgz", + "integrity": "sha512-WiggBWPhhpSjcYVHJiawCduCsydM/RPudUE8zxv0Nmj/APuzNgvUr6E//uYTrhNv9EIJoZutRovw7R4Y/yXj4Q==", + "dev": true, + "requires": { + "@angular-eslint/utils": "13.1.0", + "@typescript-eslint/experimental-utils": "5.11.0" + } + }, + "@angular-eslint/eslint-plugin-template": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/eslint-plugin-template/-/eslint-plugin-template-13.1.0.tgz", + "integrity": "sha512-ceZAMjufE58aduOW/UwjrbCRWocYC0zOEJ2jUkPt6jlL8yzc+SC6UitO0VmMgUsCizHueav5/3gKy05xqwk/CA==", + "dev": true, + "requires": { + "@angular-eslint/bundled-angular-compiler": "13.1.0", + "@typescript-eslint/experimental-utils": "5.11.0", + "aria-query": "^4.2.2", + "axobject-query": "^2.2.0" + }, + "dependencies": { + "aria-query": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", + "dev": true, + "requires": { + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" + } + }, + "axobject-query": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", + "dev": true + } + } + }, + "@angular-eslint/schematics": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/schematics/-/schematics-13.1.0.tgz", + "integrity": "sha512-/gVtkRP09cGhnUF3tr0phwNA5/ml94V3cqO8X4Z4QmyAaIwxuOJ0mJvWrVN7aurURxh9WoeWD/HXOvtC5igtpQ==", + "dev": true, + "requires": { + "@angular-eslint/eslint-plugin": "13.1.0", + "@angular-eslint/eslint-plugin-template": "13.1.0", + "ignore": "5.2.0", + "strip-json-comments": "3.1.1", + "tmp": "0.2.1" + }, + "dependencies": { + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + } + } + }, + "@angular-eslint/template-parser": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-13.1.0.tgz", + "integrity": "sha512-gKV+kms+kYm1HdToH3j4HL1RrjvMHscVkhwjoV1WbJColnfDivVAZ6/5/J92/A/8r7vJptQkftsbiaGKDyg47w==", + "dev": true, + "requires": { + "@angular-eslint/bundled-angular-compiler": "13.1.0", + "eslint-scope": "^5.1.0" + } + }, + "@angular-eslint/utils": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@angular-eslint/utils/-/utils-13.1.0.tgz", + "integrity": "sha512-iLmYMXNk+1sFMIlYXN8/Z5UcNAOno38v10lzo4bMjCpzXKkMa0O2b+4qi+469iUJAU6RAZ5weUL+S2Wtlr0ooA==", + "dev": true, + "requires": { + "@angular-eslint/bundled-angular-compiler": "13.1.0", + "@typescript-eslint/experimental-utils": "5.11.0" + } + }, "@angular/animations": { "version": "13.3.1", "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-13.3.1.tgz", @@ -22664,6 +24758,16 @@ "regenerator-runtime": "^0.13.4" } }, + "@babel/runtime-corejs3": { + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.17.8.tgz", + "integrity": "sha512-ZbYSUvoSF6dXZmMl/CYTMOvzIFnbGfv4W3SEHYgMvNsFTeLaF2gkGAF4K2ddmtSK4Emej+0aYcnSC6N5dPCXUQ==", + "dev": true, + "requires": { + "core-js-pure": "^3.20.2", + "regenerator-runtime": "^0.13.4" + } + }, "@babel/template": { "version": "7.16.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", @@ -22770,12 +24874,87 @@ "integrity": "sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA==", "dev": true }, + "@eslint/eslintrc": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", + "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.1.1", + "espree": "^7.3.0", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "globals": { + "version": "13.13.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", + "integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, "@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", "dev": true }, + "@humanwhocodes/config-array": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", + "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.0", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -23290,6 +25469,346 @@ "read-package-json-fast": "^2.0.1" } }, + "@nrwl/cli": { + "version": "13.9.6", + "resolved": "https://registry.npmjs.org/@nrwl/cli/-/cli-13.9.6.tgz", + "integrity": "sha512-TWsk0gqzGUzpKr6yd6YtnKAMYHqazhejNR0y/bIVqi3xgStQQ+O7SdHHyOe0ocZSyczg+61I0FHJNyTq7+D6PQ==", + "dev": true, + "requires": { + "nx": "13.9.6" + }, + "dependencies": { + "@nrwl/tao": { + "version": "13.9.6", + "resolved": "https://registry.npmjs.org/@nrwl/tao/-/tao-13.9.6.tgz", + "integrity": "sha512-TcOV9fCwRA+JUX3WK1OTUM1WTa0DZnyz970Q/AJogpqbbuzjUzCzCVHbJVbt3rVNqjoVYeDoUroQ9SDHMez6tQ==", + "dev": true, + "requires": { + "nx": "13.9.6" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "fast-glob": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.7.tgz", + "integrity": "sha512-rYGMRwip6lUMvYD3BTScMwT1HtAs2d71SMv66Vrxs0IekGZEjhM0pcMfjQPnknBt2zeCwQMEupiN02ZP4DiT1Q==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "nx": { + "version": "13.9.6", + "resolved": "https://registry.npmjs.org/nx/-/nx-13.9.6.tgz", + "integrity": "sha512-mb1tPxpc8LnnRlY2qXPR07X5kMrnUtfwOGJGP12xxauSXIgrqThIt0AGjZXU5UepFeo5Ejb7HpiggALKjqGRgg==", + "dev": true, + "requires": { + "@nrwl/cli": "13.9.6", + "@nrwl/tao": "13.9.6", + "@swc-node/register": "^1.4.2", + "@swc/core": "^1.2.146", + "chalk": "4.1.0", + "enquirer": "~2.3.6", + "fast-glob": "3.2.7", + "fs-extra": "^9.1.0", + "ignore": "^5.0.4", + "jsonc-parser": "3.0.0", + "rxjs": "^6.5.4", + "rxjs-for-await": "0.0.2", + "semver": "7.3.4", + "tmp": "~0.2.1", + "tsconfig-paths": "^3.9.0", + "tslib": "^2.3.0", + "v8-compile-cache": "2.3.0", + "yargs-parser": "20.0.0" + } + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "rxjs-for-await": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/rxjs-for-await/-/rxjs-for-await-0.0.2.tgz", + "integrity": "sha512-IJ8R/ZCFMHOcDIqoABs82jal00VrZx8Xkgfe7TOKoaRPAW5nH/VFlG23bXpeGdrmtqI9UobFPgUKgCuFc7Lncw==", + "dev": true, + "requires": {} + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + }, + "yargs-parser": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.0.0.tgz", + "integrity": "sha512-8eblPHTL7ZWRkyjIZJjnGf+TijiKJSwA24svzLRVvtgoi/RZiKa9fFQTrlx0OKLnyHSdt/enrdadji6WFfESVA==", + "dev": true + } + } + }, + "@nrwl/devkit": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/@nrwl/devkit/-/devkit-13.1.3.tgz", + "integrity": "sha512-TAAsZJvVc/obeH0rZKY6miVhyM2GHGb8qIWp9MAIdLlXf4VDcNC7rxwb5OrGVSwuTTjqGYBGPUx0yEogOOJthA==", + "dev": true, + "requires": { + "@nrwl/tao": "13.1.3", + "ejs": "^3.1.5", + "ignore": "^5.0.4", + "rxjs": "^6.5.4", + "semver": "7.3.4", + "tslib": "^2.0.0" + }, + "dependencies": { + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@nrwl/tao": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/@nrwl/tao/-/tao-13.1.3.tgz", + "integrity": "sha512-/IwJgSgCBD1SaF+n8RuXX2OxDAh8ut/+P8pMswjm8063ac30UlAHjQ4XTYyskLH8uoUmNi2hNaGgHUrkwt7tQA==", + "dev": true, + "requires": { + "chalk": "4.1.0", + "enquirer": "~2.3.6", + "fs-extra": "^9.1.0", + "jsonc-parser": "3.0.0", + "nx": "13.1.3", + "rxjs": "^6.5.4", + "rxjs-for-await": "0.0.2", + "semver": "7.3.4", + "tmp": "~0.2.1", + "tslib": "^2.0.0", + "yargs-parser": "20.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, + "rxjs-for-await": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/rxjs-for-await/-/rxjs-for-await-0.0.2.tgz", + "integrity": "sha512-IJ8R/ZCFMHOcDIqoABs82jal00VrZx8Xkgfe7TOKoaRPAW5nH/VFlG23bXpeGdrmtqI9UobFPgUKgCuFc7Lncw==", + "dev": true, + "requires": {} + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "requires": { + "rimraf": "^3.0.0" + } + }, + "yargs-parser": { + "version": "20.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.0.0.tgz", + "integrity": "sha512-8eblPHTL7ZWRkyjIZJjnGf+TijiKJSwA24svzLRVvtgoi/RZiKa9fFQTrlx0OKLnyHSdt/enrdadji6WFfESVA==", + "dev": true + } + } + }, "@polka/url": { "version": "1.0.0-next.21", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", @@ -23318,6 +25837,202 @@ "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.3.tgz", "integrity": "sha512-O3uyB/JbkAEMZaP3YqyHH7TMnex7tWyCbCI4EfJdOCoN6HIhqdJBWTM6aCCiWQ/5f5wxjgU735QAIpJbjDvmzg==" }, + "@swc-node/core": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@swc-node/core/-/core-1.8.2.tgz", + "integrity": "sha512-IoJ7tGHQ6JOMSmFe4VhP64uLmFKMNasS0QEgUrLFQ0h/dTvpQMynnoGBEJoPL6LfsebZ/q4uKqbpWrth6/yrAA==", + "dev": true, + "requires": { + "@swc/core": "^1.2.119" + } + }, + "@swc-node/register": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@swc-node/register/-/register-1.4.2.tgz", + "integrity": "sha512-wLZz0J7BTO//1Eq7e4eBQjKF380Hr2eVemz849msQSKcVM1D7UJUt/dP2TinEVGx++/BXJ/0q37i6n9Iw0EM0w==", + "dev": true, + "requires": { + "@swc-node/core": "^1.8.2", + "@swc-node/sourcemap-support": "^0.1.11", + "chalk": "4", + "debug": "^4.3.3", + "pirates": "^4.0.4", + "tslib": "^2.3.1", + "typescript": "^4.5.3" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "@swc-node/sourcemap-support": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@swc-node/sourcemap-support/-/sourcemap-support-0.1.11.tgz", + "integrity": "sha512-b+Mn3oQl+7nUSt7hPzIbY9B30YhcFo1PT4kd9P4QmD6raycmIealOAhAdZID/JevphzsOXHQB4OqJm7Yi5tMcA==", + "dev": true, + "requires": { + "source-map-support": "^0.5.21" + } + }, + "@swc/core": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.2.162.tgz", + "integrity": "sha512-MFBmoV2qgGvi5bPX1tH3NLtWV4exa5jTCkC/30mdP5PTwEsxKJ5u+m1fuYOlgzDiBlytx8AihVZy2TmXhWZByw==", + "dev": true, + "requires": { + "@swc/core-android-arm-eabi": "1.2.162", + "@swc/core-android-arm64": "1.2.162", + "@swc/core-darwin-arm64": "1.2.162", + "@swc/core-darwin-x64": "1.2.162", + "@swc/core-freebsd-x64": "1.2.162", + "@swc/core-linux-arm-gnueabihf": "1.2.162", + "@swc/core-linux-arm64-gnu": "1.2.162", + "@swc/core-linux-arm64-musl": "1.2.162", + "@swc/core-linux-x64-gnu": "1.2.162", + "@swc/core-linux-x64-musl": "1.2.162", + "@swc/core-win32-arm64-msvc": "1.2.162", + "@swc/core-win32-ia32-msvc": "1.2.162", + "@swc/core-win32-x64-msvc": "1.2.162" + } + }, + "@swc/core-android-arm-eabi": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-android-arm-eabi/-/core-android-arm-eabi-1.2.162.tgz", + "integrity": "sha512-mQSuLspB1qBAYXyDP0Da60tPumhwD0CIm7tMjAFiOplEJN+9YKBlZ3EV9Xc1wF5bdWzJpmzmqEdN9FEfOQqlwQ==", + "dev": true, + "optional": true + }, + "@swc/core-android-arm64": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-android-arm64/-/core-android-arm64-1.2.162.tgz", + "integrity": "sha512-9TuuTrsrxbw1W1xUfcmRuEIKImJC725S/4McSFpoylYRIoHzD1DPpgP4fquU0/fzq7rldVD1tu4tg3xvEL8auw==", + "dev": true, + "optional": true + }, + "@swc/core-darwin-arm64": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.2.162.tgz", + "integrity": "sha512-gWJjD7NqKVxGFSJ4BeTXfBpRRRkxaQcWmmkwoXDQ1tmLRUX6K3V8MXp41mTdg7jJWDyKq4VTN6D8zLQcCUEhmQ==", + "dev": true, + "optional": true + }, + "@swc/core-darwin-x64": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.2.162.tgz", + "integrity": "sha512-+3foKCmxiMuPp1UCIPUg3N8CuzFRDPoPEQagz3TKT8W7Bkv9SXeIL8LPuwfH970rIcx1Ie/Q2UWXJwbckVmMHQ==", + "dev": true, + "optional": true + }, + "@swc/core-freebsd-x64": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-freebsd-x64/-/core-freebsd-x64-1.2.162.tgz", + "integrity": "sha512-YdfQgALPwJ6ZCvfqLlytCvZG/r/ZgBlOa0gaZvMGl6WMpnWgoVPA5OYBA5qzstg/OEWjMu6fldi+lElsvq8v2Q==", + "dev": true, + "optional": true + }, + "@swc/core-linux-arm-gnueabihf": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.2.162.tgz", + "integrity": "sha512-4elULEP2JWvSpEEI7JmhoI25cRQ2/ffBtf3+4vLlcAgJCdCrkYvHJO2fbWlN1fpydj34QabMsOROYS4ff4p0Og==", + "dev": true, + "optional": true + }, + "@swc/core-linux-arm64-gnu": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.2.162.tgz", + "integrity": "sha512-ZgR1J8H4qI7EuADgHEeDBtiiF8yt6vrznVtaBvEInDPdV9W10QNKsTqhuFkTfOqaHAO2u1+MkZRuvALGahdDaQ==", + "dev": true, + "optional": true + }, + "@swc/core-linux-arm64-musl": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.2.162.tgz", + "integrity": "sha512-1Egev+v8wlr7zPaS715sG7flzbGE0OLtcCR7p7oUqD/NbKwlA6czMch5JwNWvdRMjLThTYEeJ/ID+/xG8BqXUg==", + "dev": true, + "optional": true + }, + "@swc/core-linux-x64-gnu": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.2.162.tgz", + "integrity": "sha512-LFWV+8h6S3KmzVgHXRYpGYsaytGt+Vrbm8554ugUdzk465JnHyKzw3e6VRcJTxAGgXa+o1qUEkeBg7Wc/WWkmQ==", + "dev": true, + "optional": true + }, + "@swc/core-linux-x64-musl": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.2.162.tgz", + "integrity": "sha512-q5insucuYBVCjpDp8/EG3dbt2PFwGAo2vFzofr/lOlOo9p90jCzFRL0+eXg4Ar1YG6BL+T9o5LhFRggY+YHIBg==", + "dev": true, + "optional": true + }, + "@swc/core-win32-arm64-msvc": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.2.162.tgz", + "integrity": "sha512-xzksaPOqB3a8gxLoE0ZMi5w2NX9zzYDylmM3qbCVqft6IZid2XFG2lPFIwxJV1xfW68xMgAe0IECnjp/nQsS8g==", + "dev": true, + "optional": true + }, + "@swc/core-win32-ia32-msvc": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.2.162.tgz", + "integrity": "sha512-hUvS7UaSW+h16SSH7GwH571L2GnqWHPsiSKIDUvv1b/lca7dLcCY8RzsKafB/GLU+5EBQIN3nab3nH0vOWRkvw==", + "dev": true, + "optional": true + }, + "@swc/core-win32-x64-msvc": { + "version": "1.2.162", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.2.162.tgz", + "integrity": "sha512-Eb0SehVYWO5TpYeaPAD3T3iIPpgJa1q/rmvgMDvL0hi4UnOJlvj43kC4Dhuor6opLd6fJkCS7gBq9SxtGtb8bQ==", + "dev": true, + "optional": true + }, "@tootallnate/once": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", @@ -23624,6 +26339,12 @@ "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", "dev": true }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", + "dev": true + }, "@types/jsonwebtoken": { "version": "8.5.8", "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.8.tgz", @@ -23817,6 +26538,187 @@ "@types/node": "*" } }, + "@typescript-eslint/eslint-plugin": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.11.0.tgz", + "integrity": "sha512-HJh33bgzXe6jGRocOj4FmefD7hRY4itgjzOrSs3JPrTNXsX7j5+nQPciAUj/1nZtwo2kAc3C75jZO+T23gzSGw==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.11.0", + "@typescript-eslint/type-utils": "5.11.0", + "@typescript-eslint/utils": "5.11.0", + "debug": "^4.3.2", + "functional-red-black-tree": "^1.0.1", + "ignore": "^5.1.8", + "regexpp": "^3.2.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + } + } + }, + "@typescript-eslint/experimental-utils": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-5.11.0.tgz", + "integrity": "sha512-EPvC/bU2n1LKtzKWP1AjGWkp7r8tJ8giVlZHIODo6q7SAd6J+/9vjtEKHK2G/Qp+D2IGPsQge+oadDR3CZcFtQ==", + "dev": true, + "requires": { + "@typescript-eslint/utils": "5.11.0" + } + }, + "@typescript-eslint/parser": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.11.0.tgz", + "integrity": "sha512-x0DCjetHZYBRovJdr3U0zG9OOdNXUaFLJ82ehr1AlkArljJuwEsgnud+Q7umlGDFLFrs8tU8ybQDFocp/eX8mQ==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "5.11.0", + "@typescript-eslint/types": "5.11.0", + "@typescript-eslint/typescript-estree": "5.11.0", + "debug": "^4.3.2" + } + }, + "@typescript-eslint/scope-manager": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.11.0.tgz", + "integrity": "sha512-z+K4LlahDFVMww20t/0zcA7gq/NgOawaLuxgqGRVKS0PiZlCTIUtX0EJbC0BK1JtR4CelmkPK67zuCgpdlF4EA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.11.0", + "@typescript-eslint/visitor-keys": "5.11.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.11.0.tgz", + "integrity": "sha512-wDqdsYO6ofLaD4DsGZ0jGwxp4HrzD2YKulpEZXmgN3xo4BHJwf7kq49JTRpV0Gx6bxkSUmc9s0EIK1xPbFFpIA==", + "dev": true, + "requires": { + "@typescript-eslint/utils": "5.11.0", + "debug": "^4.3.2", + "tsutils": "^3.21.0" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + } + } + }, + "@typescript-eslint/types": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.11.0.tgz", + "integrity": "sha512-cxgBFGSRCoBEhvSVLkKw39+kMzUKHlJGVwwMbPcTZX3qEhuXhrjwaZXWMxVfxDgyMm+b5Q5b29Llo2yow8Y7xQ==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.11.0.tgz", + "integrity": "sha512-yVH9hKIv3ZN3lw8m/Jy5I4oXO4ZBMqijcXCdA4mY8ull6TPTAoQnKKrcZ0HDXg7Bsl0Unwwx7jcXMuNZc0m4lg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.11.0", + "@typescript-eslint/visitor-keys": "5.11.0", + "debug": "^4.3.2", + "globby": "^11.0.4", + "is-glob": "^4.0.3", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + }, + "dependencies": { + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + } + } + }, + "@typescript-eslint/utils": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.11.0.tgz", + "integrity": "sha512-g2I480tFE1iYRDyMhxPAtLQ9HAn0jjBtipgTCZmd9I9s11OV8CTsG+YfFciuNDcHqm4csbAgC2aVZCHzLxMSUw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "@typescript-eslint/scope-manager": "5.11.0", + "@typescript-eslint/types": "5.11.0", + "@typescript-eslint/typescript-estree": "5.11.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.11.0.tgz", + "integrity": "sha512-E8w/vJReMGuloGxJDkpPlGwhxocxOpSVgSvjiLO5IxZPmxZF30weOeJYyPSEACwM+X4NziYS9q+WkN/2DHYQwA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "5.11.0", + "eslint-visitor-keys": "^3.0.0" + } + }, "@ungap/promise-all-settled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", @@ -24026,6 +26928,13 @@ "dev": true, "requires": {} }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, "acorn-walk": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", @@ -24475,6 +27384,12 @@ "integrity": "sha1-9wtzXGvKGlycItmCw+Oef+ujva0=", "dev": true }, + "astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true + }, "async": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", @@ -24513,6 +27428,12 @@ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true + }, "atob": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", @@ -24952,7 +27873,8 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true + "dev": true, + "peer": true }, "builtins": { "version": "1.0.3", @@ -25922,6 +28844,12 @@ } } }, + "core-js-pure": { + "version": "3.21.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.21.1.tgz", + "integrity": "sha512-12VZfFIu+wyVbBebyHmRTuEE/tZrB4tJToWcwAMcsp3h4+sHR+fMJWbKpYiCRWlhFBq+KNyO8rIV9rTkeVmznQ==", + "dev": true + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -26320,6 +29248,12 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "deepmerge": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", @@ -26541,6 +29475,15 @@ "buffer-indexof": "^1.0.0" } }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "dom-serialize": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", @@ -26840,6 +29783,15 @@ "tapable": "^2.2.0" } }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dev": true, + "requires": { + "ansi-colors": "^4.1.1" + } + }, "ent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", @@ -27122,6 +30074,182 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "dev": true, + "requires": { + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + }, + "globals": { + "version": "13.13.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz", + "integrity": "sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -27132,12 +30260,77 @@ "estraverse": "^4.1.1" } }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "dev": true, + "requires": { + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } + } + }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + } + } + }, "esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -27617,6 +30810,15 @@ "escape-string-regexp": "^1.0.5" } }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, "file-type": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/file-type/-/file-type-9.0.0.tgz", @@ -27859,6 +31061,16 @@ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, "flatted": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", @@ -28089,6 +31301,12 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "gauge": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", @@ -30482,6 +33700,16 @@ "klona": "^2.0.4" } }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, "license-webpack-plugin": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/license-webpack-plugin/-/license-webpack-plugin-4.0.2.tgz", @@ -30649,6 +33877,18 @@ "integrity": "sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw=", "dev": true }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha1-WjUNoLERO4N+z//VgSy+WNbq4ZM=", + "dev": true + }, "lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", @@ -31503,6 +34743,12 @@ "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==" }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, "needle": { "version": "2.9.1", "resolved": "https://registry.npmjs.org/needle/-/needle-2.9.1.tgz", @@ -32009,6 +35255,15 @@ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, + "nx": { + "version": "13.1.3", + "resolved": "https://registry.npmjs.org/nx/-/nx-13.1.3.tgz", + "integrity": "sha512-clM0NQhQKYkqcNz2E3uYRMLwhp2L/9dBhJhQi9XBX4IAyA2gWAomhRIlLm5Xxg3g4h1xwSpP3eJ5t89VikY8Pw==", + "dev": true, + "requires": { + "@nrwl/cli": "*" + } + }, "nyc": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", @@ -32393,6 +35648,28 @@ "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", "dev": true }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "dependencies": { + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + } + } + }, "ora": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", @@ -32828,6 +36105,12 @@ "pinkie": "^2.0.0" } }, + "pirates": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", + "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "dev": true + }, "piscina": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/piscina/-/piscina-3.2.0.tgz", @@ -33397,6 +36680,12 @@ } } }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, "pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -33443,7 +36732,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "optional": true + "devOptional": true }, "promise-inflight": { "version": "1.0.1", @@ -33809,6 +37098,12 @@ "define-properties": "^1.1.3" } }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, "regexpu-core": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", @@ -34620,6 +37915,43 @@ "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", "dev": true }, + "slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + } + } + }, "smart-buffer": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", @@ -35361,6 +38693,19 @@ "integrity": "sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==", "dev": true }, + "table": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.8.0.tgz", + "integrity": "sha512-s/fitrbVeEyHKFa7mFdkuQMWlH1Wgw/yEXMt5xACT4ZpzWFluehAxRtUUQKPuWhaLAWhFcVx6w3oC8VKaUfPGA==", + "dev": true, + "requires": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + } + }, "tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -35787,6 +39132,35 @@ "resolved": "https://registry.npmjs.org/ts-node-iptc/-/ts-node-iptc-1.0.11.tgz", "integrity": "sha512-6f10Fae8EjQwEOY4OuIDEiRVZfwVnnWq9ipb7B7UB71diTEz4lgH8R5vAvRt4B9G15K3/Q/8XdA0OX4vC27Fxg==" }, + "tsconfig-paths": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz", + "integrity": "sha512-fxDhWnFSLt3VuTwtvJt5fpwxBHg5AdKWMsgcPOOIilyjymcYVZoCQF8fvFRezCNfblEXmi+PcM1eYHeOAgXCOQ==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + } + } + }, "tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", @@ -35797,6 +39171,7 @@ "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", "dev": true, + "peer": true, "requires": { "@babel/code-frame": "^7.0.0", "builtin-modules": "^1.1.1", @@ -35817,13 +39192,15 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true + "dev": true, + "peer": true }, "mkdirp": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, + "peer": true, "requires": { "minimist": "^1.2.6" } @@ -35832,13 +39209,15 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true + "dev": true, + "peer": true }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "dev": true, + "peer": true } } }, @@ -35852,6 +39231,7 @@ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", "dev": true, + "peer": true, "requires": { "tslib": "^1.8.1" }, @@ -35860,7 +39240,8 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true + "dev": true, + "peer": true } } }, @@ -35884,6 +39265,15 @@ "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", "dev": true }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, "type-detect": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", @@ -36036,8 +39426,7 @@ "version": "4.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", - "devOptional": true, - "peer": true + "devOptional": true }, "ua-parser-js": { "version": "0.7.31", @@ -36260,6 +39649,12 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, "v8-compile-cache-lib": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz", @@ -36785,6 +40180,12 @@ "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", "dev": true }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, "workerpool": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.0.tgz", diff --git a/package.json b/package.json index 31784dc6..bd943b02 100644 --- a/package.json +++ b/package.json @@ -14,14 +14,14 @@ "build-backend": "tsc", "pretest": "tsc", "test": "ng test && nyc mocha --recursive test", - "lint": "tslint -p tsconfig.json -c tslint.json src/**/**.ts test/**/**.ts", "coverage": "nyc report --reporter=text-lcov | coveralls", "start": "node ./src/backend/index", "run-dev": "ng build --configuration=dev", "build-stats": "ng build --stats-json", "analyze": "webpack-bundle-analyzer dist/en/stats.json", "merge-new-translation": "gulp merge-new-translation", - "generate-man": "gulp generate-man" + "generate-man": "gulp generate-man", + "lint": "ng lint" }, "repository": { "type": "git", @@ -56,6 +56,11 @@ "devDependencies": { "@angular-devkit/build-angular": "13.3.1", "@angular-devkit/build-optimizer": "0.1302.1", + "@angular-eslint/builder": "13.1.0", + "@angular-eslint/eslint-plugin": "13.1.0", + "@angular-eslint/eslint-plugin-template": "13.1.0", + "@angular-eslint/schematics": "13.1.0", + "@angular-eslint/template-parser": "13.1.0", "@angular/animations": "13.3.1", "@angular/cli": "13.3.1", "@angular/common": "13.3.1", @@ -92,6 +97,8 @@ "@types/node-geocoder": "3.24.4", "@types/sharp": "0.30.0", "@types/xml2js": "0.4.9", + "@typescript-eslint/eslint-plugin": "5.11.0", + "@typescript-eslint/parser": "5.11.0", "bootstrap": "4.6.1", "chai": "4.3.6", "chai-http": "4.3.0", @@ -100,6 +107,7 @@ "coveralls": "3.1.1", "deep-equal-in-any-order": "1.1.15", "ejs-loader": "0.5.0", + "eslint": "7.32.0", "gulp": "4.0.2", "gulp-json-editor": "2.5.6", "gulp-typescript": "5.0.1", @@ -127,7 +135,6 @@ "rxjs": "7.5.5", "ts-helpers": "1.1.2", "ts-node": "10.7.0", - "tslint": "6.1.3", "webpack-bundle-analyzer": "4.5.0", "xlf-google-translate": "1.0.0-beta.22", "xml2js": "0.4.23", diff --git a/src/backend/Logger.ts b/src/backend/Logger.ts index 2fa4a0e4..ec7f449a 100644 --- a/src/backend/Logger.ts +++ b/src/backend/Logger.ts @@ -1,12 +1,14 @@ -import {Config} from '../common/config/private/Config'; -import {LogLevel} from '../common/config/private/PrivateConfig'; +import { Config } from '../common/config/private/Config'; +import { LogLevel } from '../common/config/private/PrivateConfig'; export type logFN = (...args: (string | number)[]) => void; -const forcedDebug = process.env.NODE_ENV === 'debug'; +const forcedDebug = process.env['NODE_ENV'] === 'debug'; if (forcedDebug === true) { - console.log('NODE_ENV environmental variable is set to debug, forcing all logs to print'); + console.log( + 'NODE_ENV environmental variable is set to debug, forcing all logs to print' + ); } export class Logger { @@ -22,10 +24,8 @@ export class Logger { return; } Logger.log(`[\x1b[34mDEBUG\x1b[0m]`, ...args); - } - public static verbose(...args: (string | number)[]): void { if (!forcedDebug && Config.Server.Log.level < LogLevel.verbose) { return; @@ -38,7 +38,6 @@ export class Logger { return; } Logger.log(`[\x1b[32mINFO_\x1b[0m]`, ...args); - } public static warn(...args: (string | number)[]): void { @@ -49,14 +48,18 @@ export class Logger { } public static error(...args: (string | number)[]): void { - Logger.log(`[\x1b[31mERROR\x1b[0m]`, ...args); } private static log(tag: string, ...args: (string | number)[]): void { - const date = (new Date()).toLocaleString(); + const date = new Date().toLocaleString(); let LOG_TAG = ''; - if (args.length > 0 && typeof args[0] === 'string' && args[0].startsWith('[') && args[0].endsWith(']')) { + if ( + args.length > 0 && + typeof args[0] === 'string' && + args[0].startsWith('[') && + args[0].endsWith(']') + ) { LOG_TAG = args[0]; args.shift(); } diff --git a/src/backend/ProjectPath.ts b/src/backend/ProjectPath.ts index d80b15fd..636161ea 100644 --- a/src/backend/ProjectPath.ts +++ b/src/backend/ProjectPath.ts @@ -1,6 +1,6 @@ import * as path from 'path'; import * as fs from 'fs'; -import {Config} from '../common/config/private/Config'; +import { Config } from '../common/config/private/Config'; class ProjectPathClass { public Root: string; @@ -40,7 +40,6 @@ class ProjectPathClass { if (!fs.existsSync(this.TempFolder)) { fs.mkdirSync(this.TempFolder); } - } } diff --git a/src/backend/exceptions/LocationLookupException.ts b/src/backend/exceptions/LocationLookupException.ts index 140d9815..e5a296f6 100644 --- a/src/backend/exceptions/LocationLookupException.ts +++ b/src/backend/exceptions/LocationLookupException.ts @@ -1,5 +1,4 @@ export class LocationLookupException extends Error { - constructor(message: string, public location: string) { super(message); } diff --git a/src/backend/index.ts b/src/backend/index.ts index 231b401f..e70361f7 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -1,9 +1,8 @@ import * as cluster from 'cluster'; -import {Server} from './server'; -import {Worker} from './model/threading/Worker'; +import { Server } from './server'; +import { Worker } from './model/threading/Worker'; if ((cluster as any).isMaster) { - // tslint:disable-next-line:no-unused-expression new Server(); } else { Worker.process(); diff --git a/src/backend/middlewares/AlbumMWs.ts b/src/backend/middlewares/AlbumMWs.ts index de05f97f..67f9e1c7 100644 --- a/src/backend/middlewares/AlbumMWs.ts +++ b/src/backend/middlewares/AlbumMWs.ts @@ -1,63 +1,91 @@ -import {NextFunction, Request, Response} from 'express'; -import {ErrorCodes, ErrorDTO} from '../../common/entities/Error'; -import {ObjectManagers} from '../model/ObjectManagers'; -import {Utils} from '../../common/Utils'; -import {Config} from '../../common/config/private/Config'; - +import { NextFunction, Request, Response } from 'express'; +import { ErrorCodes, ErrorDTO } from '../../common/entities/Error'; +import { ObjectManagers } from '../model/ObjectManagers'; +import { Utils } from '../../common/Utils'; +import { Config } from '../../common/config/private/Config'; export class AlbumMWs { - - - public static async listAlbums(req: Request, res: Response, next: NextFunction): Promise { + public static async listAlbums( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (Config.Client.Album.enabled === false) { return next(); } try { - req.resultPipe = await ObjectManagers.getInstance() - .AlbumManager.getAlbums(); + req.resultPipe = + await ObjectManagers.getInstance().AlbumManager.getAlbums(); return next(); - } catch (err) { - return next(new ErrorDTO(ErrorCodes.ALBUM_ERROR, 'Error during listing albums', err)); + return next( + new ErrorDTO(ErrorCodes.ALBUM_ERROR, 'Error during listing albums', err) + ); } } - - public static async deleteAlbum(req: Request, res: Response, next: NextFunction): Promise { + public static async deleteAlbum( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (Config.Client.Album.enabled === false) { return next(); } - if (!req.params.id || !Utils.isUInt32(parseInt(req.params.id, 10))) { + if (!req.params['id'] || !Utils.isUInt32(parseInt(req.params['id'], 10))) { return next(); } try { - await ObjectManagers.getInstance().AlbumManager.deleteAlbum(parseInt(req.params.id, 10)); + await ObjectManagers.getInstance().AlbumManager.deleteAlbum( + parseInt(req.params['id'], 10) + ); req.resultPipe = 'ok'; return next(); - } catch (err) { - return next(new ErrorDTO(ErrorCodes.ALBUM_ERROR, 'Error during deleting albums', err)); + return next( + new ErrorDTO( + ErrorCodes.ALBUM_ERROR, + 'Error during deleting albums', + err + ) + ); } } - public static async createSavedSearch(req: Request, res: Response, next: NextFunction): Promise { + public static async createSavedSearch( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (Config.Client.Album.enabled === false) { return next(); } - if ((typeof req.body === 'undefined') || (typeof req.body.name !== 'string') || (typeof req.body.searchQuery !== 'object')) { - return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'updateSharing filed is missing')); + if ( + typeof req.body === 'undefined' || + typeof req.body.name !== 'string' || + typeof req.body.searchQuery !== 'object' + ) { + return next( + new ErrorDTO(ErrorCodes.INPUT_ERROR, 'updateSharing filed is missing') + ); } try { - await ObjectManagers.getInstance().AlbumManager.addSavedSearch(req.body.name, req.body.searchQuery); + await ObjectManagers.getInstance().AlbumManager.addSavedSearch( + req.body.name, + req.body.searchQuery + ); req.resultPipe = 'ok'; return next(); - } catch (err) { - return next(new ErrorDTO(ErrorCodes.ALBUM_ERROR, 'Error during creating saved search albums', err)); + return next( + new ErrorDTO( + ErrorCodes.ALBUM_ERROR, + 'Error during creating saved search albums', + err + ) + ); } } - - } diff --git a/src/backend/middlewares/GalleryMWs.ts b/src/backend/middlewares/GalleryMWs.ts index 6b4945e5..c214e60f 100644 --- a/src/backend/middlewares/GalleryMWs.ts +++ b/src/backend/middlewares/GalleryMWs.ts @@ -1,32 +1,43 @@ import * as path from 'path'; -import {promises as fsp} from 'fs'; +import { promises as fsp } from 'fs'; import * as archiver from 'archiver'; -import {NextFunction, Request, Response} from 'express'; -import {ErrorCodes, ErrorDTO} from '../../common/entities/Error'; -import {DirectoryDTOUtils, ParentDirectoryDTO} from '../../common/entities/DirectoryDTO'; -import {ObjectManagers} from '../model/ObjectManagers'; -import {ContentWrapper} from '../../common/entities/ConentWrapper'; -import {PhotoDTO} from '../../common/entities/PhotoDTO'; -import {ProjectPath} from '../ProjectPath'; -import {Config} from '../../common/config/private/Config'; -import {UserDTOUtils} from '../../common/entities/UserDTO'; -import {MediaDTO, MediaDTOUtils} from '../../common/entities/MediaDTO'; -import {VideoDTO} from '../../common/entities/VideoDTO'; -import {Utils} from '../../common/Utils'; -import {QueryParams} from '../../common/QueryParams'; -import {VideoProcessing} from '../model/fileprocessing/VideoProcessing'; -import {SearchQueryDTO, SearchQueryTypes} from '../../common/entities/SearchQueryDTO'; -import {LocationLookupException} from '../exceptions/LocationLookupException'; -import {SupportedFormats} from '../../common/SupportedFormats'; -import {ServerTime} from './ServerTimingMWs'; +import { NextFunction, Request, Response } from 'express'; +import { ErrorCodes, ErrorDTO } from '../../common/entities/Error'; +import { + DirectoryDTOUtils, + ParentDirectoryDTO, +} from '../../common/entities/DirectoryDTO'; +import { ObjectManagers } from '../model/ObjectManagers'; +import { ContentWrapper } from '../../common/entities/ConentWrapper'; +import { PhotoDTO } from '../../common/entities/PhotoDTO'; +import { ProjectPath } from '../ProjectPath'; +import { Config } from '../../common/config/private/Config'; +import { UserDTOUtils } from '../../common/entities/UserDTO'; +import { MediaDTO, MediaDTOUtils } from '../../common/entities/MediaDTO'; +import { VideoDTO } from '../../common/entities/VideoDTO'; +import { Utils } from '../../common/Utils'; +import { QueryParams } from '../../common/QueryParams'; +import { VideoProcessing } from '../model/fileprocessing/VideoProcessing'; +import { + SearchQueryDTO, + SearchQueryTypes, +} from '../../common/entities/SearchQueryDTO'; +import { LocationLookupException } from '../exceptions/LocationLookupException'; +import { SupportedFormats } from '../../common/SupportedFormats'; +import { ServerTime } from './ServerTimingMWs'; export class GalleryMWs { - - @ServerTime('1.db', 'List Directory') - public static async listDirectory(req: Request, res: Response, next: NextFunction): Promise { - const directoryName = req.params.directory || '/'; - const absoluteDirectoryName = path.join(ProjectPath.ImageFolder, directoryName); + public static async listDirectory( + req: Request, + res: Response, + next: NextFunction + ): Promise { + const directoryName = req.params['directory'] || '/'; + const absoluteDirectoryName = path.join( + ProjectPath.ImageFolder, + directoryName + ); try { if ((await fsp.stat(absoluteDirectoryName)).isDirectory() === false) { return next(); @@ -36,36 +47,59 @@ export class GalleryMWs { } try { - const directory = await ObjectManagers.getInstance() - .GalleryManager.listDirectory(directoryName, - parseInt(req.query[QueryParams.gallery.knownLastModified] as string, 10), - parseInt(req.query[QueryParams.gallery.knownLastScanned] as string, 10)); + const directory = + await ObjectManagers.getInstance().GalleryManager.listDirectory( + directoryName, + parseInt( + req.query[QueryParams.gallery.knownLastModified] as string, + 10 + ), + parseInt( + req.query[QueryParams.gallery.knownLastScanned] as string, + 10 + ) + ); if (directory == null) { req.resultPipe = new ContentWrapper(null, null, true); return next(); } - if (req.session.user.permissions && - req.session.user.permissions.length > 0 && - req.session.user.permissions[0] !== '/*') { + if ( + req.session['user'].permissions && + req.session['user'].permissions.length > 0 && + req.session['user'].permissions[0] !== '/*' + ) { directory.directories = directory.directories.filter((d): boolean => - UserDTOUtils.isDirectoryAvailable(d, req.session.user.permissions)); + UserDTOUtils.isDirectoryAvailable(d, req.session['user'].permissions) + ); } req.resultPipe = new ContentWrapper(directory, null); return next(); - } catch (err) { - return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during listing the directory', err)); + return next( + new ErrorDTO( + ErrorCodes.GENERAL_ERROR, + 'Error during listing the directory', + err + ) + ); } } @ServerTime('1.zip', 'Zip Directory') - public static async zipDirectory(req: Request, res: Response, next: NextFunction): Promise { + public static async zipDirectory( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (Config.Client.Other.enableDownloadZip === false) { return next(); } - const directoryName = req.params.directory || '/'; - const absoluteDirectoryName = path.join(ProjectPath.ImageFolder, directoryName); + const directoryName = req.params['directory'] || '/'; + const absoluteDirectoryName = path.join( + ProjectPath.ImageFolder, + directoryName + ); try { if ((await fsp.stat(absoluteDirectoryName)).isDirectory() === false) { return next(); @@ -95,24 +129,29 @@ export class GalleryMWs { // append photos in absoluteDirectoryName // using case-insensitive glob of extensions for (const ext of SupportedFormats.WithDots.Photos) { - archive.glob(`*${ext}`, {cwd: absoluteDirectoryName, nocase: true}); + archive.glob(`*${ext}`, { cwd: absoluteDirectoryName, nocase: true }); } // append videos in absoluteDirectoryName // using case-insensitive glob of extensions for (const ext of SupportedFormats.WithDots.Videos) { - archive.glob(`*${ext}`, {cwd: absoluteDirectoryName, nocase: true}); + archive.glob(`*${ext}`, { cwd: absoluteDirectoryName, nocase: true }); } await archive.finalize(); return next(); - } catch (err) { - return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error creating zip', err)); + return next( + new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error creating zip', err) + ); } } @ServerTime('3.cleanUp', 'Clean up') - public static cleanUpGalleryResults(req: Request, res: Response, next: NextFunction): any { + public static cleanUpGalleryResults( + req: Request, + res: Response, + next: NextFunction + ): any { if (!req.resultPipe) { return next(); } @@ -148,32 +187,43 @@ export class GalleryMWs { cleanUpMedia(cw.directory.media); } if (cw.searchResult) { - cw.searchResult.directories.forEach(d => DirectoryDTOUtils.packDirectory(d)); + cw.searchResult.directories.forEach((d) => + DirectoryDTOUtils.packDirectory(d) + ); cleanUpMedia(cw.searchResult.media); } - if (Config.Client.Media.Video.enabled === false) { if (cw.directory) { const removeVideos = (dir: ParentDirectoryDTO): void => { - dir.media = dir.media.filter((m): boolean => !MediaDTOUtils.isVideo(m)); + dir.media = dir.media.filter( + (m): boolean => !MediaDTOUtils.isVideo(m) + ); }; removeVideos(cw.directory); } if (cw.searchResult) { - cw.searchResult.media = cw.searchResult.media.filter((m): boolean => !MediaDTOUtils.isVideo(m)); + cw.searchResult.media = cw.searchResult.media.filter( + (m): boolean => !MediaDTOUtils.isVideo(m) + ); } } return next(); } - - public static async loadFile(req: Request, res: Response, next: NextFunction): Promise { - if (!(req.params.mediaPath)) { + public static async loadFile( + req: Request, + res: Response, + next: NextFunction + ): Promise { + if (!req.params['mediaPath']) { return next(); } - const fullMediaPath = path.join(ProjectPath.ImageFolder, req.params.mediaPath); + const fullMediaPath = path.join( + ProjectPath.ImageFolder, + req.params['mediaPath'] + ); // check if file exist try { @@ -181,63 +231,95 @@ export class GalleryMWs { return next(); } } catch (e) { - return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'no such file:' + req.params.mediaPath, 'can\'t find file: ' + fullMediaPath)); + return next( + new ErrorDTO( + ErrorCodes.GENERAL_ERROR, + 'no such file:' + req.params['mediaPath'], + "can't find file: " + fullMediaPath + ) + ); } - req.resultPipe = fullMediaPath; return next(); } - public static async loadBestFitVideo(req: Request, res: Response, next: NextFunction): Promise { - if (!(req.resultPipe)) { + public static async loadBestFitVideo( + req: Request, + res: Response, + next: NextFunction + ): Promise { + if (!req.resultPipe) { return next(); } const fullMediaPath: string = req.resultPipe; - const convertedVideo = VideoProcessing.generateConvertedFilePath(fullMediaPath); + const convertedVideo = + VideoProcessing.generateConvertedFilePath(fullMediaPath); // check if transcoded video exist try { await fsp.access(convertedVideo); req.resultPipe = convertedVideo; - } catch (e) { - } - + // eslint-disable-next-line no-empty + } catch (e) {} return next(); } - @ServerTime('1.db', 'Search') - public static async search(req: Request, res: Response, next: NextFunction): Promise { - if (Config.Client.Search.enabled === false || !(req.params.searchQueryDTO)) { + public static async search( + req: Request, + res: Response, + next: NextFunction + ): Promise { + if ( + Config.Client.Search.enabled === false || + !req.params['searchQueryDTO'] + ) { return next(); } - const query: SearchQueryDTO = JSON.parse(req.params.searchQueryDTO as any); + const query: SearchQueryDTO = JSON.parse( + req.params['searchQueryDTO'] as string + ); try { - const result = await ObjectManagers.getInstance().SearchManager.search(query); + const result = await ObjectManagers.getInstance().SearchManager.search( + query + ); - result.directories.forEach((dir): MediaDTO[] => dir.media = dir.media || []); + result.directories.forEach( + (dir): MediaDTO[] => (dir.media = dir.media || []) + ); req.resultPipe = new ContentWrapper(null, result); return next(); } catch (err) { if (err instanceof LocationLookupException) { - return next(new ErrorDTO(ErrorCodes.LocationLookUp_ERROR, 'Cannot find location: ' + err.location, err)); + return next( + new ErrorDTO( + ErrorCodes.LocationLookUp_ERROR, + 'Cannot find location: ' + err.location, + err + ) + ); } - return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during searching', err)); + return next( + new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during searching', err) + ); } } - @ServerTime('1.db', 'Autocomplete') - public static async autocomplete(req: Request, res: Response, next: NextFunction): Promise { + public static async autocomplete( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (Config.Client.Search.AutoComplete.enabled === false) { return next(); } - if (!(req.params.text)) { + if (!req.params['text']) { return next(); } @@ -246,35 +328,55 @@ export class GalleryMWs { type = parseInt(req.query[QueryParams.gallery.search.type] as string, 10); } try { - req.resultPipe = await ObjectManagers.getInstance().SearchManager.autocomplete(req.params.text, type); + req.resultPipe = + await ObjectManagers.getInstance().SearchManager.autocomplete( + req.params['text'], + type + ); return next(); } catch (err) { - return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during searching', err)); + return next( + new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during searching', err) + ); } - } - - public static async getRandomImage(req: Request, res: Response, next: NextFunction): Promise { - if (Config.Client.RandomPhoto.enabled === false || !(req.params.searchQueryDTO)) { + public static async getRandomImage( + req: Request, + res: Response, + next: NextFunction + ): Promise { + if ( + Config.Client.RandomPhoto.enabled === false || + !req.params['searchQueryDTO'] + ) { return next(); } try { - const query: SearchQueryDTO = JSON.parse(req.params.searchQueryDTO as any); + const query: SearchQueryDTO = JSON.parse( + req.params['searchQueryDTO'] as string + ); - const photo = await ObjectManagers.getInstance() - .SearchManager.getRandomPhoto(query); + const photo = + await ObjectManagers.getInstance().SearchManager.getRandomPhoto(query); if (!photo) { return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'No photo found')); } - req.params.mediaPath = path.join(photo.directory.path, photo.directory.name, photo.name); + req.params['mediaPath'] = path.join( + photo.directory.path, + photo.directory.name, + photo.name + ); return next(); } catch (e) { - return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Can\'t get random photo: ' + e.toString())); + return next( + new ErrorDTO( + ErrorCodes.GENERAL_ERROR, + "Can't get random photo: " + e.toString() + ) + ); } } - - } diff --git a/src/backend/middlewares/NotificationMWs.ts b/src/backend/middlewares/NotificationMWs.ts index 1d4d8bda..eabaefd8 100644 --- a/src/backend/middlewares/NotificationMWs.ts +++ b/src/backend/middlewares/NotificationMWs.ts @@ -1,17 +1,12 @@ -import {NextFunction, Request, Response} from 'express'; -import {UserRoles} from '../../common/entities/UserDTO'; -import {NotificationManager} from '../model/NotifocationManager'; - +import { NextFunction, Request, Response } from 'express'; +import { UserRoles } from '../../common/entities/UserDTO'; +import { NotificationManager } from '../model/NotifocationManager'; export class NotificationMWs { - - public static list(req: Request, res: Response, next: NextFunction): any { - - if (req.session.user.role >= UserRoles.Admin) { + if (req.session['user'].role >= UserRoles.Admin) { req.resultPipe = NotificationManager.notifications; } else if (NotificationManager.notifications.length > 0) { - req.resultPipe = NotificationManager.HasNotification; } else { req.resultPipe = []; diff --git a/src/backend/middlewares/PersonMWs.ts b/src/backend/middlewares/PersonMWs.ts index 7adec16a..9b3f26f1 100644 --- a/src/backend/middlewares/PersonMWs.ts +++ b/src/backend/middlewares/PersonMWs.ts @@ -1,59 +1,91 @@ -import {NextFunction, Request, Response} from 'express'; -import {ErrorCodes, ErrorDTO} from '../../common/entities/Error'; -import {ObjectManagers} from '../model/ObjectManagers'; -import {PersonDTO, PersonWithSampleRegion} from '../../common/entities/PersonDTO'; -import {Utils} from '../../common/Utils'; - +import { NextFunction, Request, Response } from 'express'; +import { ErrorCodes, ErrorDTO } from '../../common/entities/Error'; +import { ObjectManagers } from '../model/ObjectManagers'; +import { + PersonDTO, + PersonWithSampleRegion, +} from '../../common/entities/PersonDTO'; +import { Utils } from '../../common/Utils'; export class PersonMWs { - - - public static async updatePerson(req: Request, res: Response, next: NextFunction): Promise { - if (!req.params.name) { + public static async updatePerson( + req: Request, + res: Response, + next: NextFunction + ): Promise { + if (!req.params['name']) { return next(); } try { - req.resultPipe = await ObjectManagers.getInstance() - .PersonManager.updatePerson(req.params.name as string, - req.body as PersonDTO); + req.resultPipe = + await ObjectManagers.getInstance().PersonManager.updatePerson( + req.params['name'] as string, + req.body as PersonDTO + ); return next(); - } catch (err) { - return next(new ErrorDTO(ErrorCodes.PERSON_ERROR, 'Error during updating a person', err)); + return next( + new ErrorDTO( + ErrorCodes.PERSON_ERROR, + 'Error during updating a person', + err + ) + ); } } - public static async getPerson(req: Request, res: Response, next: NextFunction): Promise { - if (!req.params.name) { + public static async getPerson( + req: Request, + res: Response, + next: NextFunction + ): Promise { + if (!req.params['name']) { return next(); } try { - req.resultPipe = await ObjectManagers.getInstance() - .PersonManager.get(req.params.name as string); + req.resultPipe = await ObjectManagers.getInstance().PersonManager.get( + req.params['name'] as string + ); return next(); - } catch (err) { - return next(new ErrorDTO(ErrorCodes.PERSON_ERROR, 'Error during updating a person', err)); + return next( + new ErrorDTO( + ErrorCodes.PERSON_ERROR, + 'Error during updating a person', + err + ) + ); } } - - public static async listPersons(req: Request, res: Response, next: NextFunction): Promise { + public static async listPersons( + req: Request, + res: Response, + next: NextFunction + ): Promise { try { - req.resultPipe = await ObjectManagers.getInstance() - .PersonManager.getAll(); + req.resultPipe = + await ObjectManagers.getInstance().PersonManager.getAll(); return next(); - } catch (err) { - return next(new ErrorDTO(ErrorCodes.PERSON_ERROR, 'Error during listing persons', err)); + return next( + new ErrorDTO( + ErrorCodes.PERSON_ERROR, + 'Error during listing persons', + err + ) + ); } } - - public static async cleanUpPersonResults(req: Request, res: Response, next: NextFunction): Promise { + public static async cleanUpPersonResults( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (!req.resultPipe) { return next(); } @@ -64,13 +96,16 @@ export class PersonMWs { } req.resultPipe = persons; return next(); - } catch (err) { - return next(new ErrorDTO(ErrorCodes.PERSON_ERROR, 'Error during removing sample photo from all persons', err)); + return next( + new ErrorDTO( + ErrorCodes.PERSON_ERROR, + 'Error during removing sample photo from all persons', + err + ) + ); } } - - } diff --git a/src/backend/middlewares/RenderingMWs.ts b/src/backend/middlewares/RenderingMWs.ts index 95a0a49f..664d2b93 100644 --- a/src/backend/middlewares/RenderingMWs.ts +++ b/src/backend/middlewares/RenderingMWs.ts @@ -1,17 +1,20 @@ -import {NextFunction, Request, Response} from 'express'; -import {ErrorCodes, ErrorDTO} from '../../common/entities/Error'; -import {Message} from '../../common/entities/Message'; -import {Config, PrivateConfigClass} from '../../common/config/private/Config'; -import {UserDTO, UserRoles} from '../../common/entities/UserDTO'; -import {NotificationManager} from '../model/NotifocationManager'; -import {Logger} from '../Logger'; -import {SharingDTO} from '../../common/entities/SharingDTO'; -import {Utils} from '../../common/Utils'; -import {LoggerRouter} from '../routes/LoggerRouter'; +import { NextFunction, Request, Response } from 'express'; +import { ErrorCodes, ErrorDTO } from '../../common/entities/Error'; +import { Message } from '../../common/entities/Message'; +import { Config, PrivateConfigClass } from '../../common/config/private/Config'; +import { UserDTO, UserRoles } from '../../common/entities/UserDTO'; +import { NotificationManager } from '../model/NotifocationManager'; +import { Logger } from '../Logger'; +import { SharingDTO } from '../../common/entities/SharingDTO'; +import { Utils } from '../../common/Utils'; +import { LoggerRouter } from '../routes/LoggerRouter'; export class RenderingMWs { - - public static renderResult(req: Request, res: Response, next: NextFunction): any { + public static renderResult( + req: Request, + res: Response, + next: NextFunction + ): any { if (typeof req.resultPipe === 'undefined') { return next(); } @@ -19,19 +22,22 @@ export class RenderingMWs { return RenderingMWs.renderMessage(res, req.resultPipe); } - - public static renderSessionUser(req: Request, res: Response, next: NextFunction): any { - if (!(req.session.user)) { + public static renderSessionUser( + req: Request, + res: Response, + next: NextFunction + ): any { + if (!req.session['user']) { return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'User not exists')); } const user = { - id: req.session.user.id, - name: req.session.user.name, - csrfToken: req.session.user.csrfToken || req.csrfToken(), - role: req.session.user.role, - usedSharingKey: req.session.user.usedSharingKey, - permissions: req.session.user.permissions + id: req.session['user'].id, + name: req.session['user'].name, + csrfToken: req.session['user'].csrfToken || req.csrfToken(), + role: req.session['user'].role, + usedSharingKey: req.session['user'].usedSharingKey, + permissions: req.session['user'].permissions, } as UserDTO; if (!user.csrfToken && req.csrfToken) { @@ -41,17 +47,24 @@ export class RenderingMWs { RenderingMWs.renderMessage(res, user); } - public static renderSharing(req: Request, res: Response, next: NextFunction): any { + public static renderSharing( + req: Request, + res: Response, + next: NextFunction + ): any { if (!req.resultPipe) { return next(); } - const {password, creator, ...sharing} = req.resultPipe; + const { password, creator, ...sharing } = req.resultPipe; RenderingMWs.renderMessage(res, sharing); } - - public static renderSharingList(req: Request, res: Response, next: NextFunction): any { + public static renderSharingList( + req: Request, + res: Response, + next: NextFunction + ): any { if (!req.resultPipe) { return next(); } @@ -64,44 +77,70 @@ export class RenderingMWs { return RenderingMWs.renderMessage(res, shares); } - public static renderFile(req: Request, res: Response, next: NextFunction): any { + public static renderFile( + req: Request, + res: Response, + next: NextFunction + ): any { if (!req.resultPipe) { return next(); } - return res.sendFile(req.resultPipe, {maxAge: 31536000, dotfiles: 'allow'}); + return res.sendFile(req.resultPipe, { + maxAge: 31536000, + dotfiles: 'allow', + }); } - public static renderOK(req: Request, res: Response, next: NextFunction): void { + public static renderOK( + req: Request, + res: Response, + next: NextFunction + ): void { const message = new Message(null, 'ok'); res.json(message); } - - public static async renderConfig(req: Request, res: Response, next: NextFunction): Promise { + public static async renderConfig( + req: Request, + res: Response, + next: NextFunction + ): Promise { const originalConf = await Config.original(); // These are sensitive information, do not send to the client side originalConf.Server.sessionSecret = null; originalConf.Server.Database.enforcedUsers = null; - const message = new Message(null, originalConf.toJSON({ - attachState: true, - attachVolatile: true - }) as any); + const message = new Message( + null, + originalConf.toJSON({ + attachState: true, + attachVolatile: true, + }) as any + ); res.json(message); } - - public static renderError(err: any, req: Request, res: Response, next: NextFunction): any { - + public static renderError( + err: any, + req: Request, + res: Response, + next: NextFunction + ): any { if (err instanceof ErrorDTO) { if (err.details) { Logger.warn('Handled error:'); LoggerRouter.log(Logger.warn, req, res); console.log(err); - delete (err.details); // do not send back error object to the client side + delete err.details; // do not send back error object to the client side // hide error details for non developers - if (!(req.session && req.session.user && req.session.user.role >= UserRoles.Developer)) { - delete (err.detailsStr); + if ( + !( + req.session && + req.session['user'] && + req.session['user'].role >= UserRoles.Developer + ) + ) { + delete err.detailsStr; } } const message = new Message(err, null); @@ -111,11 +150,8 @@ export class RenderingMWs { return next(err); } - protected static renderMessage(res: Response, content: T): void { const message = new Message(null, content); res.json(message); } - - } diff --git a/src/backend/middlewares/ServerTimingMWs.ts b/src/backend/middlewares/ServerTimingMWs.ts index 51cbc7cc..09f85e52 100644 --- a/src/backend/middlewares/ServerTimingMWs.ts +++ b/src/backend/middlewares/ServerTimingMWs.ts @@ -1,13 +1,11 @@ -import {NextFunction, Request, Response} from 'express'; -import {Config} from '../../common/config/private/Config'; - +import { NextFunction, Request, Response } from 'express'; +import { Config } from '../../common/config/private/Config'; export class ServerTimeEntry { public name: string; startHR: any; public endTime: number = null; - constructor(name: string) { this.name = name; } @@ -18,13 +16,16 @@ export class ServerTimeEntry { public end(): void { const duration = process.hrtime(this.startHR); - this.endTime = (duration[0] * 1E3) + (duration[1] * 1e-6); + this.endTime = duration[0] * 1e3 + duration[1] * 1e-6; } } - export const ServerTime = (id: string, name: string) => { - return (target: any, propertyName: string, descriptor: TypedPropertyDescriptor): any => { + return ( + target: any, + propertyName: string, + descriptor: TypedPropertyDescriptor + ): any => { if (Config.Server.Log.logServerTiming === false) { return; } @@ -38,27 +39,34 @@ export const ServerTime = (id: string, name: string) => { next(err); }); }; - descriptor.value = new Function('action', 'return function ' + m.name + '(...args){ action(...args) };')(customAction); - + descriptor.value = new Function( + 'action', + 'return function ' + m.name + '(...args){ action(...args) };' + )(customAction); }; }; - -const forcedDebug = process.env.NODE_ENV === 'debug'; +const forcedDebug = process.env['NODE_ENV'] === 'debug'; export class ServerTimingMWs { - - /** * Add server timing */ - public static async addServerTiming(req: Request, res: Response, next: NextFunction): Promise { - if ((Config.Server.Log.logServerTiming === false && !forcedDebug) || !req.timing) { + public static async addServerTiming( + req: Request, + res: Response, + next: NextFunction + ): Promise { + if ( + (Config.Server.Log.logServerTiming === false && !forcedDebug) || + !req.timing + ) { return next(); } - const l = Object.entries(req.timing).filter(e => e[1].endTime).map(e => `${e[0]};dur=${e[1].endTime};desc="${e[1].name}"`); + const l = Object.entries(req.timing) + .filter((e) => e[1].endTime) + .map((e) => `${e[0]};dur=${e[1].endTime};desc="${e[1].name}"`); res.header('Server-Timing', l.join(', ')); next(); } - } diff --git a/src/backend/middlewares/SharingMWs.ts b/src/backend/middlewares/SharingMWs.ts index be5697d5..632cce68 100644 --- a/src/backend/middlewares/SharingMWs.ts +++ b/src/backend/middlewares/SharingMWs.ts @@ -1,139 +1,215 @@ -import {NextFunction, Request, Response} from 'express'; -import {CreateSharingDTO, SharingDTO} from '../../common/entities/SharingDTO'; -import {ObjectManagers} from '../model/ObjectManagers'; -import {ErrorCodes, ErrorDTO} from '../../common/entities/Error'; -import {Config} from '../../common/config/private/Config'; -import {QueryParams} from '../../common/QueryParams'; +import { NextFunction, Request, Response } from 'express'; +import { CreateSharingDTO, SharingDTO } from '../../common/entities/SharingDTO'; +import { ObjectManagers } from '../model/ObjectManagers'; +import { ErrorCodes, ErrorDTO } from '../../common/entities/Error'; +import { Config } from '../../common/config/private/Config'; +import { QueryParams } from '../../common/QueryParams'; import * as path from 'path'; -import {UserRoles} from '../../common/entities/UserDTO'; - +import { UserRoles } from '../../common/entities/UserDTO'; export class SharingMWs { - - - public static async getSharing(req: Request, res: Response, next: NextFunction): Promise { + public static async getSharing( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (Config.Client.Sharing.enabled === false) { return next(); } const sharingKey = req.params[QueryParams.gallery.sharingKey_params]; try { - req.resultPipe = await ObjectManagers.getInstance().SharingManager.findOne({sharingKey}); + req.resultPipe = + await ObjectManagers.getInstance().SharingManager.findOne({ + sharingKey, + }); return next(); - } catch (err) { - return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during retrieving sharing link', err)); + return next( + new ErrorDTO( + ErrorCodes.GENERAL_ERROR, + 'Error during retrieving sharing link', + err + ) + ); } - } - public static async createSharing(req: Request, res: Response, next: NextFunction): Promise { + public static async createSharing( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (Config.Client.Sharing.enabled === false) { return next(); } - if ((typeof req.body === 'undefined') || (typeof req.body.createSharing === 'undefined')) { - return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'createSharing filed is missing')); + if ( + typeof req.body === 'undefined' || + typeof req.body.createSharing === 'undefined' + ) { + return next( + new ErrorDTO(ErrorCodes.INPUT_ERROR, 'createSharing filed is missing') + ); } const createSharing: CreateSharingDTO = req.body.createSharing; let sharingKey = SharingMWs.generateKey(); // create one not yet used + // eslint-disable-next-line no-constant-condition while (true) { try { - await ObjectManagers.getInstance().SharingManager.findOne({sharingKey}); + await ObjectManagers.getInstance().SharingManager.findOne({ + sharingKey, + }); sharingKey = this.generateKey(); } catch (err) { break; } } - - const directoryName = path.normalize(req.params.directory || '/'); + const directoryName = path.normalize(req.params['directory'] || '/'); const sharing: SharingDTO = { id: null, sharingKey, path: directoryName, password: createSharing.password, - creator: req.session.user, - expires: createSharing.valid >= 0 ? // if === -1 its forever - Date.now() + createSharing.valid : - (new Date(9999, 0, 1)).getTime(), // never expire + creator: req.session['user'], + expires: + createSharing.valid >= 0 // if === -1 its forever + ? Date.now() + createSharing.valid + : new Date(9999, 0, 1).getTime(), // never expire includeSubfolders: createSharing.includeSubfolders, - timeStamp: Date.now() + timeStamp: Date.now(), }; try { - - req.resultPipe = await ObjectManagers.getInstance().SharingManager.createSharing(sharing); + req.resultPipe = + await ObjectManagers.getInstance().SharingManager.createSharing( + sharing + ); return next(); - } catch (err) { console.warn(err); - return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during creating sharing link', err)); + return next( + new ErrorDTO( + ErrorCodes.GENERAL_ERROR, + 'Error during creating sharing link', + err + ) + ); } } - public static async updateSharing(req: Request, res: Response, next: NextFunction): Promise { + public static async updateSharing( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (Config.Client.Sharing.enabled === false) { return next(); } - if ((typeof req.body === 'undefined') || (typeof req.body.updateSharing === 'undefined')) { - return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'updateSharing filed is missing')); + if ( + typeof req.body === 'undefined' || + typeof req.body.updateSharing === 'undefined' + ) { + return next( + new ErrorDTO(ErrorCodes.INPUT_ERROR, 'updateSharing filed is missing') + ); } const updateSharing: CreateSharingDTO = req.body.updateSharing; - const directoryName = path.normalize(req.params.directory || '/'); + const directoryName = path.normalize(req.params['directory'] || '/'); const sharing: SharingDTO = { id: updateSharing.id, path: directoryName, sharingKey: '', - password: (updateSharing.password && updateSharing.password !== '') ? updateSharing.password : null, - creator: req.session.user, - expires: updateSharing.valid >= 0 // if === -1 its forever - ? Date.now() + updateSharing.valid : - (new Date(9999, 0, 1)).getTime(), // never expire + password: + updateSharing.password && updateSharing.password !== '' + ? updateSharing.password + : null, + creator: req.session['user'], + expires: + updateSharing.valid >= 0 // if === -1 its forever + ? Date.now() + updateSharing.valid + : new Date(9999, 0, 1).getTime(), // never expire includeSubfolders: updateSharing.includeSubfolders, - timeStamp: Date.now() + timeStamp: Date.now(), }; try { - const forceUpdate = req.session.user.role >= UserRoles.Admin; - req.resultPipe = await ObjectManagers.getInstance().SharingManager.updateSharing(sharing, forceUpdate); + const forceUpdate = req.session['user'].role >= UserRoles.Admin; + req.resultPipe = + await ObjectManagers.getInstance().SharingManager.updateSharing( + sharing, + forceUpdate + ); return next(); } catch (err) { - return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during updating sharing link', err)); + return next( + new ErrorDTO( + ErrorCodes.GENERAL_ERROR, + 'Error during updating sharing link', + err + ) + ); } - } - - public static async deleteSharing(req: Request, res: Response, next: NextFunction): Promise { + public static async deleteSharing( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (Config.Client.Sharing.enabled === false) { return next(); } - if ((typeof req.params === 'undefined') || (typeof req.params.sharingKey === 'undefined')) { - return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'sharingKey is missing')); + if ( + typeof req.params === 'undefined' || + typeof req.params['sharingKey'] === 'undefined' + ) { + return next( + new ErrorDTO(ErrorCodes.INPUT_ERROR, 'sharingKey is missing') + ); } - const sharingKey: string = req.params.sharingKey; + const sharingKey: string = req.params['sharingKey']; try { - req.resultPipe = await ObjectManagers.getInstance().SharingManager.deleteSharing(sharingKey); + req.resultPipe = + await ObjectManagers.getInstance().SharingManager.deleteSharing( + sharingKey + ); req.resultPipe = 'ok'; return next(); } catch (err) { - return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during deleting sharing', err)); + return next( + new ErrorDTO( + ErrorCodes.GENERAL_ERROR, + 'Error during deleting sharing', + err + ) + ); } - } - public static async listSharing(req: Request, res: Response, next: NextFunction): Promise { + public static async listSharing( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (Config.Client.Sharing.enabled === false) { return next(); } try { - req.resultPipe = await ObjectManagers.getInstance().SharingManager.listAll(); + req.resultPipe = + await ObjectManagers.getInstance().SharingManager.listAll(); return next(); } catch (err) { - return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error during listing shares', err)); + return next( + new ErrorDTO( + ErrorCodes.GENERAL_ERROR, + 'Error during listing shares', + err + ) + ); } } diff --git a/src/backend/middlewares/VersionMWs.ts b/src/backend/middlewares/VersionMWs.ts index 956df414..12d39b64 100644 --- a/src/backend/middlewares/VersionMWs.ts +++ b/src/backend/middlewares/VersionMWs.ts @@ -1,22 +1,31 @@ -import {NextFunction, Request, Response} from 'express'; -import {ObjectManagers} from '../model/ObjectManagers'; -import {ErrorCodes, ErrorDTO} from '../../common/entities/Error'; -import {CustomHeaders} from '../../common/CustomHeaders'; - +import { NextFunction, Request, Response } from 'express'; +import { ObjectManagers } from '../model/ObjectManagers'; +import { ErrorCodes, ErrorDTO } from '../../common/entities/Error'; +import { CustomHeaders } from '../../common/CustomHeaders'; export class VersionMWs { - - /** * This version data is mainly used on the client side to invalidate the cache */ - public static async injectGalleryVersion(req: Request, res: Response, next: NextFunction): Promise { + public static async injectGalleryVersion( + req: Request, + res: Response, + next: NextFunction + ): Promise { try { - res.header(CustomHeaders.dataVersion, await ObjectManagers.getInstance().VersionManager.getDataVersion()); + res.header( + CustomHeaders.dataVersion, + await ObjectManagers.getInstance().VersionManager.getDataVersion() + ); next(); } catch (err) { - return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Can not get data version', err.toString())); + return next( + new ErrorDTO( + ErrorCodes.GENERAL_ERROR, + 'Can not get data version', + err.toString() + ) + ); } } - } diff --git a/src/backend/middlewares/admin/AdminMWs.ts b/src/backend/middlewares/admin/AdminMWs.ts index 56e7d4af..cac5704d 100644 --- a/src/backend/middlewares/admin/AdminMWs.ts +++ b/src/backend/middlewares/admin/AdminMWs.ts @@ -1,23 +1,31 @@ -import {NextFunction, Request, Response} from 'express'; -import {ErrorCodes, ErrorDTO} from '../../../common/entities/Error'; -import {ObjectManagers} from '../../model/ObjectManagers'; -import {Config} from '../../../common/config/private/Config'; -import {ISQLGalleryManager} from '../../model/database/sql/IGalleryManager'; -import {DatabaseType, ServerConfig} from '../../../common/config/private/PrivateConfig'; -import {ISQLPersonManager} from '../../model/database/sql/IPersonManager'; -import {StatisticDTO} from '../../../common/entities/settings/StatisticDTO'; - +import { NextFunction, Request, Response } from 'express'; +import { ErrorCodes, ErrorDTO } from '../../../common/entities/Error'; +import { ObjectManagers } from '../../model/ObjectManagers'; +import { Config } from '../../../common/config/private/Config'; +import { ISQLGalleryManager } from '../../model/database/sql/IGalleryManager'; +import { DatabaseType } from '../../../common/config/private/PrivateConfig'; +import { ISQLPersonManager } from '../../model/database/sql/IPersonManager'; +import { StatisticDTO } from '../../../common/entities/settings/StatisticDTO'; export class AdminMWs { - - public static async loadStatistic(req: Request, res: Response, next: NextFunction): Promise { + public static async loadStatistic( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (Config.Server.Database.type === DatabaseType.memory) { - return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Statistic is only available for indexed content')); + return next( + new ErrorDTO( + ErrorCodes.GENERAL_ERROR, + 'Statistic is only available for indexed content' + ) + ); } - - const galleryManager = ObjectManagers.getInstance().GalleryManager as ISQLGalleryManager; - const personManager = ObjectManagers.getInstance().PersonManager as ISQLPersonManager; + const galleryManager = ObjectManagers.getInstance() + .GalleryManager as ISQLGalleryManager; + const personManager = ObjectManagers.getInstance() + .PersonManager as ISQLPersonManager; try { req.resultPipe = { directories: await galleryManager.countDirectories(), @@ -29,83 +37,181 @@ export class AdminMWs { return next(); } catch (err) { if (err instanceof Error) { - return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error while getting statistic: ' + err.toString(), err)); + return next( + new ErrorDTO( + ErrorCodes.GENERAL_ERROR, + 'Error while getting statistic: ' + err.toString(), + err + ) + ); } - return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error while getting statistic', err)); + return next( + new ErrorDTO( + ErrorCodes.GENERAL_ERROR, + 'Error while getting statistic', + err + ) + ); } } - public static async getDuplicates(req: Request, res: Response, next: NextFunction): Promise { + public static async getDuplicates( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (Config.Server.Database.type === DatabaseType.memory) { - return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Statistic is only available for indexed content')); + return next( + new ErrorDTO( + ErrorCodes.GENERAL_ERROR, + 'Statistic is only available for indexed content' + ) + ); } - - const galleryManager = ObjectManagers.getInstance().GalleryManager as ISQLGalleryManager; + const galleryManager = ObjectManagers.getInstance() + .GalleryManager as ISQLGalleryManager; try { req.resultPipe = await galleryManager.getPossibleDuplicates(); return next(); } catch (err) { if (err instanceof Error) { - return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error while getting duplicates: ' + err.toString(), err)); + return next( + new ErrorDTO( + ErrorCodes.GENERAL_ERROR, + 'Error while getting duplicates: ' + err.toString(), + err + ) + ); } - return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, 'Error while getting duplicates', err)); + return next( + new ErrorDTO( + ErrorCodes.GENERAL_ERROR, + 'Error while getting duplicates', + err + ) + ); } } - - public static async startJob(req: Request, res: Response, next: NextFunction): Promise { + public static async startJob( + req: Request, + res: Response, + next: NextFunction + ): Promise { try { - const id = req.params.id; + const id = req.params['id']; const JobConfig: any = req.body.config; const soloRun: boolean = req.body.soloRun; const allowParallelRun: boolean = req.body.allowParallelRun; - await ObjectManagers.getInstance().JobManager.run(id, JobConfig, soloRun, allowParallelRun); + await ObjectManagers.getInstance().JobManager.run( + id, + JobConfig, + soloRun, + allowParallelRun + ); req.resultPipe = 'ok'; return next(); } catch (err) { if (err instanceof Error) { - return next(new ErrorDTO(ErrorCodes.JOB_ERROR, 'Job error: ' + err.toString(), err)); + return next( + new ErrorDTO( + ErrorCodes.JOB_ERROR, + 'Job error: ' + err.toString(), + err + ) + ); } - return next(new ErrorDTO(ErrorCodes.JOB_ERROR, 'Job error: ' + JSON.stringify(err, null, ' '), err)); + return next( + new ErrorDTO( + ErrorCodes.JOB_ERROR, + 'Job error: ' + JSON.stringify(err, null, ' '), + err + ) + ); } } public static stopJob(req: Request, res: Response, next: NextFunction): void { try { - const id = req.params.id; + const id = req.params['id']; ObjectManagers.getInstance().JobManager.stop(id); req.resultPipe = 'ok'; return next(); } catch (err) { if (err instanceof Error) { - return next(new ErrorDTO(ErrorCodes.JOB_ERROR, 'Job error: ' + err.toString(), err)); + return next( + new ErrorDTO( + ErrorCodes.JOB_ERROR, + 'Job error: ' + err.toString(), + err + ) + ); } - return next(new ErrorDTO(ErrorCodes.JOB_ERROR, 'Job error: ' + JSON.stringify(err, null, ' '), err)); + return next( + new ErrorDTO( + ErrorCodes.JOB_ERROR, + 'Job error: ' + JSON.stringify(err, null, ' '), + err + ) + ); } } - public static getAvailableJobs(req: Request, res: Response, next: NextFunction): void { + public static getAvailableJobs( + req: Request, + res: Response, + next: NextFunction + ): void { try { - req.resultPipe = ObjectManagers.getInstance().JobManager.getAvailableJobs(); + req.resultPipe = + ObjectManagers.getInstance().JobManager.getAvailableJobs(); return next(); } catch (err) { if (err instanceof Error) { - return next(new ErrorDTO(ErrorCodes.JOB_ERROR, 'Job error: ' + err.toString(), err)); + return next( + new ErrorDTO( + ErrorCodes.JOB_ERROR, + 'Job error: ' + err.toString(), + err + ) + ); } - return next(new ErrorDTO(ErrorCodes.JOB_ERROR, 'Job error: ' + JSON.stringify(err, null, ' '), err)); + return next( + new ErrorDTO( + ErrorCodes.JOB_ERROR, + 'Job error: ' + JSON.stringify(err, null, ' '), + err + ) + ); } } - public static getJobProgresses(req: Request, res: Response, next: NextFunction): void { + public static getJobProgresses( + req: Request, + res: Response, + next: NextFunction + ): void { try { req.resultPipe = ObjectManagers.getInstance().JobManager.getProgresses(); return next(); } catch (err) { if (err instanceof Error) { - return next(new ErrorDTO(ErrorCodes.JOB_ERROR, 'Job error: ' + err.toString(), err)); + return next( + new ErrorDTO( + ErrorCodes.JOB_ERROR, + 'Job error: ' + err.toString(), + err + ) + ); } - return next(new ErrorDTO(ErrorCodes.JOB_ERROR, 'Job error: ' + JSON.stringify(err, null, ' '), err)); + return next( + new ErrorDTO( + ErrorCodes.JOB_ERROR, + 'Job error: ' + JSON.stringify(err, null, ' '), + err + ) + ); } } } diff --git a/src/backend/middlewares/customtypings/ExtendedRequest.d.ts b/src/backend/middlewares/customtypings/ExtendedRequest.d.ts index 69da02dc..00975abb 100644 --- a/src/backend/middlewares/customtypings/ExtendedRequest.d.ts +++ b/src/backend/middlewares/customtypings/ExtendedRequest.d.ts @@ -1,14 +1,12 @@ -import {LoginCredential} from '../../../common/entities/LoginCredential'; -import {UserDTO} from '../../../common/entities/UserDTO'; - +import { LoginCredential } from '../../../common/entities/LoginCredential'; +import { UserDTO } from '../../../common/entities/UserDTO'; declare global { namespace Express { interface Request { - resultPipe?: any; body?: { - loginCredential?: LoginCredential + loginCredential?: LoginCredential; }; locale?: string; } diff --git a/src/backend/middlewares/thumbnail/PhotoConverterMWs.ts b/src/backend/middlewares/thumbnail/PhotoConverterMWs.ts index 1cb92c4e..c1053ae3 100644 --- a/src/backend/middlewares/thumbnail/PhotoConverterMWs.ts +++ b/src/backend/middlewares/thumbnail/PhotoConverterMWs.ts @@ -1,11 +1,14 @@ -import {NextFunction, Request, Response} from 'express'; +import { NextFunction, Request, Response } from 'express'; import * as fs from 'fs'; -import {PhotoProcessing} from '../../model/fileprocessing/PhotoProcessing'; -import {Config} from '../../../common/config/private/Config'; +import { PhotoProcessing } from '../../model/fileprocessing/PhotoProcessing'; +import { Config } from '../../../common/config/private/Config'; export class PhotoConverterMWs { - - public static async convertPhoto(req: Request, res: Response, next: NextFunction): Promise { + public static async convertPhoto( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (!req.resultPipe) { return next(); } @@ -15,7 +18,10 @@ export class PhotoConverterMWs { } const fullMediaPath = req.resultPipe; - const convertedVideo = PhotoProcessing.generateConvertedPath(fullMediaPath, Config.Server.Media.Photo.Converting.resolution); + const convertedVideo = PhotoProcessing.generateConvertedPath( + fullMediaPath, + Config.Server.Media.Photo.Converting.resolution + ); // check if converted photo exist if (fs.existsSync(convertedVideo) === true) { diff --git a/src/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts b/src/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts index e6ff49cc..e0521e54 100644 --- a/src/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts +++ b/src/backend/middlewares/thumbnail/ThumbnailGeneratorMWs.ts @@ -1,23 +1,30 @@ import * as path from 'path'; import * as fs from 'fs'; -import {NextFunction, Request, Response} from 'express'; -import {ErrorCodes, ErrorDTO} from '../../../common/entities/Error'; -import {ContentWrapper} from '../../../common/entities/ConentWrapper'; -import {ParentDirectoryDTO, SubDirectoryDTO} from '../../../common/entities/DirectoryDTO'; -import {ProjectPath} from '../../ProjectPath'; -import {Config} from '../../../common/config/private/Config'; -import {ThumbnailSourceType} from '../../model/threading/PhotoWorker'; -import {MediaDTO} from '../../../common/entities/MediaDTO'; -import {PhotoProcessing} from '../../model/fileprocessing/PhotoProcessing'; -import {PersonWithSampleRegion} from '../../../common/entities/PersonDTO'; -import {ServerTime} from '../ServerTimingMWs'; - +import { NextFunction, Request, Response } from 'express'; +import { ErrorCodes, ErrorDTO } from '../../../common/entities/Error'; +import { ContentWrapper } from '../../../common/entities/ConentWrapper'; +import { + ParentDirectoryDTO, + SubDirectoryDTO, +} from '../../../common/entities/DirectoryDTO'; +import { ProjectPath } from '../../ProjectPath'; +import { Config } from '../../../common/config/private/Config'; +import { ThumbnailSourceType } from '../../model/threading/PhotoWorker'; +import { MediaDTO } from '../../../common/entities/MediaDTO'; +import { PhotoProcessing } from '../../model/fileprocessing/PhotoProcessing'; +import { PersonWithSampleRegion } from '../../../common/entities/PersonDTO'; +import { ServerTime } from '../ServerTimingMWs'; export class ThumbnailGeneratorMWs { - private static ThumbnailMap: { [key: number]: number } = Config.Client.Media.Thumbnail.generateThumbnailMap(); + private static ThumbnailMap: { [key: number]: number } = + Config.Client.Media.Thumbnail.generateThumbnailMap(); @ServerTime('2.th', 'Thumbnail decoration') - public static async addThumbnailInformation(req: Request, res: Response, next: NextFunction): Promise { + public static async addThumbnailInformation( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (!req.resultPipe) { return next(); } @@ -33,20 +40,26 @@ export class ThumbnailGeneratorMWs { if (cw.searchResult && cw.searchResult.media) { ThumbnailGeneratorMWs.addThInfoToPhotos(cw.searchResult.media); } - } catch (error) { console.error(error); - return next(new ErrorDTO(ErrorCodes.SERVER_ERROR, 'error during postprocessing result (adding thumbnail info)', error.toString())); - + return next( + new ErrorDTO( + ErrorCodes.SERVER_ERROR, + 'error during postprocessing result (adding thumbnail info)', + error.toString() + ) + ); } return next(); - } - - // tslint:disable-next-line:typedef - public static addThumbnailInfoForPersons(req: Request, res: Response, next: NextFunction): void { + // eslint-disable-next-line @typescript-eslint/typedef, @typescript-eslint/explicit-function-return-type, @typescript-eslint/explicit-module-boundary-types + public static addThumbnailInfoForPersons( + req: Request, + res: Response, + next: NextFunction + ): void { if (!req.resultPipe) { return next(); } @@ -61,28 +74,40 @@ export class ThumbnailGeneratorMWs { continue; } // load parameters - const mediaPath = path.join(ProjectPath.ImageFolder, + const mediaPath = path.join( + ProjectPath.ImageFolder, item.sampleRegion.media.directory.path, - item.sampleRegion.media.directory.name, item.sampleRegion.media.name); + item.sampleRegion.media.directory.name, + item.sampleRegion.media.name + ); // generate thumbnail path - const thPath = PhotoProcessing.generatePersonThumbnailPath(mediaPath, item.sampleRegion, size); + const thPath = PhotoProcessing.generatePersonThumbnailPath( + mediaPath, + item.sampleRegion, + size + ); item.missingThumbnail = !fs.existsSync(thPath); } - } catch (error) { - return next(new ErrorDTO(ErrorCodes.SERVER_ERROR, 'error during postprocessing result (adding thumbnail info for persons)', - error.toString())); - + return next( + new ErrorDTO( + ErrorCodes.SERVER_ERROR, + 'error during postprocessing result (adding thumbnail info for persons)', + error.toString() + ) + ); } return next(); - } - - public static async generatePersonThumbnail(req: Request, res: Response, next: NextFunction): Promise { + public static async generatePersonThumbnail( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (!req.resultPipe) { return next(); } @@ -92,43 +117,67 @@ export class ThumbnailGeneratorMWs { return next(); } catch (error) { console.error(error); - return next(new ErrorDTO(ErrorCodes.THUMBNAIL_GENERATION_ERROR, - 'Error during generating face thumbnail: ' + person.name, error.toString())); + return next( + new ErrorDTO( + ErrorCodes.THUMBNAIL_GENERATION_ERROR, + 'Error during generating face thumbnail: ' + person.name, + error.toString() + ) + ); } - } - - public static generateThumbnailFactory(sourceType: ThumbnailSourceType): - (req: Request, res: Response, next: NextFunction) => Promise { - return async (req: Request, res: Response, next: NextFunction): Promise => { + public static generateThumbnailFactory( + sourceType: ThumbnailSourceType + ): (req: Request, res: Response, next: NextFunction) => Promise { + return async ( + req: Request, + res: Response, + next: NextFunction + ): Promise => { if (!req.resultPipe) { return next(); } // load parameters const mediaPath = req.resultPipe; - let size: number = parseInt(req.params.size, 10) || Config.Client.Media.Thumbnail.thumbnailSizes[0]; + let size: number = + parseInt(req.params.size, 10) || + Config.Client.Media.Thumbnail.thumbnailSizes[0]; // validate size if (Config.Client.Media.Thumbnail.thumbnailSizes.indexOf(size) === -1) { size = Config.Client.Media.Thumbnail.thumbnailSizes[0]; } - try { - req.resultPipe = await PhotoProcessing.generateThumbnail(mediaPath, size, sourceType, false); + req.resultPipe = await PhotoProcessing.generateThumbnail( + mediaPath, + size, + sourceType, + false + ); return next(); } catch (error) { - return next(new ErrorDTO(ErrorCodes.THUMBNAIL_GENERATION_ERROR, - 'Error during generating thumbnail: ' + mediaPath, error.toString())); + return next( + new ErrorDTO( + ErrorCodes.THUMBNAIL_GENERATION_ERROR, + 'Error during generating thumbnail: ' + mediaPath, + error.toString() + ) + ); } }; } - public static generateIconFactory(sourceType: ThumbnailSourceType): - (req: Request, res: Response, next: NextFunction) => Promise { - return async (req: Request, res: Response, next: NextFunction): Promise => { + public static generateIconFactory( + sourceType: ThumbnailSourceType + ): (req: Request, res: Response, next: NextFunction) => Promise { + return async ( + req: Request, + res: Response, + next: NextFunction + ): Promise => { if (!req.resultPipe) { return next(); } @@ -138,18 +187,30 @@ export class ThumbnailGeneratorMWs { const size: number = Config.Client.Media.Thumbnail.iconSize; try { - req.resultPipe = await PhotoProcessing.generateThumbnail(mediaPath, size, sourceType, true); + req.resultPipe = await PhotoProcessing.generateThumbnail( + mediaPath, + size, + sourceType, + true + ); return next(); } catch (error) { - return next(new ErrorDTO(ErrorCodes.THUMBNAIL_GENERATION_ERROR, - 'Error during generating thumbnail: ' + mediaPath, error.toString())); + return next( + new ErrorDTO( + ErrorCodes.THUMBNAIL_GENERATION_ERROR, + 'Error during generating thumbnail: ' + mediaPath, + error.toString() + ) + ); } }; } - - private static addThInfoTODir(directory: ParentDirectoryDTO | SubDirectoryDTO): void { - ThumbnailGeneratorMWs.ThumbnailMap = Config.Client.Media.Thumbnail.generateThumbnailMap(); + private static addThInfoTODir( + directory: ParentDirectoryDTO | SubDirectoryDTO + ): void { + ThumbnailGeneratorMWs.ThumbnailMap = + Config.Client.Media.Thumbnail.generateThumbnailMap(); if (typeof directory.media !== 'undefined') { ThumbnailGeneratorMWs.addThInfoToPhotos(directory.media); } @@ -165,18 +226,26 @@ export class ThumbnailGeneratorMWs { } private static addThInfoToAPhoto(photo: MediaDTO): void { - const fullMediaPath = path.join(ProjectPath.ImageFolder, photo.directory.path, photo.directory.name, photo.name); + const fullMediaPath = path.join( + ProjectPath.ImageFolder, + photo.directory.path, + photo.directory.name, + photo.name + ); for (const size of Object.keys(ThumbnailGeneratorMWs.ThumbnailMap)) { - const thPath = PhotoProcessing.generateConvertedPath(fullMediaPath, size as any); + const thPath = PhotoProcessing.generateConvertedPath( + fullMediaPath, + size as any + ); if (fs.existsSync(thPath) !== true) { if (typeof photo.missingThumbnails === 'undefined') { photo.missingThumbnails = 0; } // this is a bitwise operation - photo.missingThumbnails += ThumbnailGeneratorMWs.ThumbnailMap[size as any]; + photo.missingThumbnails += + ThumbnailGeneratorMWs.ThumbnailMap[size as any]; } } } - } diff --git a/src/backend/middlewares/user/AuthenticationMWs.ts b/src/backend/middlewares/user/AuthenticationMWs.ts index 92e9567b..c5ab7723 100644 --- a/src/backend/middlewares/user/AuthenticationMWs.ts +++ b/src/backend/middlewares/user/AuthenticationMWs.ts @@ -1,82 +1,109 @@ -import {NextFunction, Request, Response} from 'express'; -import {ErrorCodes, ErrorDTO} from '../../../common/entities/Error'; -import {UserDTO, UserDTOUtils, UserRoles} from '../../../common/entities/UserDTO'; -import {ObjectManagers} from '../../model/ObjectManagers'; -import {Config} from '../../../common/config/private/Config'; -import {PasswordHelper} from '../../model/PasswordHelper'; -import {Utils} from '../../../common/Utils'; -import {QueryParams} from '../../../common/QueryParams'; +import { NextFunction, Request, Response } from 'express'; +import { ErrorCodes, ErrorDTO } from '../../../common/entities/Error'; +import { + UserDTO, + UserDTOUtils, + UserRoles, +} from '../../../common/entities/UserDTO'; +import { ObjectManagers } from '../../model/ObjectManagers'; +import { Config } from '../../../common/config/private/Config'; +import { PasswordHelper } from '../../model/PasswordHelper'; +import { Utils } from '../../../common/Utils'; +import { QueryParams } from '../../../common/QueryParams'; import * as path from 'path'; export class AuthenticationMWs { - - public static async tryAuthenticate(req: Request, res: Response, next: NextFunction): Promise { + public static async tryAuthenticate( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (Config.Client.authenticationRequired === false) { - req.session.user = { + req.session['user'] = { name: UserRoles[Config.Client.unAuthenticatedUserRole], - role: Config.Client.unAuthenticatedUserRole + role: Config.Client.unAuthenticatedUserRole, } as UserDTO; return next(); } try { const user = await AuthenticationMWs.getSharingUser(req); if (!!user) { - req.session.user = user; + req.session['user'] = user; return next(); } - } catch (err) { - } + } catch (err) {} return next(); - } - public static async authenticate(req: Request, res: Response, next: NextFunction): Promise { + public static async authenticate( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (Config.Client.authenticationRequired === false) { - req.session.user = { + req.session['user'] = { name: UserRoles[Config.Client.unAuthenticatedUserRole], - role: Config.Client.unAuthenticatedUserRole + role: Config.Client.unAuthenticatedUserRole, } as UserDTO; return next(); } // if already authenticated, do not try to use sharing authentication - if (typeof req.session.user !== 'undefined') { + if (typeof req.session['user'] !== 'undefined') { return next(); } try { const user = await AuthenticationMWs.getSharingUser(req); if (!!user) { - req.session.user = user; + req.session['user'] = user; return next(); } } catch (err) { return next(new ErrorDTO(ErrorCodes.CREDENTIAL_NOT_FOUND, null, err)); } - if (typeof req.session.user === 'undefined') { + if (typeof req.session['user'] === 'undefined') { res.status(401); - return next(new ErrorDTO(ErrorCodes.NOT_AUTHENTICATED, 'Not authenticated')); + return next( + new ErrorDTO(ErrorCodes.NOT_AUTHENTICATED, 'Not authenticated') + ); } return next(); } - - public static normalizePathParam(paramName: string): (req: Request, res: Response, next: NextFunction) => void { - return function normalizePathParam(req: Request, res: Response, next: NextFunction): void { - req.params[paramName] = path.normalize(req.params[paramName] || path.sep).replace(/^(\.\.[\/\\])+/, ''); + public static normalizePathParam( + paramName: string + ): (req: Request, res: Response, next: NextFunction) => void { + return function normalizePathParam( + req: Request, + res: Response, + next: NextFunction + ): void { + req.params[paramName] = path + .normalize(req.params[paramName] || path.sep) + .replace(/^(\.\.[\/\\])+/, ''); return next(); }; } - public static authorisePath(paramName: string, isDirectory: boolean): (req: Request, res: Response, next: NextFunction) => void { - return function authorisePath(req: Request, res: Response, next: NextFunction): Response | void { + public static authorisePath( + paramName: string, + isDirectory: boolean + ): (req: Request, res: Response, next: NextFunction) => void { + return function authorisePath( + req: Request, + res: Response, + next: NextFunction + ): Response | void { let p: string = req.params[paramName]; if (!isDirectory) { p = path.dirname(p); } - if (!UserDTOUtils.isDirectoryPathAvailable(p, req.session.user.permissions)) { + if ( + !UserDTOUtils.isDirectoryPathAvailable(p, req.session['user'].permissions) + ) { return res.sendStatus(403); } @@ -84,38 +111,57 @@ export class AuthenticationMWs { }; } - - public static authorise(role: UserRoles): (req: Request, res: Response, next: NextFunction) => void { - return function authorise(req: Request, res: Response, next: NextFunction): void { - if (req.session.user.role < role) { + public static authorise( + role: UserRoles + ): (req: Request, res: Response, next: NextFunction) => void { + return function authorise( + req: Request, + res: Response, + next: NextFunction + ): void { + if (req.session['user'].role < role) { return next(new ErrorDTO(ErrorCodes.NOT_AUTHORISED)); } return next(); }; } - public static async shareLogin(req: Request, res: Response, next: NextFunction): Promise { - + public static async shareLogin( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (Config.Client.Sharing.enabled === false) { return next(); } // not enough parameter - if ((!req.query[QueryParams.gallery.sharingKey_query] && !req.params[QueryParams.gallery.sharingKey_params])) { - return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'no sharing key provided')); + if ( + !req.query[QueryParams.gallery.sharingKey_query] && + !req.params[QueryParams.gallery.sharingKey_params] + ) { + return next( + new ErrorDTO(ErrorCodes.INPUT_ERROR, 'no sharing key provided') + ); } try { const password = (req.body ? req.body.password : null) || null; - const sharingKey: string = req.query[QueryParams.gallery.sharingKey_query] as string || - req.params[QueryParams.gallery.sharingKey_params] as string; - const sharing = await ObjectManagers.getInstance().SharingManager.findOne({ - sharingKey - }); + const sharingKey: string = + (req.query[QueryParams.gallery.sharingKey_query] as string) || + (req.params[QueryParams.gallery.sharingKey_params] as string); + const sharing = await ObjectManagers.getInstance().SharingManager.findOne( + { + sharingKey, + } + ); - if (!sharing || sharing.expires < Date.now() || - (Config.Client.Sharing.passwordProtected === true - && (sharing.password) - && !PasswordHelper.comparePassword(password, sharing.password))) { + if ( + !sharing || + sharing.expires < Date.now() || + (Config.Client.Sharing.passwordProtected === true && + sharing.password && + !PasswordHelper.comparePassword(password, sharing.password)) + ) { res.status(401); return next(new ErrorDTO(ErrorCodes.CREDENTIAL_NOT_FOUND)); } @@ -125,78 +171,106 @@ export class AuthenticationMWs { sharingPath += '*'; } - req.session.user = { + req.session['user'] = { name: 'Guest', role: UserRoles.LimitedGuest, permissions: [sharingPath], - usedSharingKey: sharing.sharingKey + usedSharingKey: sharing.sharingKey, } as UserDTO; return next(); - } catch (err) { return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, null, err)); } - } - public static inverseAuthenticate(req: Request, res: Response, next: NextFunction): void { - if (typeof req.session.user !== 'undefined') { + public static inverseAuthenticate( + req: Request, + res: Response, + next: NextFunction + ): void { + if (typeof req.session['user'] !== 'undefined') { return next(new ErrorDTO(ErrorCodes.ALREADY_AUTHENTICATED)); } return next(); } - public static async login(req: Request, res: Response, next: NextFunction): Promise { - + public static async login( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (Config.Client.authenticationRequired === false) { return res.sendStatus(404); } // not enough parameter - if ((typeof req.body === 'undefined') || - (typeof req.body.loginCredential === 'undefined') || - (typeof req.body.loginCredential.username === 'undefined') || - (typeof req.body.loginCredential.password === 'undefined')) { - return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'not all parameters are included for loginCredential')); + if ( + typeof req.body === 'undefined' || + typeof req.body.loginCredential === 'undefined' || + typeof req.body.loginCredential.username === 'undefined' || + typeof req.body.loginCredential.password === 'undefined' + ) { + return next( + new ErrorDTO( + ErrorCodes.INPUT_ERROR, + 'not all parameters are included for loginCredential' + ) + ); } try { // lets find the user - const user = Utils.clone(await ObjectManagers.getInstance().UserManager.findOne({ - name: req.body.loginCredential.username, - password: req.body.loginCredential.password - })); - delete (user.password); - req.session.user = user; + const user = Utils.clone( + await ObjectManagers.getInstance().UserManager.findOne({ + name: req.body.loginCredential.username, + password: req.body.loginCredential.password, + }) + ); + delete user.password; + req.session['user'] = user; if (req.body.loginCredential.rememberMe) { - req.sessionOptions.expires = new Date(Date.now() + Config.Server.sessionTimeout); + req.sessionOptions.expires = new Date( + Date.now() + Config.Server.sessionTimeout + ); } return next(); - } catch (err) { - return next(new ErrorDTO(ErrorCodes.CREDENTIAL_NOT_FOUND, 'credentials not found during login', err)); + return next( + new ErrorDTO( + ErrorCodes.CREDENTIAL_NOT_FOUND, + 'credentials not found during login', + err + ) + ); } - - } public static logout(req: Request, res: Response, next: NextFunction): void { - delete req.session.user; + delete req.session['user']; return next(); } private static async getSharingUser(req: Request): Promise { - if (Config.Client.Sharing.enabled === true && - (!!req.query[QueryParams.gallery.sharingKey_query] || !!req.params[QueryParams.gallery.sharingKey_params])) { - const sharingKey: string = req.query[QueryParams.gallery.sharingKey_query] as string || - req.params[QueryParams.gallery.sharingKey_params] as string; - const sharing = await ObjectManagers.getInstance().SharingManager.findOne({ - sharingKey - }); + if ( + Config.Client.Sharing.enabled === true && + (!!req.query[QueryParams.gallery.sharingKey_query] || + !!req.params[QueryParams.gallery.sharingKey_params]) + ) { + const sharingKey: string = + (req.query[QueryParams.gallery.sharingKey_query] as string) || + (req.params[QueryParams.gallery.sharingKey_params] as string); + const sharing = await ObjectManagers.getInstance().SharingManager.findOne( + { + sharingKey, + } + ); if (!sharing || sharing.expires < Date.now()) { return null; } - if (Config.Client.Sharing.passwordProtected === true && sharing.password) { + if ( + Config.Client.Sharing.passwordProtected === true && + sharing.password + ) { return null; } @@ -208,11 +282,9 @@ export class AuthenticationMWs { name: 'Guest', role: UserRoles.LimitedGuest, permissions: [sharingPath], - usedSharingKey: sharing.sharingKey + usedSharingKey: sharing.sharingKey, } as UserDTO; - } return null; } - } diff --git a/src/backend/middlewares/user/UserMWs.ts b/src/backend/middlewares/user/UserMWs.ts index 3a4cee80..675664b0 100644 --- a/src/backend/middlewares/user/UserMWs.ts +++ b/src/backend/middlewares/user/UserMWs.ts @@ -1,89 +1,121 @@ -import {NextFunction, Request, Response} from 'express'; -import {ErrorCodes, ErrorDTO} from '../../../common/entities/Error'; -import {ObjectManagers} from '../../model/ObjectManagers'; -import {Utils} from '../../../common/Utils'; -import {Config} from '../../../common/config/private/Config'; +import { NextFunction, Request, Response } from 'express'; +import { ErrorCodes, ErrorDTO } from '../../../common/entities/Error'; +import { ObjectManagers } from '../../model/ObjectManagers'; +import { Utils } from '../../../common/Utils'; +import { Config } from '../../../common/config/private/Config'; export class UserMWs { - - public static async changePassword(req: Request, res: Response, next: NextFunction): Promise { + public static async changePassword( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (Config.Client.authenticationRequired === false) { return next(new ErrorDTO(ErrorCodes.USER_MANAGEMENT_DISABLED)); } - if ((typeof req.body === 'undefined') || (typeof req.body.userModReq === 'undefined') - || (typeof req.body.userModReq.id === 'undefined') - || (typeof req.body.userModReq.oldPassword === 'undefined') - || (typeof req.body.userModReq.newPassword === 'undefined')) { + if ( + typeof req.body === 'undefined' || + typeof req.body.userModReq === 'undefined' || + typeof req.body.userModReq.id === 'undefined' || + typeof req.body.userModReq.oldPassword === 'undefined' || + typeof req.body.userModReq.newPassword === 'undefined' + ) { return next(); } try { - await ObjectManagers.getInstance().UserManager.changePassword(req.body.userModReq); + await ObjectManagers.getInstance().UserManager.changePassword( + req.body.userModReq + ); return next(); - } catch (err) { return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, null, err)); } } - - public static async createUser(req: Request, res: Response, next: NextFunction): Promise { + public static async createUser( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (Config.Client.authenticationRequired === false) { return next(new ErrorDTO(ErrorCodes.USER_MANAGEMENT_DISABLED)); } - if ((typeof req.body === 'undefined') || (typeof req.body.newUser === 'undefined')) { + if ( + typeof req.body === 'undefined' || + typeof req.body.newUser === 'undefined' + ) { return next(); } try { - await ObjectManagers.getInstance().UserManager.createUser(req.body.newUser); + await ObjectManagers.getInstance().UserManager.createUser( + req.body.newUser + ); return next(); - } catch (err) { return next(new ErrorDTO(ErrorCodes.USER_CREATION_ERROR, null, err)); } - - } - public static async deleteUser(req: Request, res: Response, next: NextFunction): Promise { + public static async deleteUser( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (Config.Client.authenticationRequired === false) { return next(new ErrorDTO(ErrorCodes.USER_MANAGEMENT_DISABLED)); } - if ((typeof req.params === 'undefined') || (typeof req.params.id === 'undefined')) { + if ( + typeof req.params === 'undefined' || + typeof req.params.id === 'undefined' + ) { return next(); } - try { - await ObjectManagers.getInstance().UserManager.deleteUser(parseInt(req.params.id, 10)); + await ObjectManagers.getInstance().UserManager.deleteUser( + parseInt(req.params.id, 10) + ); return next(); - } catch (err) { return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, null, err)); } } - public static async changeRole(req: Request, res: Response, next: NextFunction): Promise { + public static async changeRole( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (Config.Client.authenticationRequired === false) { return next(new ErrorDTO(ErrorCodes.USER_MANAGEMENT_DISABLED)); } - if ((typeof req.params === 'undefined') || (typeof req.params.id === 'undefined') - || (typeof req.body === 'undefined') || (typeof req.body.newRole === 'undefined')) { + if ( + typeof req.params === 'undefined' || + typeof req.params.id === 'undefined' || + typeof req.body === 'undefined' || + typeof req.body.newRole === 'undefined' + ) { return next(); } try { - await ObjectManagers.getInstance().UserManager.changeRole(parseInt(req.params.id, 10), req.body.newRole); + await ObjectManagers.getInstance().UserManager.changeRole( + parseInt(req.params.id, 10), + req.body.newRole + ); return next(); - } catch (err) { return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, null, err)); } } - - public static async listUsers(req: Request, res: Response, next: NextFunction): Promise { + public static async listUsers( + req: Request, + res: Response, + next: NextFunction + ): Promise { if (Config.Client.authenticationRequired === false) { return next(new ErrorDTO(ErrorCodes.USER_MANAGEMENT_DISABLED)); } @@ -100,6 +132,4 @@ export class UserMWs { return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, null, err)); } } - - } diff --git a/src/backend/middlewares/user/UserRequestConstrainsMWs.ts b/src/backend/middlewares/user/UserRequestConstrainsMWs.ts index 338a9f63..e1a0bd20 100644 --- a/src/backend/middlewares/user/UserRequestConstrainsMWs.ts +++ b/src/backend/middlewares/user/UserRequestConstrainsMWs.ts @@ -1,57 +1,74 @@ -import {NextFunction, Request, Response} from 'express'; +import { NextFunction, Request, Response } from 'express'; import { MoreThanOrEqual } from 'typeorm'; -import {ErrorCodes, ErrorDTO} from '../../../common/entities/Error'; -import {UserRoles} from '../../../common/entities/UserDTO'; -import {ObjectManagers} from '../../model/ObjectManagers'; +import { ErrorCodes, ErrorDTO } from '../../../common/entities/Error'; +import { UserRoles } from '../../../common/entities/UserDTO'; +import { ObjectManagers } from '../../model/ObjectManagers'; export class UserRequestConstrainsMWs { - - public static forceSelfRequest(req: Request, res: Response, next: NextFunction): any { - if ((typeof req.params === 'undefined') || (typeof req.params.id === 'undefined')) { + public static forceSelfRequest( + req: Request, + res: Response, + next: NextFunction + ): any { + if ( + typeof req.params === 'undefined' || + typeof req.params.id === 'undefined' + ) { return next(); } - if (req.session.user.id !== parseInt(req.params.id, 10)) { + if (req.session['user'].id !== parseInt(req.params.id, 10)) { return next(new ErrorDTO(ErrorCodes.NOT_AUTHORISED)); } return next(); } - - public static notSelfRequest(req: Request, res: Response, next: NextFunction): any { - if ((typeof req.params === 'undefined') || (typeof req.params.id === 'undefined')) { + public static notSelfRequest( + req: Request, + res: Response, + next: NextFunction + ): any { + if ( + typeof req.params === 'undefined' || + typeof req.params.id === 'undefined' + ) { return next(); } - if (req.session.user.id === parseInt(req.params.id, 10)) { + if (req.session['user'].id === parseInt(req.params.id, 10)) { return next(new ErrorDTO(ErrorCodes.NOT_AUTHORISED)); } return next(); } - public static async notSelfRequestOr2Admins(req: Request, res: Response, next: NextFunction): Promise { - if ((typeof req.params === 'undefined') || (typeof req.params.id === 'undefined')) { + public static async notSelfRequestOr2Admins( + req: Request, + res: Response, + next: NextFunction + ): Promise { + if ( + typeof req.params === 'undefined' || + typeof req.params.id === 'undefined' + ) { return next(); } - if (req.session.user.id !== parseInt(req.params.id, 10)) { + if (req.session['user'].id !== parseInt(req.params.id, 10)) { return next(); } // TODO: fix it! try { - const result = await ObjectManagers.getInstance().UserManager.find({role: MoreThanOrEqual(UserRoles.Admin)}); + const result = await ObjectManagers.getInstance().UserManager.find({ + role: MoreThanOrEqual(UserRoles.Admin), + }); if (result.length <= 1) { return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR)); } return next(); - } catch (err) { return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR)); } - } - - } diff --git a/src/backend/model/DiskManger.ts b/src/backend/model/DiskManger.ts index 6c1313a0..bbdd9915 100644 --- a/src/backend/model/DiskManger.ts +++ b/src/backend/model/DiskManger.ts @@ -1,10 +1,15 @@ -import {DirectoryDTOUtils, ParentDirectoryDTO} from '../../common/entities/DirectoryDTO'; -import {Logger} from '../Logger'; -import {Config} from '../../common/config/private/Config'; -import {DiskManagerTH} from './threading/ThreadPool'; -import {DirectoryScanSettings, DiskMangerWorker} from './threading/DiskMangerWorker'; -import {FileDTO} from '../../common/entities/FileDTO'; - +import { + DirectoryDTOUtils, + ParentDirectoryDTO, +} from '../../common/entities/DirectoryDTO'; +import { Logger } from '../Logger'; +import { Config } from '../../common/config/private/Config'; +import { DiskManagerTH } from './threading/ThreadPool'; +import { + DirectoryScanSettings, + DiskMangerWorker, +} from './threading/DiskMangerWorker'; +import { FileDTO } from '../../common/entities/FileDTO'; const LOG_TAG = '[DiskManager]'; @@ -20,26 +25,34 @@ export class DiskManager { /** * List all files in a folder as fast as possible */ - public static async scanDirectoryNoMetadata(relativeDirectoryName: string, - settings: DirectoryScanSettings = {}): Promise> { + public static async scanDirectoryNoMetadata( + relativeDirectoryName: string, + settings: DirectoryScanSettings = {} + ): Promise> { settings.noMetadata = true; return this.scanDirectory(relativeDirectoryName, settings); } - public static async scanDirectory(relativeDirectoryName: string, - settings: DirectoryScanSettings = {}): Promise { - + public static async scanDirectory( + relativeDirectoryName: string, + settings: DirectoryScanSettings = {} + ): Promise { Logger.silly(LOG_TAG, 'scanning directory:', relativeDirectoryName); let directory: ParentDirectoryDTO; if (Config.Server.Threading.enabled === true) { - directory = await DiskManager.threadPool.execute(relativeDirectoryName, settings); + directory = await DiskManager.threadPool.execute( + relativeDirectoryName, + settings + ); } else { - directory = await DiskMangerWorker.scanDirectory(relativeDirectoryName, settings) as ParentDirectoryDTO; + directory = (await DiskMangerWorker.scanDirectory( + relativeDirectoryName, + settings + )) as ParentDirectoryDTO; } DirectoryDTOUtils.unpackDirectory(directory); return directory; } - } diff --git a/src/backend/model/FFmpegFactory.ts b/src/backend/model/FFmpegFactory.ts index fe1abb8a..17525e7c 100644 --- a/src/backend/model/FFmpegFactory.ts +++ b/src/backend/model/FFmpegFactory.ts @@ -6,8 +6,7 @@ export class FFmpegFactory { ffmpeg.setFfmpegPath(ffmpegPath); const ffprobePath = require('ffprobe-static'); ffmpeg.setFfprobePath(ffprobePath.path); - } catch (e) { - } + } catch (e) {} return ffmpeg; } } diff --git a/src/backend/model/Localizations.ts b/src/backend/model/Localizations.ts index a693f50e..49b874d2 100644 --- a/src/backend/model/Localizations.ts +++ b/src/backend/model/Localizations.ts @@ -1,19 +1,21 @@ -import {ProjectPath} from '../ProjectPath'; +import { ProjectPath } from '../ProjectPath'; import * as fs from 'fs'; import * as path from 'path'; -import {Config} from '../../common/config/private/Config'; +import { Config } from '../../common/config/private/Config'; export class Localizations { - - constructor() { - } + constructor() {} public static init(): void { const notLanguage = ['assets']; - const dirCont = fs.readdirSync(ProjectPath.FrontendFolder) - .filter((f): any => fs.statSync(path.join(ProjectPath.FrontendFolder, f)).isDirectory()); - Config.Client.languages = dirCont.filter((d): boolean => notLanguage.indexOf(d) === -1); + const dirCont = fs + .readdirSync(ProjectPath.FrontendFolder) + .filter((f): any => + fs.statSync(path.join(ProjectPath.FrontendFolder, f)).isDirectory() + ); + Config.Client.languages = dirCont.filter( + (d): boolean => notLanguage.indexOf(d) === -1 + ); Config.Client.languages.sort(); } - } diff --git a/src/backend/model/NotifocationManager.ts b/src/backend/model/NotifocationManager.ts index 38fa91d4..191c9212 100644 --- a/src/backend/model/NotifocationManager.ts +++ b/src/backend/model/NotifocationManager.ts @@ -1,28 +1,30 @@ -import {NotificationDTO, NotificationType} from '../../common/entities/NotificationDTO'; -import {Request} from 'express'; +import { + NotificationDTO, + NotificationType, +} from '../../common/entities/NotificationDTO'; +import { Request } from 'express'; export class NotificationManager { public static notifications: NotificationDTO[] = []; - public static HasNotification: NotificationDTO[] = - [ - { - type: NotificationType.info, - message: 'There are unhandled server notification. Login as Administrator to handle them.' - } - ]; - + public static HasNotification: NotificationDTO[] = [ + { + type: NotificationType.info, + message: + 'There are unhandled server notification. Login as Administrator to handle them.', + }, + ]; public static error(message: string, details?: any, req?: Request): void { const noti: NotificationDTO = { type: NotificationType.error, message, - details + details, }; if (req) { noti.request = { method: req.method, url: req.url, - statusCode: req.statusCode + statusCode: req.statusCode, }; } NotificationManager.notifications.push(noti); @@ -32,13 +34,13 @@ export class NotificationManager { const noti: NotificationDTO = { type: NotificationType.warning, message, - details + details, }; if (req) { noti.request = { method: req.method, url: req.url, - statusCode: req.statusCode + statusCode: req.statusCode, }; } NotificationManager.notifications.push(noti); diff --git a/src/backend/model/ObjectManagers.ts b/src/backend/model/ObjectManagers.ts index 1729e4ed..4414fa66 100644 --- a/src/backend/model/ObjectManagers.ts +++ b/src/backend/model/ObjectManagers.ts @@ -1,24 +1,23 @@ -import {IUserManager} from './database/interfaces/IUserManager'; -import {IGalleryManager} from './database/interfaces/IGalleryManager'; -import {ISearchManager} from './database/interfaces/ISearchManager'; -import {SQLConnection} from './database/sql/SQLConnection'; -import {ISharingManager} from './database/interfaces/ISharingManager'; -import {Logger} from '../Logger'; -import {IIndexingManager} from './database/interfaces/IIndexingManager'; -import {IPersonManager} from './database/interfaces/IPersonManager'; -import {IVersionManager} from './database/interfaces/IVersionManager'; -import {IJobManager} from './database/interfaces/IJobManager'; -import {LocationManager} from './database/LocationManager'; -import {IAlbumManager} from './database/interfaces/IAlbumManager'; -import {JobManager} from './jobs/JobManager'; -import {IPreviewManager} from './database/interfaces/IPreviewManager'; -import {ParentDirectoryDTO} from '../../common/entities/DirectoryDTO'; -import {IObjectManager} from './database/interfaces/IObjectManager'; +import { IUserManager } from './database/interfaces/IUserManager'; +import { IGalleryManager } from './database/interfaces/IGalleryManager'; +import { ISearchManager } from './database/interfaces/ISearchManager'; +import { SQLConnection } from './database/sql/SQLConnection'; +import { ISharingManager } from './database/interfaces/ISharingManager'; +import { Logger } from '../Logger'; +import { IIndexingManager } from './database/interfaces/IIndexingManager'; +import { IPersonManager } from './database/interfaces/IPersonManager'; +import { IVersionManager } from './database/interfaces/IVersionManager'; +import { IJobManager } from './database/interfaces/IJobManager'; +import { LocationManager } from './database/LocationManager'; +import { IAlbumManager } from './database/interfaces/IAlbumManager'; +import { JobManager } from './jobs/JobManager'; +import { IPreviewManager } from './database/interfaces/IPreviewManager'; +import { ParentDirectoryDTO } from '../../common/entities/DirectoryDTO'; +import { IObjectManager } from './database/interfaces/IObjectManager'; const LOG_TAG = '[ObjectManagers]'; export class ObjectManagers { - private static instance: ObjectManagers = null; private readonly managers: IObjectManager[]; @@ -34,7 +33,6 @@ export class ObjectManagers { private locationManager: LocationManager; private albumManager: IAlbumManager; - constructor() { this.managers = []; } @@ -111,7 +109,6 @@ export class ObjectManagers { this.managers.push(this.indexingManager); } - get GalleryManager(): IGalleryManager { return this.galleryManager; } @@ -181,8 +178,10 @@ export class ObjectManagers { public static async reset(): Promise { Logger.silly(LOG_TAG, 'Object manager reset begin'); - if (ObjectManagers.getInstance().IndexingManager && - ObjectManagers.getInstance().IndexingManager.IsSavingInProgress) { + if ( + ObjectManagers.getInstance().IndexingManager && + ObjectManagers.getInstance().IndexingManager.IsSavingInProgress + ) { await ObjectManagers.getInstance().IndexingManager.SavingReady; } if (ObjectManagers.getInstance().JobManager) { @@ -207,20 +206,31 @@ export class ObjectManagers { } private static initManagers(type: 'memory' | 'sql'): void { - ObjectManagers.getInstance().AlbumManager = new (require(`./database/${type}/AlbumManager`).AlbumManager)(); - ObjectManagers.getInstance().GalleryManager = new (require(`./database/${type}/GalleryManager`).GalleryManager)(); - ObjectManagers.getInstance().IndexingManager = new (require(`./database/${type}/IndexingManager`).IndexingManager)(); - ObjectManagers.getInstance().PersonManager = new (require(`./database/${type}/PersonManager`).PersonManager)(); - ObjectManagers.getInstance().PreviewManager = new (require(`./database/${type}/PreviewManager`).PreviewManager)(); - ObjectManagers.getInstance().SearchManager = new (require(`./database/${type}/SearchManager`).SearchManager)(); - ObjectManagers.getInstance().SharingManager = new (require(`./database/${type}/SharingManager`).SharingManager)(); - ObjectManagers.getInstance().UserManager = new (require(`./database/${type}/UserManager`).UserManager)(); - ObjectManagers.getInstance().VersionManager = new (require(`./database/${type}/VersionManager`).VersionManager)(); + ObjectManagers.getInstance().AlbumManager = + new (require(`./database/${type}/AlbumManager`).AlbumManager)(); + ObjectManagers.getInstance().GalleryManager = + new (require(`./database/${type}/GalleryManager`).GalleryManager)(); + ObjectManagers.getInstance().IndexingManager = + new (require(`./database/${type}/IndexingManager`).IndexingManager)(); + ObjectManagers.getInstance().PersonManager = + new (require(`./database/${type}/PersonManager`).PersonManager)(); + ObjectManagers.getInstance().PreviewManager = + new (require(`./database/${type}/PreviewManager`).PreviewManager)(); + ObjectManagers.getInstance().SearchManager = + new (require(`./database/${type}/SearchManager`).SearchManager)(); + ObjectManagers.getInstance().SharingManager = + new (require(`./database/${type}/SharingManager`).SharingManager)(); + ObjectManagers.getInstance().UserManager = + new (require(`./database/${type}/UserManager`).UserManager)(); + ObjectManagers.getInstance().VersionManager = + new (require(`./database/${type}/VersionManager`).VersionManager)(); ObjectManagers.getInstance().JobManager = new JobManager(); ObjectManagers.getInstance().LocationManager = new LocationManager(); } - public async onDataChange(changedDir: ParentDirectoryDTO = null): Promise { + public async onDataChange( + changedDir: ParentDirectoryDTO = null + ): Promise { await this.VersionManager.onNewDataVersion(changedDir); for (const manager of this.managers) { @@ -232,5 +242,4 @@ export class ObjectManagers { } } } - } diff --git a/src/backend/model/PasswordHelper.ts b/src/backend/model/PasswordHelper.ts index 01b5f19a..185cdefa 100644 --- a/src/backend/model/PasswordHelper.ts +++ b/src/backend/model/PasswordHelper.ts @@ -6,11 +6,13 @@ export class PasswordHelper { return bcrypt.hashSync(password, salt); } - public static comparePassword(password: string, encryptedPassword: string): boolean { + public static comparePassword( + password: string, + encryptedPassword: string + ): boolean { try { return bcrypt.compareSync(password, encryptedPassword); - } catch (e) { - } + } catch (e) {} return false; } } diff --git a/src/backend/model/database/LocationManager.ts b/src/backend/model/database/LocationManager.ts index d2031098..d3549802 100644 --- a/src/backend/model/database/LocationManager.ts +++ b/src/backend/model/database/LocationManager.ts @@ -1,9 +1,9 @@ -import {GPSMetadata} from '../../../common/entities/PhotoDTO'; +import { GPSMetadata } from '../../../common/entities/PhotoDTO'; import * as NodeGeocoder from 'node-geocoder'; -import {LocationLookupException} from '../../exceptions/LocationLookupException'; -import {LRU} from '../../../common/Utils'; -import {IObjectManager} from './interfaces/IObjectManager'; -import {ParentDirectoryDTO} from '../../../common/entities/DirectoryDTO'; +import { LocationLookupException } from '../../exceptions/LocationLookupException'; +import { LRU } from '../../../common/Utils'; +import { IObjectManager } from './interfaces/IObjectManager'; +import { ParentDirectoryDTO } from '../../../common/entities/DirectoryDTO'; export class LocationManager implements IObjectManager { // onNewDataVersion only need for TypeScript, otherwise the interface is not implemented. @@ -12,23 +12,21 @@ export class LocationManager implements IObjectManager { cache = new LRU(100); constructor() { - this.geocoder = NodeGeocoder({provider: 'openstreetmap'}); + this.geocoder = NodeGeocoder({ provider: 'openstreetmap' }); } async getGPSData(text: string): Promise { if (!this.cache.get(text)) { - const ret = await this.geocoder.geocode(text); if (ret.length < 1) { throw new LocationLookupException('Cannot find location:' + text, text); } this.cache.set(text, { latitude: ret[0].latitude, - longitude: ret[0].longitude + longitude: ret[0].longitude, }); } return this.cache.get(text); } - } diff --git a/src/backend/model/database/interfaces/IAlbumManager.ts b/src/backend/model/database/interfaces/IAlbumManager.ts index ed51e22b..98686c49 100644 --- a/src/backend/model/database/interfaces/IAlbumManager.ts +++ b/src/backend/model/database/interfaces/IAlbumManager.ts @@ -1,19 +1,26 @@ -import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO'; -import {AlbumBaseDTO} from '../../../../common/entities/album/AlbumBaseDTO'; -import {IObjectManager} from './IObjectManager'; +import { SearchQueryDTO } from '../../../../common/entities/SearchQueryDTO'; +import { AlbumBaseDTO } from '../../../../common/entities/album/AlbumBaseDTO'; +import { IObjectManager } from './IObjectManager'; export interface IAlbumManager extends IObjectManager { /** * Creates a saved search type of album */ - addSavedSearch(name: string, searchQuery: SearchQueryDTO, lockedAlbum?: boolean): Promise; - + addSavedSearch( + name: string, + searchQuery: SearchQueryDTO, + lockedAlbum?: boolean + ): Promise; /** * Creates a saved search type of album if the album is not yet exists * lockAlbum: Album cannot be removed from the UI */ - addIfNotExistSavedSearch(name: string, searchQuery: SearchQueryDTO, lockedAlbum?: boolean): Promise; + addIfNotExistSavedSearch( + name: string, + searchQuery: SearchQueryDTO, + lockedAlbum?: boolean + ): Promise; /** * Deletes an album diff --git a/src/backend/model/database/interfaces/IGalleryManager.ts b/src/backend/model/database/interfaces/IGalleryManager.ts index 14c163ba..7fe94da6 100644 --- a/src/backend/model/database/interfaces/IGalleryManager.ts +++ b/src/backend/model/database/interfaces/IGalleryManager.ts @@ -1,10 +1,10 @@ -import {ParentDirectoryDTO} from '../../../../common/entities/DirectoryDTO'; -import {IObjectManager} from './IObjectManager'; +import { ParentDirectoryDTO } from '../../../../common/entities/DirectoryDTO'; +import { IObjectManager } from './IObjectManager'; export interface IGalleryManager extends IObjectManager { - listDirectory(relativeDirectoryName: string, - knownLastModified?: number, - knownLastScanned?: number): Promise; - - + listDirectory( + relativeDirectoryName: string, + knownLastModified?: number, + knownLastScanned?: number + ): Promise; } diff --git a/src/backend/model/database/interfaces/IIndexingManager.ts b/src/backend/model/database/interfaces/IIndexingManager.ts index f7a06680..056b1607 100644 --- a/src/backend/model/database/interfaces/IIndexingManager.ts +++ b/src/backend/model/database/interfaces/IIndexingManager.ts @@ -1,5 +1,5 @@ -import {ParentDirectoryDTO} from '../../../../common/entities/DirectoryDTO'; -import {IObjectManager} from './IObjectManager'; +import { ParentDirectoryDTO } from '../../../../common/entities/DirectoryDTO'; +import { IObjectManager } from './IObjectManager'; export interface IIndexingManager extends IObjectManager { SavingReady: Promise; diff --git a/src/backend/model/database/interfaces/IJobManager.ts b/src/backend/model/database/interfaces/IJobManager.ts index ec33990b..2beb9c06 100644 --- a/src/backend/model/database/interfaces/IJobManager.ts +++ b/src/backend/model/database/interfaces/IJobManager.ts @@ -1,11 +1,14 @@ -import {JobProgressDTO} from '../../../../common/entities/job/JobProgressDTO'; -import {JobDTO} from '../../../../common/entities/job/JobDTO'; -import {IObjectManager} from './IObjectManager'; +import { JobProgressDTO } from '../../../../common/entities/job/JobProgressDTO'; +import { JobDTO } from '../../../../common/entities/job/JobDTO'; +import { IObjectManager } from './IObjectManager'; export interface IJobManager extends IObjectManager { - - - run(jobId: string, config: any, soloRun: boolean, allowParallelRun: boolean): Promise; + run( + jobId: string, + config: any, + soloRun: boolean, + allowParallelRun: boolean + ): Promise; stop(jobId: string): void; @@ -16,5 +19,4 @@ export interface IJobManager extends IObjectManager { stopSchedules(): void; runSchedules(): void; - } diff --git a/src/backend/model/database/interfaces/IObjectManager.ts b/src/backend/model/database/interfaces/IObjectManager.ts index 37eff20a..216d9f12 100644 --- a/src/backend/model/database/interfaces/IObjectManager.ts +++ b/src/backend/model/database/interfaces/IObjectManager.ts @@ -1,4 +1,4 @@ -import {ParentDirectoryDTO} from '../../../../common/entities/DirectoryDTO'; +import { ParentDirectoryDTO } from '../../../../common/entities/DirectoryDTO'; export interface IObjectManager { onNewDataVersion?: (changedDir?: ParentDirectoryDTO) => Promise; diff --git a/src/backend/model/database/interfaces/IPersonManager.ts b/src/backend/model/database/interfaces/IPersonManager.ts index 14cff056..7df1fe3e 100644 --- a/src/backend/model/database/interfaces/IPersonManager.ts +++ b/src/backend/model/database/interfaces/IPersonManager.ts @@ -1,7 +1,7 @@ -import {PersonEntry} from '../sql/enitites/PersonEntry'; -import {PersonDTO} from '../../../../common/entities/PersonDTO'; -import {IObjectManager} from './IObjectManager'; -import {FaceRegion} from '../../../../common/entities/PhotoDTO'; +import { PersonEntry } from '../sql/enitites/PersonEntry'; +import { PersonDTO } from '../../../../common/entities/PersonDTO'; +import { IObjectManager } from './IObjectManager'; +import { FaceRegion } from '../../../../common/entities/PhotoDTO'; export interface IPersonManager extends IObjectManager { getAll(): Promise; @@ -9,7 +9,7 @@ export interface IPersonManager extends IObjectManager { get(name: string): Promise; // saving a Person with a sample region. Person entry cannot exist without a face region - saveAll(person: { name: string, faceRegion: FaceRegion }[]): Promise; + saveAll(person: { name: string; faceRegion: FaceRegion }[]): Promise; updatePerson(name: string, partialPerson: PersonDTO): Promise; diff --git a/src/backend/model/database/interfaces/IPreviewManager.ts b/src/backend/model/database/interfaces/IPreviewManager.ts index cc5dad72..56e0bc96 100644 --- a/src/backend/model/database/interfaces/IPreviewManager.ts +++ b/src/backend/model/database/interfaces/IPreviewManager.ts @@ -1,14 +1,21 @@ -import {PreviewPhotoDTO} from '../../../../common/entities/PhotoDTO'; -import {IObjectManager} from './IObjectManager'; -import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO'; +import { PreviewPhotoDTO } from '../../../../common/entities/PhotoDTO'; +import { IObjectManager } from './IObjectManager'; +import { SearchQueryDTO } from '../../../../common/entities/SearchQueryDTO'; export interface IPreviewManager extends IObjectManager { - setAndGetPreviewForDirectory(dir: { id: number, name: string, path: string }): Promise; + setAndGetPreviewForDirectory(dir: { + id: number; + name: string; + path: string; + }): Promise; - getAlbumPreview(album: { searchQuery: SearchQueryDTO }): Promise; - - getPartialDirsWithoutPreviews(): Promise<{ id: number; name: string; path: string }[]>; + getAlbumPreview(album: { + searchQuery: SearchQueryDTO; + }): Promise; + getPartialDirsWithoutPreviews(): Promise< + { id: number; name: string; path: string }[] + >; resetPreviews(): Promise; } diff --git a/src/backend/model/database/interfaces/ISearchManager.ts b/src/backend/model/database/interfaces/ISearchManager.ts index 1d905bc6..2e3f4139 100644 --- a/src/backend/model/database/interfaces/ISearchManager.ts +++ b/src/backend/model/database/interfaces/ISearchManager.ts @@ -1,11 +1,17 @@ -import {AutoCompleteItem} from '../../../../common/entities/AutoCompleteItem'; -import {SearchResultDTO} from '../../../../common/entities/SearchResultDTO'; -import {SearchQueryDTO, SearchQueryTypes} from '../../../../common/entities/SearchQueryDTO'; -import {PhotoDTO} from '../../../../common/entities/PhotoDTO'; -import {IObjectManager} from './IObjectManager'; +import { AutoCompleteItem } from '../../../../common/entities/AutoCompleteItem'; +import { SearchResultDTO } from '../../../../common/entities/SearchResultDTO'; +import { + SearchQueryDTO, + SearchQueryTypes, +} from '../../../../common/entities/SearchQueryDTO'; +import { PhotoDTO } from '../../../../common/entities/PhotoDTO'; +import { IObjectManager } from './IObjectManager'; -export interface ISearchManager extends IObjectManager{ - autocomplete(text: string, type: SearchQueryTypes): Promise; +export interface ISearchManager extends IObjectManager { + autocomplete( + text: string, + type: SearchQueryTypes + ): Promise; search(query: SearchQueryDTO): Promise; diff --git a/src/backend/model/database/interfaces/ISharingManager.ts b/src/backend/model/database/interfaces/ISharingManager.ts index fabbb956..c646adb8 100644 --- a/src/backend/model/database/interfaces/ISharingManager.ts +++ b/src/backend/model/database/interfaces/ISharingManager.ts @@ -1,6 +1,6 @@ -import {SharingDTO} from '../../../../common/entities/SharingDTO'; -import {IObjectManager} from './IObjectManager'; -import {FindOptionsWhere} from 'typeorm'; +import { SharingDTO } from '../../../../common/entities/SharingDTO'; +import { IObjectManager } from './IObjectManager'; +import { FindOptionsWhere } from 'typeorm'; export interface ISharingManager extends IObjectManager { findOne(filter: FindOptionsWhere): Promise; diff --git a/src/backend/model/database/interfaces/IUserManager.ts b/src/backend/model/database/interfaces/IUserManager.ts index 3c5ae493..c8bd720e 100644 --- a/src/backend/model/database/interfaces/IUserManager.ts +++ b/src/backend/model/database/interfaces/IUserManager.ts @@ -1,6 +1,6 @@ -import {UserDTO, UserRoles} from '../../../../common/entities/UserDTO'; -import {IObjectManager} from './IObjectManager'; -import {FindOptionsWhere} from 'typeorm'; +import { UserDTO, UserRoles } from '../../../../common/entities/UserDTO'; +import { IObjectManager } from './IObjectManager'; +import { FindOptionsWhere } from 'typeorm'; export interface IUserManager extends IObjectManager { findOne(filter: FindOptionsWhere): Promise; diff --git a/src/backend/model/database/interfaces/IVersionManager.ts b/src/backend/model/database/interfaces/IVersionManager.ts index 76ecea25..69bbe847 100644 --- a/src/backend/model/database/interfaces/IVersionManager.ts +++ b/src/backend/model/database/interfaces/IVersionManager.ts @@ -1,4 +1,4 @@ -import {IObjectManager} from './IObjectManager'; +import { IObjectManager } from './IObjectManager'; export interface IVersionManager extends IObjectManager { getDataVersion(): Promise; diff --git a/src/backend/model/database/memory/AlbumManager.ts b/src/backend/model/database/memory/AlbumManager.ts index ba73ec79..09e4ede1 100644 --- a/src/backend/model/database/memory/AlbumManager.ts +++ b/src/backend/model/database/memory/AlbumManager.ts @@ -1,6 +1,6 @@ -import {AlbumBaseDTO} from '../../../../common/entities/album/AlbumBaseDTO'; -import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO'; -import {IAlbumManager} from '../interfaces/IAlbumManager'; +import { AlbumBaseDTO } from '../../../../common/entities/album/AlbumBaseDTO'; +import { SearchQueryDTO } from '../../../../common/entities/SearchQueryDTO'; +import { IAlbumManager } from '../interfaces/IAlbumManager'; export class AlbumManager implements IAlbumManager { resetPreviews(): Promise { @@ -15,11 +15,19 @@ export class AlbumManager implements IAlbumManager { throw new Error('not supported by memory DB'); } - public async addIfNotExistSavedSearch(name: string, searchQuery: SearchQueryDTO, lockedAlbum?: boolean): Promise { + public async addIfNotExistSavedSearch( + name: string, + searchQuery: SearchQueryDTO, + lockedAlbum?: boolean + ): Promise { throw new Error('not supported by memory DB'); } - public async addSavedSearch(name: string, searchQuery: SearchQueryDTO, lockedAlbum?: boolean): Promise { + public async addSavedSearch( + name: string, + searchQuery: SearchQueryDTO, + lockedAlbum?: boolean + ): Promise { throw new Error('not supported by memory DB'); } diff --git a/src/backend/model/database/memory/GalleryManager.ts b/src/backend/model/database/memory/GalleryManager.ts index c937e5e0..4006a825 100644 --- a/src/backend/model/database/memory/GalleryManager.ts +++ b/src/backend/model/database/memory/GalleryManager.ts @@ -1,32 +1,38 @@ -import {ParentDirectoryDTO} from '../../../../common/entities/DirectoryDTO'; -import {IGalleryManager} from '../interfaces/IGalleryManager'; +import { ParentDirectoryDTO } from '../../../../common/entities/DirectoryDTO'; +import { IGalleryManager } from '../interfaces/IGalleryManager'; import * as path from 'path'; import * as fs from 'fs'; -import {DiskManager} from '../../DiskManger'; -import {ProjectPath} from '../../../ProjectPath'; -import {Config} from '../../../../common/config/private/Config'; -import {DiskMangerWorker} from '../../threading/DiskMangerWorker'; -import {ReIndexingSensitivity} from '../../../../common/config/private/PrivateConfig'; -import {ServerPG2ConfMap} from '../../../../common/PG2ConfMap'; +import { DiskManager } from '../../DiskManger'; +import { ProjectPath } from '../../../ProjectPath'; +import { Config } from '../../../../common/config/private/Config'; +import { DiskMangerWorker } from '../../threading/DiskMangerWorker'; +import { ReIndexingSensitivity } from '../../../../common/config/private/PrivateConfig'; +import { ServerPG2ConfMap } from '../../../../common/PG2ConfMap'; export class GalleryManager implements IGalleryManager { - - public async listDirectory(relativeDirectoryName: string, - knownLastModified?: number, - knownLastScanned?: number): Promise { + public async listDirectory( + relativeDirectoryName: string, + knownLastModified?: number, + knownLastScanned?: number + ): Promise { // If it seems that the content did not changed, do not work on it if (knownLastModified && knownLastScanned) { - const stat = fs.statSync(path.join(ProjectPath.ImageFolder, relativeDirectoryName)); + const stat = fs.statSync( + path.join(ProjectPath.ImageFolder, relativeDirectoryName) + ); const lastModified = DiskMangerWorker.calcLastModified(stat); - if (Date.now() - knownLastScanned <= Config.Server.Indexing.cachedFolderTimeout && + if ( + Date.now() - knownLastScanned <= + Config.Server.Indexing.cachedFolderTimeout && lastModified === knownLastModified && - Config.Server.Indexing.reIndexingSensitivity < ReIndexingSensitivity.high) { + Config.Server.Indexing.reIndexingSensitivity < + ReIndexingSensitivity.high + ) { return Promise.resolve(null); } } const dir = await DiskManager.scanDirectory(relativeDirectoryName); - dir.metaFile = dir.metaFile.filter(m => !ServerPG2ConfMap[m.name]); + dir.metaFile = dir.metaFile.filter((m) => !ServerPG2ConfMap[m.name]); return dir; } - } diff --git a/src/backend/model/database/memory/IndexingManager.ts b/src/backend/model/database/memory/IndexingManager.ts index b7555ce0..f06b98e5 100644 --- a/src/backend/model/database/memory/IndexingManager.ts +++ b/src/backend/model/database/memory/IndexingManager.ts @@ -1,5 +1,5 @@ -import {IIndexingManager} from '../interfaces/IIndexingManager'; -import {ParentDirectoryDTO} from '../../../../common/entities/DirectoryDTO'; +import { IIndexingManager } from '../interfaces/IIndexingManager'; +import { ParentDirectoryDTO } from '../../../../common/entities/DirectoryDTO'; export class IndexingManager implements IIndexingManager { IsSavingInProgress: boolean; @@ -16,6 +16,4 @@ export class IndexingManager implements IIndexingManager { resetDB(): Promise { throw new Error('not supported by memory DB'); } - - } diff --git a/src/backend/model/database/memory/PersonManager.ts b/src/backend/model/database/memory/PersonManager.ts index 6e40c608..611b0fed 100644 --- a/src/backend/model/database/memory/PersonManager.ts +++ b/src/backend/model/database/memory/PersonManager.ts @@ -1,11 +1,12 @@ -import {IPersonManager} from '../interfaces/IPersonManager'; -import {PersonDTO} from '../../../../common/entities/PersonDTO'; -import {FaceRegion} from '../../../../common/entities/PhotoDTO'; +import { IPersonManager } from '../interfaces/IPersonManager'; +import { PersonDTO } from '../../../../common/entities/PersonDTO'; +import { FaceRegion } from '../../../../common/entities/PhotoDTO'; export class PersonManager implements IPersonManager { resetPreviews(): Promise { throw new Error('not supported by memory DB'); } + saveAll(person: { name: string; faceRegion: FaceRegion }[]): Promise { throw new Error('not supported by memory DB'); } @@ -18,7 +19,6 @@ export class PersonManager implements IPersonManager { throw new Error('not supported by memory DB'); } - onGalleryIndexUpdate(): Promise { throw new Error('not supported by memory DB'); } diff --git a/src/backend/model/database/memory/PreviewManager.ts b/src/backend/model/database/memory/PreviewManager.ts index 9e942a27..5772fbd7 100644 --- a/src/backend/model/database/memory/PreviewManager.ts +++ b/src/backend/model/database/memory/PreviewManager.ts @@ -1,14 +1,16 @@ -import {IPreviewManager} from '../interfaces/IPreviewManager'; -import {DirectoryPathDTO} from '../../../../common/entities/DirectoryDTO'; -import {MediaDTO} from '../../../../common/entities/MediaDTO'; -import {SavedSearchDTO} from '../../../../common/entities/album/SavedSearchDTO'; +import { IPreviewManager } from '../interfaces/IPreviewManager'; +import { DirectoryPathDTO } from '../../../../common/entities/DirectoryDTO'; +import { MediaDTO } from '../../../../common/entities/MediaDTO'; +import { SavedSearchDTO } from '../../../../common/entities/album/SavedSearchDTO'; export class PreviewManager implements IPreviewManager { resetPreviews(): Promise { throw new Error('not implemented'); } - getPartialDirsWithoutPreviews(): Promise<{ id: number; name: string; path: string }[]> { + getPartialDirsWithoutPreviews(): Promise< + { id: number; name: string; path: string }[] + > { throw new Error('not implemented'); } diff --git a/src/backend/model/database/memory/SearchManager.ts b/src/backend/model/database/memory/SearchManager.ts index b8d27588..fe5f8b64 100644 --- a/src/backend/model/database/memory/SearchManager.ts +++ b/src/backend/model/database/memory/SearchManager.ts @@ -1,15 +1,21 @@ -import {AutoCompleteItem} from '../../../../common/entities/AutoCompleteItem'; -import {ISearchManager} from '../interfaces/ISearchManager'; -import {SearchResultDTO} from '../../../../common/entities/SearchResultDTO'; -import {SearchQueryDTO, SearchQueryTypes} from '../../../../common/entities/SearchQueryDTO'; -import {PhotoDTO} from '../../../../common/entities/PhotoDTO'; +import { AutoCompleteItem } from '../../../../common/entities/AutoCompleteItem'; +import { ISearchManager } from '../interfaces/ISearchManager'; +import { SearchResultDTO } from '../../../../common/entities/SearchResultDTO'; +import { + SearchQueryDTO, + SearchQueryTypes, +} from '../../../../common/entities/SearchQueryDTO'; +import { PhotoDTO } from '../../../../common/entities/PhotoDTO'; export class SearchManager implements ISearchManager { getRandomPhoto(queryFilter: SearchQueryDTO): Promise { throw new Error('Method not implemented.'); } - autocomplete(text: string, type: SearchQueryTypes): Promise { + autocomplete( + text: string, + type: SearchQueryTypes + ): Promise { throw new Error('Method not implemented.'); } diff --git a/src/backend/model/database/memory/SharingManager.ts b/src/backend/model/database/memory/SharingManager.ts index d0f1c6a1..1731acde 100644 --- a/src/backend/model/database/memory/SharingManager.ts +++ b/src/backend/model/database/memory/SharingManager.ts @@ -1,8 +1,7 @@ -import {ISharingManager} from '../interfaces/ISharingManager'; -import {SharingDTO} from '../../../../common/entities/SharingDTO'; +import { ISharingManager } from '../interfaces/ISharingManager'; +import { SharingDTO } from '../../../../common/entities/SharingDTO'; export class SharingManager implements ISharingManager { - deleteSharing(sharingKey: string): Promise { throw new Error('not implemented'); } @@ -19,9 +18,10 @@ export class SharingManager implements ISharingManager { throw new Error('not implemented'); } - updateSharing(sharing: SharingDTO, forceUpdate: boolean): Promise { + updateSharing( + sharing: SharingDTO, + forceUpdate: boolean + ): Promise { throw new Error('not implemented'); } - - } diff --git a/src/backend/model/database/memory/UserManager.ts b/src/backend/model/database/memory/UserManager.ts index 77abc117..3c32f7fe 100644 --- a/src/backend/model/database/memory/UserManager.ts +++ b/src/backend/model/database/memory/UserManager.ts @@ -1,17 +1,15 @@ -import {UserDTO, UserRoles} from '../../../../common/entities/UserDTO'; -import {IUserManager} from '../interfaces/IUserManager'; -import {ProjectPath} from '../../../ProjectPath'; -import {Utils} from '../../../../common/Utils'; +import { UserDTO, UserRoles } from '../../../../common/entities/UserDTO'; +import { IUserManager } from '../interfaces/IUserManager'; +import { ProjectPath } from '../../../ProjectPath'; +import { Utils } from '../../../../common/Utils'; import * as fs from 'fs'; import * as path from 'path'; -import {PasswordHelper} from '../../PasswordHelper'; - +import { PasswordHelper } from '../../PasswordHelper'; export class UserManager implements IUserManager { - private db: { users?: UserDTO[], idCounter?: number } = {}; + private db: { users?: UserDTO[]; idCounter?: number } = {}; private readonly dbPath: string; - constructor() { this.dbPath = path.join(ProjectPath.DBFolder, 'users.db'); if (fs.existsSync(this.dbPath)) { @@ -25,13 +23,15 @@ export class UserManager implements IUserManager { if (!this.db.users) { this.db.users = []; // TODO: remove defaults - this.createUser({name: 'admin', password: 'admin', role: UserRoles.Admin} as UserDTO); + this.createUser({ + name: 'admin', + password: 'admin', + role: UserRoles.Admin, + } as UserDTO); } this.saveDB(); - } - public async findOne(filter: any): Promise { const result = await this.find(filter); @@ -47,7 +47,7 @@ export class UserManager implements IUserManager { const users = this.db.users.slice(); let i = users.length; while (i--) { - if (pass && !(PasswordHelper.comparePassword(pass, users[i].password))) { + if (pass && !PasswordHelper.comparePassword(pass, users[i].password)) { users.splice(i, 1); continue; } @@ -98,5 +98,4 @@ export class UserManager implements IUserManager { private saveDB(): void { fs.writeFileSync(this.dbPath, JSON.stringify(this.db)); } - } diff --git a/src/backend/model/database/memory/VersionManager.ts b/src/backend/model/database/memory/VersionManager.ts index dbcc0695..9b4137ba 100644 --- a/src/backend/model/database/memory/VersionManager.ts +++ b/src/backend/model/database/memory/VersionManager.ts @@ -1,5 +1,5 @@ -import {IVersionManager} from '../interfaces/IVersionManager'; -import {DataStructureVersion} from '../../../../common/DataStructureVersion'; +import { IVersionManager } from '../interfaces/IVersionManager'; +import { DataStructureVersion } from '../../../../common/DataStructureVersion'; export class VersionManager implements IVersionManager { async getDataVersion(): Promise { diff --git a/src/backend/model/database/sql/AlbumManager.ts b/src/backend/model/database/sql/AlbumManager.ts index 085fcdf7..0416ff03 100644 --- a/src/backend/model/database/sql/AlbumManager.ts +++ b/src/backend/model/database/sql/AlbumManager.ts @@ -1,18 +1,17 @@ -import {SQLConnection} from './SQLConnection'; -import {AlbumBaseEntity} from './enitites/album/AlbumBaseEntity'; -import {AlbumBaseDTO} from '../../../../common/entities/album/AlbumBaseDTO'; -import {SavedSearchDTO} from '../../../../common/entities/album/SavedSearchDTO'; -import {ObjectManagers} from '../../ObjectManagers'; -import {ISQLSearchManager} from './ISearchManager'; -import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO'; -import {SavedSearchEntity} from './enitites/album/SavedSearchEntity'; -import {IAlbumManager} from '../interfaces/IAlbumManager'; -import {Logger} from '../../../Logger'; +import { SQLConnection } from './SQLConnection'; +import { AlbumBaseEntity } from './enitites/album/AlbumBaseEntity'; +import { AlbumBaseDTO } from '../../../../common/entities/album/AlbumBaseDTO'; +import { SavedSearchDTO } from '../../../../common/entities/album/SavedSearchDTO'; +import { ObjectManagers } from '../../ObjectManagers'; +import { ISQLSearchManager } from './ISearchManager'; +import { SearchQueryDTO } from '../../../../common/entities/SearchQueryDTO'; +import { SavedSearchEntity } from './enitites/album/SavedSearchEntity'; +import { IAlbumManager } from '../interfaces/IAlbumManager'; +import { Logger } from '../../../Logger'; const LOG_TAG = '[AlbumManager]'; export class AlbumManager implements IAlbumManager { - /** * Person table contains denormalized data that needs to update when isDBValid = false */ @@ -20,57 +19,73 @@ export class AlbumManager implements IAlbumManager { private static async updateAlbum(album: SavedSearchEntity): Promise { const connection = await SQLConnection.getConnection(); - const preview = await ObjectManagers.getInstance().PreviewManager - .getAlbumPreview(album); - const count = await (ObjectManagers.getInstance().SearchManager as ISQLSearchManager) - .getCount((album as SavedSearchDTO).searchQuery); + const preview = + await ObjectManagers.getInstance().PreviewManager.getAlbumPreview(album); + const count = await ( + ObjectManagers.getInstance().SearchManager as ISQLSearchManager + ).getCount((album as SavedSearchDTO).searchQuery); await connection .createQueryBuilder() .update(AlbumBaseEntity) - .set({preview, count}) - .where('id = :id', {id: album.id}) + .set({ preview, count }) + .where('id = :id', { id: album.id }) .execute(); } - public async addIfNotExistSavedSearch(name: string, searchQuery: SearchQueryDTO, lockedAlbum: boolean): Promise { + public async addIfNotExistSavedSearch( + name: string, + searchQuery: SearchQueryDTO, + lockedAlbum: boolean + ): Promise { const connection = await SQLConnection.getConnection(); - const album = await connection.getRepository(SavedSearchEntity) - .findOneBy({name, searchQuery}); + const album = await connection + .getRepository(SavedSearchEntity) + .findOneBy({ name, searchQuery }); if (album) { return; } await this.addSavedSearch(name, searchQuery, lockedAlbum); } - public async addSavedSearch(name: string, searchQuery: SearchQueryDTO, lockedAlbum?: boolean): Promise { + public async addSavedSearch( + name: string, + searchQuery: SearchQueryDTO, + lockedAlbum?: boolean + ): Promise { const connection = await SQLConnection.getConnection(); - const a = await connection.getRepository(SavedSearchEntity).save({name, searchQuery, locked: lockedAlbum}); + const a = await connection + .getRepository(SavedSearchEntity) + .save({ name, searchQuery, locked: lockedAlbum }); await AlbumManager.updateAlbum(a); } public async deleteAlbum(id: number): Promise { const connection = await SQLConnection.getConnection(); - if (await connection.getRepository(AlbumBaseEntity) - .countBy({id, locked: false}) !== 1) { + if ( + (await connection + .getRepository(AlbumBaseEntity) + .countBy({ id, locked: false })) !== 1 + ) { throw new Error('Could not delete album, id:' + id); } - await connection.getRepository(AlbumBaseEntity).delete({id, locked: false}); - + await connection + .getRepository(AlbumBaseEntity) + .delete({ id, locked: false }); } public async getAlbums(): Promise { await this.updateAlbums(); const connection = await SQLConnection.getConnection(); - return await connection.getRepository(AlbumBaseEntity) + return await connection + .getRepository(AlbumBaseEntity) .createQueryBuilder('album') .innerJoin('album.preview', 'preview') .innerJoin('preview.directory', 'directory') - .select(['album', 'preview.name', - 'directory.name', - 'directory.path']).getMany(); + .select(['album', 'preview.name', 'directory.name', 'directory.path']) + .getMany(); } public async onNewDataVersion(): Promise { @@ -94,5 +109,4 @@ export class AlbumManager implements IAlbumManager { } this.isDBValid = true; } - } diff --git a/src/backend/model/database/sql/GalleryManager.ts b/src/backend/model/database/sql/GalleryManager.ts index a436ef65..2f5615e6 100644 --- a/src/backend/model/database/sql/GalleryManager.ts +++ b/src/backend/model/database/sql/GalleryManager.ts @@ -1,84 +1,128 @@ -import {IGalleryManager} from '../interfaces/IGalleryManager'; -import {ParentDirectoryDTO, SubDirectoryDTO} from '../../../../common/entities/DirectoryDTO'; +import { IGalleryManager } from '../interfaces/IGalleryManager'; +import { + ParentDirectoryDTO, + SubDirectoryDTO, +} from '../../../../common/entities/DirectoryDTO'; import * as path from 'path'; import * as fs from 'fs'; -import {DirectoryEntity} from './enitites/DirectoryEntity'; -import {SQLConnection} from './SQLConnection'; -import {PhotoEntity} from './enitites/PhotoEntity'; -import {ProjectPath} from '../../../ProjectPath'; -import {Config} from '../../../../common/config/private/Config'; -import {ISQLGalleryManager} from './IGalleryManager'; -import {PhotoDTO} from '../../../../common/entities/PhotoDTO'; -import {Brackets, Connection, WhereExpression} from 'typeorm'; -import {MediaEntity} from './enitites/MediaEntity'; -import {VideoEntity} from './enitites/VideoEntity'; -import {DiskMangerWorker} from '../../threading/DiskMangerWorker'; -import {Logger} from '../../../Logger'; -import {FaceRegionEntry} from './enitites/FaceRegionEntry'; -import {ObjectManagers} from '../../ObjectManagers'; -import {DuplicatesDTO} from '../../../../common/entities/DuplicatesDTO'; -import {ReIndexingSensitivity} from '../../../../common/config/private/PrivateConfig'; - +import { DirectoryEntity } from './enitites/DirectoryEntity'; +import { SQLConnection } from './SQLConnection'; +import { PhotoEntity } from './enitites/PhotoEntity'; +import { ProjectPath } from '../../../ProjectPath'; +import { Config } from '../../../../common/config/private/Config'; +import { ISQLGalleryManager } from './IGalleryManager'; +import { PhotoDTO } from '../../../../common/entities/PhotoDTO'; +import { Connection } from 'typeorm'; +import { MediaEntity } from './enitites/MediaEntity'; +import { VideoEntity } from './enitites/VideoEntity'; +import { DiskMangerWorker } from '../../threading/DiskMangerWorker'; +import { Logger } from '../../../Logger'; +import { FaceRegionEntry } from './enitites/FaceRegionEntry'; +import { ObjectManagers } from '../../ObjectManagers'; +import { DuplicatesDTO } from '../../../../common/entities/DuplicatesDTO'; +import { ReIndexingSensitivity } from '../../../../common/config/private/PrivateConfig'; const LOG_TAG = '[GalleryManager]'; export class GalleryManager implements IGalleryManager, ISQLGalleryManager { - - public static parseRelativeDirePath(relativeDirectoryName: string): { name: string, parent: string } { - - relativeDirectoryName = DiskMangerWorker.normalizeDirPath(relativeDirectoryName); + public static parseRelativeDirePath(relativeDirectoryName: string): { + name: string; + parent: string; + } { + relativeDirectoryName = DiskMangerWorker.normalizeDirPath( + relativeDirectoryName + ); return { name: path.basename(relativeDirectoryName), parent: path.join(path.dirname(relativeDirectoryName), path.sep), }; } - public async listDirectory(relativeDirectoryName: string, - knownLastModified?: number, - knownLastScanned?: number): Promise { - const directoryPath = GalleryManager.parseRelativeDirePath(relativeDirectoryName); + public async listDirectory( + relativeDirectoryName: string, + knownLastModified?: number, + knownLastScanned?: number + ): Promise { + const directoryPath = GalleryManager.parseRelativeDirePath( + relativeDirectoryName + ); const connection = await SQLConnection.getConnection(); - const stat = fs.statSync(path.join(ProjectPath.ImageFolder, relativeDirectoryName)); + const stat = fs.statSync( + path.join(ProjectPath.ImageFolder, relativeDirectoryName) + ); const lastModified = DiskMangerWorker.calcLastModified(stat); - const dir = await this.selectParentDir(connection, directoryPath.name, directoryPath.parent); + const dir = await this.selectParentDir( + connection, + directoryPath.name, + directoryPath.parent + ); if (dir && dir.lastScanned != null) { // If it seems that the content did not changed, do not work on it - if (knownLastModified && knownLastScanned - && lastModified === knownLastModified && - dir.lastScanned === knownLastScanned) { - if (Config.Server.Indexing.reIndexingSensitivity === ReIndexingSensitivity.low) { + if ( + knownLastModified && + knownLastScanned && + lastModified === knownLastModified && + dir.lastScanned === knownLastScanned + ) { + if ( + Config.Server.Indexing.reIndexingSensitivity === + ReIndexingSensitivity.low + ) { return null; } - if (Date.now() - dir.lastScanned <= Config.Server.Indexing.cachedFolderTimeout && - Config.Server.Indexing.reIndexingSensitivity === ReIndexingSensitivity.medium) { + if ( + Date.now() - dir.lastScanned <= + Config.Server.Indexing.cachedFolderTimeout && + Config.Server.Indexing.reIndexingSensitivity === + ReIndexingSensitivity.medium + ) { return null; } } - if (dir.lastModified !== lastModified) { - Logger.silly(LOG_TAG, 'Reindexing reason: lastModified mismatch: known: ' - + dir.lastModified + ', current:' + lastModified); - const ret = await ObjectManagers.getInstance().IndexingManager.indexDirectory(relativeDirectoryName); + Logger.silly( + LOG_TAG, + 'Reindexing reason: lastModified mismatch: known: ' + + dir.lastModified + + ', current:' + + lastModified + ); + const ret = + await ObjectManagers.getInstance().IndexingManager.indexDirectory( + relativeDirectoryName + ); for (const subDir of ret.directories) { - if (!subDir.preview) { // if sub directories does not have photos, so cannot show a preview, try get one from DB + if (!subDir.preview) { + // if sub directories does not have photos, so cannot show a preview, try get one from DB await this.fillPreviewForSubDir(connection, subDir); } } return ret; } - // not indexed since a while, index it in a lazy manner - if ((Date.now() - dir.lastScanned > Config.Server.Indexing.cachedFolderTimeout && - Config.Server.Indexing.reIndexingSensitivity >= ReIndexingSensitivity.medium) || - Config.Server.Indexing.reIndexingSensitivity >= ReIndexingSensitivity.high) { + if ( + (Date.now() - dir.lastScanned > + Config.Server.Indexing.cachedFolderTimeout && + Config.Server.Indexing.reIndexingSensitivity >= + ReIndexingSensitivity.medium) || + Config.Server.Indexing.reIndexingSensitivity >= + ReIndexingSensitivity.high + ) { // on the fly reindexing - Logger.silly(LOG_TAG, 'lazy reindexing reason: cache timeout: lastScanned: ' - + (Date.now() - dir.lastScanned) + 'ms ago, cachedFolderTimeout:' + Config.Server.Indexing.cachedFolderTimeout); - ObjectManagers.getInstance().IndexingManager.indexDirectory(relativeDirectoryName).catch(console.error); + Logger.silly( + LOG_TAG, + 'lazy reindexing reason: cache timeout: lastScanned: ' + + (Date.now() - dir.lastScanned) + + 'ms ago, cachedFolderTimeout:' + + Config.Server.Indexing.cachedFolderTimeout + ); + ObjectManagers.getInstance() + .IndexingManager.indexDirectory(relativeDirectoryName) + .catch(console.error); } await this.fillParentDir(connection, dir); return dir; @@ -86,21 +130,23 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager { // never scanned (deep indexed), do it and return with it Logger.silly(LOG_TAG, 'Reindexing reason: never scanned'); - return ObjectManagers.getInstance().IndexingManager.indexDirectory(relativeDirectoryName); - - + return ObjectManagers.getInstance().IndexingManager.indexDirectory( + relativeDirectoryName + ); } async countDirectories(): Promise { const connection = await SQLConnection.getConnection(); - return await connection.getRepository(DirectoryEntity) + return await connection + .getRepository(DirectoryEntity) .createQueryBuilder('directory') .getCount(); } async countMediaSize(): Promise { const connection = await SQLConnection.getConnection(); - const {sum} = await connection.getRepository(MediaEntity) + const { sum } = await connection + .getRepository(MediaEntity) .createQueryBuilder('media') .select('SUM(media.metadata.fileSize)', 'sum') .getRawOne(); @@ -109,14 +155,16 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager { async countPhotos(): Promise { const connection = await SQLConnection.getConnection(); - return await connection.getRepository(PhotoEntity) + return await connection + .getRepository(PhotoEntity) .createQueryBuilder('directory') .getCount(); } async countVideos(): Promise { const connection = await SQLConnection.getConnection(); - return await connection.getRepository(VideoEntity) + return await connection + .getRepository(VideoEntity) .createQueryBuilder('directory') .getCount(); } @@ -125,22 +173,33 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager { const connection = await SQLConnection.getConnection(); const mediaRepository = connection.getRepository(MediaEntity); - let duplicates = await mediaRepository.createQueryBuilder('media') - .innerJoin((query): any => query.from(MediaEntity, 'innerMedia') - .select(['innerMedia.name as name', 'innerMedia.metadata.fileSize as fileSize', 'count(*)']) - .groupBy('innerMedia.name, innerMedia.metadata.fileSize') - .having('count(*)>1'), + let duplicates = await mediaRepository + .createQueryBuilder('media') + .innerJoin( + (query): any => + query + .from(MediaEntity, 'innerMedia') + .select([ + 'innerMedia.name as name', + 'innerMedia.metadata.fileSize as fileSize', + 'count(*)', + ]) + .groupBy('innerMedia.name, innerMedia.metadata.fileSize') + .having('count(*)>1'), 'innerMedia', - 'media.name=innerMedia.name AND media.metadata.fileSize = innerMedia.fileSize') + 'media.name=innerMedia.name AND media.metadata.fileSize = innerMedia.fileSize' + ) .innerJoinAndSelect('media.directory', 'directory') .orderBy('media.name, media.metadata.fileSize') - .limit(Config.Server.Duplicates.listingLimit).getMany(); - + .limit(Config.Server.Duplicates.listingLimit) + .getMany(); const duplicateParis: DuplicatesDTO[] = []; - const processDuplicates = (duplicateList: MediaEntity[], - equalFn: (a: MediaEntity, b: MediaEntity) => boolean, - checkDuplicates: boolean = false): void => { + const processDuplicates = ( + duplicateList: MediaEntity[], + equalFn: (a: MediaEntity, b: MediaEntity) => boolean, + checkDuplicates: boolean = false + ): void => { let i = duplicateList.length - 1; while (i >= 0) { const list = [duplicateList[i]]; @@ -156,12 +215,17 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager { } if (checkDuplicates) { // ad to group if one already existed - const foundDuplicates = duplicateParis.find((dp): boolean => - !!dp.media.find((m): boolean => - !!list.find((lm): boolean => lm.id === m.id))); + const foundDuplicates = duplicateParis.find( + (dp): boolean => + !!dp.media.find( + (m): boolean => !!list.find((lm): boolean => lm.id === m.id) + ) + ); if (foundDuplicates) { list.forEach((lm): void => { - if (!!foundDuplicates.media.find((m): boolean => m.id === lm.id)) { + if ( + !!foundDuplicates.media.find((m): boolean => m.id === lm.id) + ) { return; } foundDuplicates.media.push(lm); @@ -170,117 +234,160 @@ export class GalleryManager implements IGalleryManager, ISQLGalleryManager { } } - duplicateParis.push({media: list}); + duplicateParis.push({ media: list }); } }; - processDuplicates(duplicates, - (a, b): boolean => a.name === b.name && - a.metadata.fileSize === b.metadata.fileSize); + processDuplicates( + duplicates, + (a, b): boolean => + a.name === b.name && a.metadata.fileSize === b.metadata.fileSize + ); - - duplicates = await mediaRepository.createQueryBuilder('media') - .innerJoin((query): any => query.from(MediaEntity, 'innerMedia') - .select(['innerMedia.metadata.creationDate as creationDate', 'innerMedia.metadata.fileSize as fileSize', 'count(*)']) - .groupBy('innerMedia.metadata.creationDate, innerMedia.metadata.fileSize') - .having('count(*)>1'), + duplicates = await mediaRepository + .createQueryBuilder('media') + .innerJoin( + (query): any => + query + .from(MediaEntity, 'innerMedia') + .select([ + 'innerMedia.metadata.creationDate as creationDate', + 'innerMedia.metadata.fileSize as fileSize', + 'count(*)', + ]) + .groupBy( + 'innerMedia.metadata.creationDate, innerMedia.metadata.fileSize' + ) + .having('count(*)>1'), 'innerMedia', - 'media.metadata.creationDate=innerMedia.creationDate AND media.metadata.fileSize = innerMedia.fileSize') + 'media.metadata.creationDate=innerMedia.creationDate AND media.metadata.fileSize = innerMedia.fileSize' + ) .innerJoinAndSelect('media.directory', 'directory') .orderBy('media.metadata.creationDate, media.metadata.fileSize') - .limit(Config.Server.Duplicates.listingLimit).getMany(); + .limit(Config.Server.Duplicates.listingLimit) + .getMany(); - processDuplicates(duplicates, - (a, b): boolean => a.metadata.creationDate === b.metadata.creationDate && - a.metadata.fileSize === b.metadata.fileSize, true); + processDuplicates( + duplicates, + (a, b): boolean => + a.metadata.creationDate === b.metadata.creationDate && + a.metadata.fileSize === b.metadata.fileSize, + true + ); return duplicateParis; - } /** * Returns with the directories only, does not include media or metafiles */ - public async selectDirStructure(relativeDirectoryName: string): Promise { - const directoryPath = GalleryManager.parseRelativeDirePath(relativeDirectoryName); + public async selectDirStructure( + relativeDirectoryName: string + ): Promise { + const directoryPath = GalleryManager.parseRelativeDirePath( + relativeDirectoryName + ); const connection = await SQLConnection.getConnection(); const query = connection .getRepository(DirectoryEntity) .createQueryBuilder('directory') .where('directory.name = :name AND directory.path = :path', { name: directoryPath.name, - path: directoryPath.parent + path: directoryPath.parent, }) .leftJoinAndSelect('directory.directories', 'directories'); return await query.getOne(); } - /** * Sets preview for the directory and caches it in the DB */ - public async fillPreviewForSubDir(connection: Connection, dir: SubDirectoryDTO): Promise { - + public async fillPreviewForSubDir( + connection: Connection, + dir: SubDirectoryDTO + ): Promise { if (!dir.validPreview) { - dir.preview = await ObjectManagers.getInstance().PreviewManager.setAndGetPreviewForDirectory(dir); + dir.preview = + await ObjectManagers.getInstance().PreviewManager.setAndGetPreviewForDirectory( + dir + ); } - dir.media = []; dir.isPartial = true; } - - protected async selectParentDir(connection: Connection, directoryName: string, directoryParent: string): Promise { + protected async selectParentDir( + connection: Connection, + directoryName: string, + directoryParent: string + ): Promise { const query = connection .getRepository(DirectoryEntity) .createQueryBuilder('directory') .where('directory.name = :name AND directory.path = :path', { name: directoryName, - path: directoryParent + path: directoryParent, }) .leftJoinAndSelect('directory.directories', 'directories') .leftJoinAndSelect('directory.media', 'media') .leftJoinAndSelect('directories.preview', 'preview') .leftJoinAndSelect('preview.directory', 'previewDirectory') - .select(['directory', + .select([ + 'directory', 'directories', 'media', 'preview.name', 'previewDirectory.name', - 'previewDirectory.path']); - + 'previewDirectory.path', + ]); // TODO: do better filtering // NOTE: it should not cause an issue as it also do not shave to the DB - if (Config.Client.MetaFile.gpx === true || + if ( + Config.Client.MetaFile.gpx === true || Config.Client.MetaFile.pg2conf === true || - Config.Client.MetaFile.markdown === true) { + Config.Client.MetaFile.markdown === true + ) { query.leftJoinAndSelect('directory.metaFile', 'metaFile'); } return await query.getOne(); } - protected async fillParentDir(connection: Connection, dir: ParentDirectoryDTO): Promise { + protected async fillParentDir( + connection: Connection, + dir: ParentDirectoryDTO + ): Promise { if (dir.media) { - const indexedFaces = await connection.getRepository(FaceRegionEntry) + const indexedFaces = await connection + .getRepository(FaceRegionEntry) .createQueryBuilder('face') .leftJoinAndSelect('face.media', 'media') .where('media.directory = :directory', { - directory: dir.id + directory: dir.id, }) .leftJoinAndSelect('face.person', 'person') - .select(['face.id', 'face.box.left', - 'face.box.top', 'face.box.width', 'face.box.height', - 'media.id', 'person.name', 'person.id']) + .select([ + 'face.id', + 'face.box.left', + 'face.box.top', + 'face.box.width', + 'face.box.height', + 'media.id', + 'person.name', + 'person.id', + ]) .getMany(); for (const item of dir.media) { item.directory = dir; (item as PhotoDTO).metadata.faces = indexedFaces .filter((fe): boolean => fe.media.id === item.id) - .map((f): { name: any; box: any } => ({box: f.box, name: f.person.name})); + .map((f): { name: any; box: any } => ({ + box: f.box, + name: f.person.name, + })); } } if (dir.metaFile) { diff --git a/src/backend/model/database/sql/IGalleryManager.ts b/src/backend/model/database/sql/IGalleryManager.ts index 46d76c48..2710da65 100644 --- a/src/backend/model/database/sql/IGalleryManager.ts +++ b/src/backend/model/database/sql/IGalleryManager.ts @@ -1,14 +1,16 @@ -import {ParentDirectoryDTO} from '../../../../common/entities/DirectoryDTO'; -import {IGalleryManager} from '../interfaces/IGalleryManager'; -import {DuplicatesDTO} from '../../../../common/entities/DuplicatesDTO'; -import {Connection} from 'typeorm'; -import {DirectoryEntity} from './enitites/DirectoryEntity'; -import {FileDTO} from '../../../../common/entities/FileDTO'; +import { ParentDirectoryDTO } from '../../../../common/entities/DirectoryDTO'; +import { IGalleryManager } from '../interfaces/IGalleryManager'; +import { DuplicatesDTO } from '../../../../common/entities/DuplicatesDTO'; +import { Connection } from 'typeorm'; +import { DirectoryEntity } from './enitites/DirectoryEntity'; +import { FileDTO } from '../../../../common/entities/FileDTO'; export interface ISQLGalleryManager extends IGalleryManager { - listDirectory(relativeDirectoryName: string, - knownLastModified?: number, - knownLastScanned?: number): Promise; + listDirectory( + relativeDirectoryName: string, + knownLastModified?: number, + knownLastScanned?: number + ): Promise; countDirectories(): Promise; @@ -22,5 +24,8 @@ export interface ISQLGalleryManager extends IGalleryManager { selectDirStructure(directory: string): Promise>; - fillPreviewForSubDir(connection: Connection, dir: DirectoryEntity): Promise; + fillPreviewForSubDir( + connection: Connection, + dir: DirectoryEntity + ): Promise; } diff --git a/src/backend/model/database/sql/IPersonManager.ts b/src/backend/model/database/sql/IPersonManager.ts index 82fb0449..62ca338d 100644 --- a/src/backend/model/database/sql/IPersonManager.ts +++ b/src/backend/model/database/sql/IPersonManager.ts @@ -1,4 +1,4 @@ -import {IPersonManager} from '../interfaces/IPersonManager'; +import { IPersonManager } from '../interfaces/IPersonManager'; export interface ISQLPersonManager extends IPersonManager { countFaces(): Promise; diff --git a/src/backend/model/database/sql/ISearchManager.ts b/src/backend/model/database/sql/ISearchManager.ts index 306c4d74..77b4114c 100644 --- a/src/backend/model/database/sql/ISearchManager.ts +++ b/src/backend/model/database/sql/ISearchManager.ts @@ -1,12 +1,18 @@ -import {SearchQueryDTO, SearchQueryTypes} from '../../../../common/entities/SearchQueryDTO'; -import {ISearchManager} from '../interfaces/ISearchManager'; -import {AutoCompleteItem} from '../../../../common/entities/AutoCompleteItem'; -import {SearchResultDTO} from '../../../../common/entities/SearchResultDTO'; -import {PhotoDTO} from '../../../../common/entities/PhotoDTO'; -import {Brackets} from 'typeorm'; +import { + SearchQueryDTO, + SearchQueryTypes, +} from '../../../../common/entities/SearchQueryDTO'; +import { ISearchManager } from '../interfaces/ISearchManager'; +import { AutoCompleteItem } from '../../../../common/entities/AutoCompleteItem'; +import { SearchResultDTO } from '../../../../common/entities/SearchResultDTO'; +import { PhotoDTO } from '../../../../common/entities/PhotoDTO'; +import { Brackets } from 'typeorm'; export interface ISQLSearchManager extends ISearchManager { - autocomplete(text: string, type: SearchQueryTypes): Promise; + autocomplete( + text: string, + type: SearchQueryTypes + ): Promise; search(query: SearchQueryDTO): Promise; @@ -15,5 +21,8 @@ export interface ISQLSearchManager extends ISearchManager { // "Protected" functions. only called from other Managers, not from middlewares getCount(query: SearchQueryDTO): Promise; - prepareAndBuildWhereQuery(query: SearchQueryDTO, directoryOnly?: boolean): Promise; + prepareAndBuildWhereQuery( + query: SearchQueryDTO, + directoryOnly?: boolean + ): Promise; } diff --git a/src/backend/model/database/sql/IndexingManager.ts b/src/backend/model/database/sql/IndexingManager.ts index fa4ef181..f7ec251e 100644 --- a/src/backend/model/database/sql/IndexingManager.ts +++ b/src/backend/model/database/sql/IndexingManager.ts @@ -1,33 +1,38 @@ -import {ParentDirectoryDTO} from '../../../../common/entities/DirectoryDTO'; -import {DirectoryEntity} from './enitites/DirectoryEntity'; -import {SQLConnection} from './SQLConnection'; -import {DiskManager} from '../../DiskManger'; -import {PhotoEntity, PhotoMetadataEntity} from './enitites/PhotoEntity'; -import {Utils} from '../../../../common/Utils'; -import {FaceRegion, PhotoMetadata} from '../../../../common/entities/PhotoDTO'; -import {Connection, Repository} from 'typeorm'; -import {MediaEntity} from './enitites/MediaEntity'; -import {MediaDTO, MediaDTOUtils} from '../../../../common/entities/MediaDTO'; -import {VideoEntity} from './enitites/VideoEntity'; -import {FileEntity} from './enitites/FileEntity'; -import {FileDTO} from '../../../../common/entities/FileDTO'; -import {NotificationManager} from '../../NotifocationManager'; -import {FaceRegionEntry} from './enitites/FaceRegionEntry'; -import {ObjectManagers} from '../../ObjectManagers'; -import {IIndexingManager} from '../interfaces/IIndexingManager'; -import {DiskMangerWorker} from '../../threading/DiskMangerWorker'; -import {Logger} from '../../../Logger'; -import {ServerPG2ConfMap, ServerSidePG2ConfAction} from '../../../../common/PG2ConfMap'; -import {ProjectPath} from '../../../ProjectPath'; +import { ParentDirectoryDTO } from '../../../../common/entities/DirectoryDTO'; +import { DirectoryEntity } from './enitites/DirectoryEntity'; +import { SQLConnection } from './SQLConnection'; +import { DiskManager } from '../../DiskManger'; +import { PhotoEntity, PhotoMetadataEntity } from './enitites/PhotoEntity'; +import { Utils } from '../../../../common/Utils'; +import { + FaceRegion, + PhotoMetadata, +} from '../../../../common/entities/PhotoDTO'; +import { Connection, Repository } from 'typeorm'; +import { MediaEntity } from './enitites/MediaEntity'; +import { MediaDTO, MediaDTOUtils } from '../../../../common/entities/MediaDTO'; +import { VideoEntity } from './enitites/VideoEntity'; +import { FileEntity } from './enitites/FileEntity'; +import { FileDTO } from '../../../../common/entities/FileDTO'; +import { NotificationManager } from '../../NotifocationManager'; +import { FaceRegionEntry } from './enitites/FaceRegionEntry'; +import { ObjectManagers } from '../../ObjectManagers'; +import { IIndexingManager } from '../interfaces/IIndexingManager'; +import { DiskMangerWorker } from '../../threading/DiskMangerWorker'; +import { Logger } from '../../../Logger'; +import { + ServerPG2ConfMap, + ServerSidePG2ConfAction, +} from '../../../../common/PG2ConfMap'; +import { ProjectPath } from '../../../ProjectPath'; import * as path from 'path'; import * as fs from 'fs'; -import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO'; -import {PersonEntry} from './enitites/PersonEntry'; +import { SearchQueryDTO } from '../../../../common/entities/SearchQueryDTO'; +import { PersonEntry } from './enitites/PersonEntry'; const LOG_TAG = '[IndexingManager]'; export class IndexingManager implements IIndexingManager { - SavingReady: Promise = null; private SavingReadyPR: () => void = null; private savingQueue: ParentDirectoryDTO[] = []; @@ -37,16 +42,31 @@ export class IndexingManager implements IIndexingManager { return this.SavingReady !== null; } - private static async processServerSidePG2Conf(files: FileDTO[]): Promise { + private static async processServerSidePG2Conf( + files: FileDTO[] + ): Promise { for (const f of files) { if (ServerPG2ConfMap[f.name] === ServerSidePG2ConfAction.SAVED_SEARCH) { - const fullMediaPath = path.join(ProjectPath.ImageFolder, f.directory.path, f.directory.name, f.name); + const fullMediaPath = path.join( + ProjectPath.ImageFolder, + f.directory.path, + f.directory.name, + f.name + ); - Logger.silly(LOG_TAG, 'Saving saved-searches to DB from:', fullMediaPath); - const savedSearches: { name: string, searchQuery: SearchQueryDTO }[] = + Logger.silly( + LOG_TAG, + 'Saving saved-searches to DB from:', + fullMediaPath + ); + const savedSearches: { name: string; searchQuery: SearchQueryDTO }[] = JSON.parse(await fs.promises.readFile(fullMediaPath, 'utf8')); for (const s of savedSearches) { - await ObjectManagers.getInstance().AlbumManager.addIfNotExistSavedSearch(s.name, s.searchQuery, true); + await ObjectManagers.getInstance().AlbumManager.addIfNotExistSavedSearch( + s.name, + s.searchQuery, + true + ); } } } @@ -56,26 +76,34 @@ export class IndexingManager implements IIndexingManager { * Indexes a dir, but returns early with the scanned version, * does not wait for the DB to be saved */ - public indexDirectory(relativeDirectoryName: string): Promise { + public indexDirectory( + relativeDirectoryName: string + ): Promise { + // eslint-disable-next-line no-async-promise-executor return new Promise(async (resolve, reject): Promise => { try { - const scannedDirectory = await DiskManager.scanDirectory(relativeDirectoryName); + const scannedDirectory = await DiskManager.scanDirectory( + relativeDirectoryName + ); const dirClone = Utils.shallowClone(scannedDirectory); // filter server side only config from returning - dirClone.metaFile = dirClone.metaFile.filter(m => !ServerPG2ConfMap[m.name]); + dirClone.metaFile = dirClone.metaFile.filter( + (m) => !ServerPG2ConfMap[m.name] + ); resolve(dirClone); // save directory to DB this.queueForSave(scannedDirectory).catch(console.error); - } catch (error) { - NotificationManager.warning('Unknown indexing error for: ' + relativeDirectoryName, error.toString()); + NotificationManager.warning( + 'Unknown indexing error for: ' + relativeDirectoryName, + error.toString() + ); console.error(error); return reject(error); } - }); } @@ -93,9 +121,16 @@ export class IndexingManager implements IIndexingManager { this.isSaving = true; try { const connection = await SQLConnection.getConnection(); - const serverSideConfigs = scannedDirectory.metaFile.filter(m => !!ServerPG2ConfMap[m.name]); - scannedDirectory.metaFile = scannedDirectory.metaFile.filter(m => !ServerPG2ConfMap[m.name]); - const currentDirId: number = await this.saveParentDir(connection, scannedDirectory); + const serverSideConfigs = scannedDirectory.metaFile.filter( + (m) => !!ServerPG2ConfMap[m.name] + ); + scannedDirectory.metaFile = scannedDirectory.metaFile.filter( + (m) => !ServerPG2ConfMap[m.name] + ); + const currentDirId: number = await this.saveParentDir( + connection, + scannedDirectory + ); await this.saveChildDirs(connection, currentDirId, scannedDirectory); await this.saveMedia(connection, currentDirId, scannedDirectory.media); await this.saveMetaFiles(connection, currentDirId, scannedDirectory); @@ -110,14 +145,23 @@ export class IndexingManager implements IIndexingManager { /** * Queues up a directory to save to the DB. */ - protected async queueForSave(scannedDirectory: ParentDirectoryDTO): Promise { + protected async queueForSave( + scannedDirectory: ParentDirectoryDTO + ): Promise { // Is this dir already queued for saving? - if (this.savingQueue.findIndex((dir): boolean => dir.name === scannedDirectory.name && - dir.path === scannedDirectory.path && - dir.lastModified === scannedDirectory.lastModified && - dir.lastScanned === scannedDirectory.lastScanned && - (dir.media || dir.media.length) === (scannedDirectory.media || scannedDirectory.media.length) && - (dir.metaFile || dir.metaFile.length) === (scannedDirectory.metaFile || scannedDirectory.metaFile.length)) !== -1) { + if ( + this.savingQueue.findIndex( + (dir): boolean => + dir.name === scannedDirectory.name && + dir.path === scannedDirectory.path && + dir.lastModified === scannedDirectory.lastModified && + dir.lastScanned === scannedDirectory.lastScanned && + (dir.media || dir.media.length) === + (scannedDirectory.media || scannedDirectory.media.length) && + (dir.metaFile || dir.metaFile.length) === + (scannedDirectory.metaFile || scannedDirectory.metaFile.length) + ) !== -1 + ) { return; } this.savingQueue.push(scannedDirectory); @@ -139,86 +183,114 @@ export class IndexingManager implements IIndexingManager { this.SavingReady = null; this.SavingReadyPR(); } - } - } - protected async saveParentDir(connection: Connection, scannedDirectory: ParentDirectoryDTO): Promise { + protected async saveParentDir( + connection: Connection, + scannedDirectory: ParentDirectoryDTO + ): Promise { const directoryRepository = connection.getRepository(DirectoryEntity); - const currentDir: DirectoryEntity = await directoryRepository.createQueryBuilder('directory') + const currentDir: DirectoryEntity = await directoryRepository + .createQueryBuilder('directory') .where('directory.name = :name AND directory.path = :path', { name: scannedDirectory.name, - path: scannedDirectory.path - }).getOne(); - if (!!currentDir) {// Updated parent dir (if it was in the DB previously) + path: scannedDirectory.path, + }) + .getOne(); + if (currentDir) { + // Updated parent dir (if it was in the DB previously) currentDir.lastModified = scannedDirectory.lastModified; currentDir.lastScanned = scannedDirectory.lastScanned; currentDir.mediaCount = scannedDirectory.mediaCount; await directoryRepository.save(currentDir); return currentDir.id; - } else { - return (await directoryRepository.insert({ - mediaCount: scannedDirectory.mediaCount, - lastModified: scannedDirectory.lastModified, - lastScanned: scannedDirectory.lastScanned, - name: scannedDirectory.name, - path: scannedDirectory.path - } as DirectoryEntity)).identifiers[0].id; + return ( + await directoryRepository.insert({ + mediaCount: scannedDirectory.mediaCount, + lastModified: scannedDirectory.lastModified, + lastScanned: scannedDirectory.lastScanned, + name: scannedDirectory.name, + path: scannedDirectory.path, + } as DirectoryEntity) + ).identifiers[0]['id']; } } - protected async saveChildDirs(connection: Connection, currentDirId: number, scannedDirectory: ParentDirectoryDTO): Promise { + protected async saveChildDirs( + connection: Connection, + currentDirId: number, + scannedDirectory: ParentDirectoryDTO + ): Promise { const directoryRepository = connection.getRepository(DirectoryEntity); // update subdirectories that does not have a parent await directoryRepository .createQueryBuilder() .update(DirectoryEntity) - .set({parent: currentDirId as any}) - .where('path = :path', - {path: DiskMangerWorker.pathFromParent(scannedDirectory)}) - .andWhere('name NOT LIKE :root', {root: DiskMangerWorker.dirName('.')}) + .set({ parent: currentDirId as any }) + .where('path = :path', { + path: DiskMangerWorker.pathFromParent(scannedDirectory), + }) + .andWhere('name NOT LIKE :root', { root: DiskMangerWorker.dirName('.') }) .andWhere('parent IS NULL') .execute(); // save subdirectories - const childDirectories = await directoryRepository.createQueryBuilder('directory') + const childDirectories = await directoryRepository + .createQueryBuilder('directory') .leftJoinAndSelect('directory.parent', 'parent') .where('directory.parent = :dir', { - dir: currentDirId - }).getMany(); + dir: currentDirId, + }) + .getMany(); for (const directory of scannedDirectory.directories) { // Was this child Dir already indexed before? - const dirIndex = childDirectories.findIndex((d): boolean => d.name === directory.name); + const dirIndex = childDirectories.findIndex( + (d): boolean => d.name === directory.name + ); - if (dirIndex !== -1) { // directory found + if (dirIndex !== -1) { + // directory found childDirectories.splice(dirIndex, 1); - } else { // dir does not exists yet - directory.parent = ({id: currentDirId} as any); + } else { + // dir does not exists yet + directory.parent = { id: currentDirId } as any; (directory as DirectoryEntity).lastScanned = null; // new child dir, not fully scanned yet - const d = await directoryRepository.insert(directory as DirectoryEntity); + const d = await directoryRepository.insert( + directory as DirectoryEntity + ); - await this.saveMedia(connection, d.identifiers[0].id, directory.media); + await this.saveMedia( + connection, + d.identifiers[0]['id'], + directory.media + ); } } // Remove child Dirs that are not anymore in the parent dir - await directoryRepository.remove(childDirectories, {chunk: Math.max(Math.ceil(childDirectories.length / 500), 1)}); - + await directoryRepository.remove(childDirectories, { + chunk: Math.max(Math.ceil(childDirectories.length / 500), 1), + }); } - protected async saveMetaFiles(connection: Connection, currentDirID: number, scannedDirectory: ParentDirectoryDTO): Promise { + protected async saveMetaFiles( + connection: Connection, + currentDirID: number, + scannedDirectory: ParentDirectoryDTO + ): Promise { const fileRepository = connection.getRepository(FileEntity); // save files - const indexedMetaFiles = await fileRepository.createQueryBuilder('file') + const indexedMetaFiles = await fileRepository + .createQueryBuilder('file') .where('file.directory = :dir', { - dir: currentDirID - }).getMany(); - + dir: currentDirID, + }) + .getMany(); const metaFilesToSave = []; for (const item of scannedDirectory.metaFile) { @@ -230,37 +302,47 @@ export class IndexingManager implements IIndexingManager { break; } } - if (metaFile == null) { // not in DB yet + if (metaFile == null) { + // not in DB yet item.directory = null; metaFile = Utils.clone(item); item.directory = scannedDirectory; - metaFile.directory = ({id: currentDirID} as any); + metaFile.directory = { id: currentDirID } as any; metaFilesToSave.push(metaFile); } } - await fileRepository.save(metaFilesToSave, {chunk: Math.max(Math.ceil(metaFilesToSave.length / 500), 1)}); - await fileRepository.remove(indexedMetaFiles, {chunk: Math.max(Math.ceil(indexedMetaFiles.length / 500), 1)}); + await fileRepository.save(metaFilesToSave, { + chunk: Math.max(Math.ceil(metaFilesToSave.length / 500), 1), + }); + await fileRepository.remove(indexedMetaFiles, { + chunk: Math.max(Math.ceil(indexedMetaFiles.length / 500), 1), + }); } - protected async saveMedia(connection: Connection, parentDirId: number, media: MediaDTO[]): Promise { + protected async saveMedia( + connection: Connection, + parentDirId: number, + media: MediaDTO[] + ): Promise { const mediaRepository = connection.getRepository(MediaEntity); const photoRepository = connection.getRepository(PhotoEntity); const videoRepository = connection.getRepository(VideoEntity); // save media - let indexedMedia = (await mediaRepository.createQueryBuilder('media') + let indexedMedia = await mediaRepository + .createQueryBuilder('media') .where('media.directory = :dir', { - dir: parentDirId + dir: parentDirId, }) - .getMany()); + .getMany(); const mediaChange: any = { saveP: [], // save/update photo saveV: [], // save/update video insertP: [], // insert photo - insertV: [] // insert video + insertV: [], // insert video }; - const facesPerPhoto: { faces: FaceRegionEntry[], mediaName: string }[] = []; - // tslint:disable-next-line:prefer-for-of + const facesPerPhoto: { faces: FaceRegionEntry[]; mediaName: string }[] = []; + // eslint-disable-next-line @typescript-eslint/prefer-for-of for (let i = 0; i < media.length; i++) { let mediaItem: MediaEntity = null; for (let j = 0; j < indexedMedia.length; j++) { @@ -272,27 +354,42 @@ export class IndexingManager implements IIndexingManager { } const scannedFaces = (media[i].metadata as PhotoMetadata).faces || []; - if ((media[i].metadata as PhotoMetadata).faces) { // if it has faces, cache them + if ((media[i].metadata as PhotoMetadata).faces) { + // if it has faces, cache them // make the list distinct (some photos may contain the same person multiple times) - (media[i].metadata as PhotoMetadataEntity).persons = [...new Set((media[i].metadata as PhotoMetadata).faces.map(f => f.name))]; + (media[i].metadata as PhotoMetadataEntity).persons = [ + ...new Set( + (media[i].metadata as PhotoMetadata).faces.map((f) => f.name) + ), + ]; } delete (media[i].metadata as PhotoMetadata).faces; // this is a separated DB, lets save separately - if (mediaItem == null) { // not in DB yet + if (mediaItem == null) { + // not in DB yet media[i].directory = null; - mediaItem = (Utils.clone(media[i]) as any); - mediaItem.directory = ({id: parentDirId} as any); - (MediaDTOUtils.isPhoto(mediaItem) ? mediaChange.insertP : mediaChange.insertV).push(mediaItem); - } else { // already in the DB, only needs to be updated + mediaItem = Utils.clone(media[i]) as any; + mediaItem.directory = { id: parentDirId } as any; + (MediaDTOUtils.isPhoto(mediaItem) + ? mediaChange.insertP + : mediaChange.insertV + ).push(mediaItem); + } else { + // already in the DB, only needs to be updated delete (mediaItem.metadata as PhotoMetadata).faces; if (!Utils.equalsFilter(mediaItem.metadata, media[i].metadata)) { - mediaItem.metadata = (media[i].metadata as any); - (MediaDTOUtils.isPhoto(mediaItem) ? mediaChange.saveP : mediaChange.saveV).push(mediaItem); - + mediaItem.metadata = media[i].metadata as any; + (MediaDTOUtils.isPhoto(mediaItem) + ? mediaChange.saveP + : mediaChange.saveV + ).push(mediaItem); } } - facesPerPhoto.push({faces: scannedFaces as FaceRegionEntry[], mediaName: mediaItem.name}); + facesPerPhoto.push({ + faces: scannedFaces as FaceRegionEntry[], + mediaName: mediaItem.name, + }); } await this.saveChunk(photoRepository, mediaChange.saveP, 100); @@ -300,17 +397,23 @@ export class IndexingManager implements IIndexingManager { await this.saveChunk(photoRepository, mediaChange.insertP, 100); await this.saveChunk(videoRepository, mediaChange.insertV, 100); - indexedMedia = (await mediaRepository.createQueryBuilder('media') + indexedMedia = await mediaRepository + .createQueryBuilder('media') .where('media.directory = :dir', { - dir: parentDirId + dir: parentDirId, }) .select(['media.name', 'media.id']) - .getMany()); + .getMany(); const faces: FaceRegionEntry[] = []; facesPerPhoto.forEach((group): void => { - const mIndex = indexedMedia.findIndex((m): boolean => m.name === group.mediaName); - group.faces.forEach((sf: FaceRegionEntry): any => sf.media = ({id: indexedMedia[mIndex].id} as any)); + const mIndex = indexedMedia.findIndex( + (m): boolean => m.name === group.mediaName + ); + group.faces.forEach( + (sf: FaceRegionEntry): any => + (sf.media = { id: indexedMedia[mIndex].id } as any) + ); faces.push(...group.faces); indexedMedia.splice(mIndex, 1); @@ -320,42 +423,47 @@ export class IndexingManager implements IIndexingManager { await mediaRepository.remove(indexedMedia); } - protected async saveFaces(connection: Connection, parentDirId: number, scannedFaces: FaceRegion[]): Promise { + protected async saveFaces( + connection: Connection, + parentDirId: number, + scannedFaces: FaceRegion[] + ): Promise { const faceRepository = connection.getRepository(FaceRegionEntry); const personRepository = connection.getRepository(PersonEntry); - const persons: { name: string, faceRegion: FaceRegion }[] = []; + const persons: { name: string; faceRegion: FaceRegion }[] = []; for (const face of scannedFaces) { - if (persons.findIndex(f => f.name === face.name) === -1) { - persons.push({name: face.name, faceRegion: face}); + if (persons.findIndex((f) => f.name === face.name) === -1) { + persons.push({ name: face.name, faceRegion: face }); } } await ObjectManagers.getInstance().PersonManager.saveAll(persons); // get saved persons without triggering denormalized data update (i.e.: do not use PersonManager.get). const savedPersons = await personRepository.find(); - const indexedFaces = await faceRepository.createQueryBuilder('face') + const indexedFaces = await faceRepository + .createQueryBuilder('face') .leftJoin('face.media', 'media') .where('media.directory = :directory', { - directory: parentDirId + directory: parentDirId, }) .leftJoinAndSelect('face.person', 'person') .getMany(); - const faceToInsert = []; - // tslint:disable-next-line:prefer-for-of + // eslint-disable-next-line @typescript-eslint/prefer-for-of for (let i = 0; i < scannedFaces.length; i++) { - // was the face region already indexed let face: FaceRegionEntry = null; for (let j = 0; j < indexedFaces.length; j++) { - if (indexedFaces[j].box.height === scannedFaces[i].box.height && + if ( + indexedFaces[j].box.height === scannedFaces[i].box.height && indexedFaces[j].box.width === scannedFaces[i].box.width && indexedFaces[j].box.left === scannedFaces[i].box.left && indexedFaces[j].box.top === scannedFaces[i].box.top && - indexedFaces[j].person.name === scannedFaces[i].name) { + indexedFaces[j].person.name === scannedFaces[i].name + ) { face = indexedFaces[j]; indexedFaces.splice(j, 1); break; // region found, stop processing @@ -363,18 +471,25 @@ export class IndexingManager implements IIndexingManager { } if (face == null) { - (scannedFaces[i] as FaceRegionEntry).person = savedPersons.find(p => p.name === scannedFaces[i].name); + (scannedFaces[i] as FaceRegionEntry).person = savedPersons.find( + (p) => p.name === scannedFaces[i].name + ); faceToInsert.push(scannedFaces[i]); } } if (faceToInsert.length > 0) { await this.insertChunk(faceRepository, faceToInsert, 100); } - await faceRepository.remove(indexedFaces, {chunk: Math.max(Math.ceil(indexedFaces.length / 500), 1)}); - + await faceRepository.remove(indexedFaces, { + chunk: Math.max(Math.ceil(indexedFaces.length / 500), 1), + }); } - private async saveChunk(repository: Repository, entities: T[], size: number): Promise { + private async saveChunk( + repository: Repository, + entities: T[], + size: number + ): Promise { if (entities.length === 0) { return []; } @@ -383,21 +498,33 @@ export class IndexingManager implements IIndexingManager { } let list: T[] = []; for (let i = 0; i < entities.length / size; i++) { - list = list.concat(await repository.save(entities.slice(i * size, (i + 1) * size))); + list = list.concat( + await repository.save(entities.slice(i * size, (i + 1) * size)) + ); } return list; } - private async insertChunk(repository: Repository, entities: T[], size: number): Promise { + private async insertChunk( + repository: Repository, + entities: T[], + size: number + ): Promise { if (entities.length === 0) { return []; } if (entities.length < size) { - return (await repository.insert(entities)).identifiers.map((i: any) => i.id); + return (await repository.insert(entities)).identifiers.map( + (i: any) => i.id + ); } let list: number[] = []; for (let i = 0; i < entities.length / size; i++) { - list = list.concat((await repository.insert(entities.slice(i * size, (i + 1) * size))).identifiers.map(ids => ids.id)); + list = list.concat( + ( + await repository.insert(entities.slice(i * size, (i + 1) * size)) + ).identifiers.map((ids) => ids['id']) + ); } return list; } diff --git a/src/backend/model/database/sql/PersonManager.ts b/src/backend/model/database/sql/PersonManager.ts index c7f7eb5a..85d456d3 100644 --- a/src/backend/model/database/sql/PersonManager.ts +++ b/src/backend/model/database/sql/PersonManager.ts @@ -1,12 +1,11 @@ -import {SQLConnection} from './SQLConnection'; -import {PersonEntry} from './enitites/PersonEntry'; -import {FaceRegionEntry} from './enitites/FaceRegionEntry'; -import {PersonDTO} from '../../../../common/entities/PersonDTO'; -import {ISQLPersonManager} from './IPersonManager'; -import {Logger} from '../../../Logger'; -import {FaceRegion} from '../../../../common/entities/PhotoDTO'; -import {SQL_COLLATE} from './enitites/EntityUtils'; - +import { SQLConnection } from './SQLConnection'; +import { PersonEntry } from './enitites/PersonEntry'; +import { FaceRegionEntry } from './enitites/FaceRegionEntry'; +import { PersonDTO } from '../../../../common/entities/PersonDTO'; +import { ISQLPersonManager } from './IPersonManager'; +import { Logger } from '../../../Logger'; +import { FaceRegion } from '../../../../common/entities/PhotoDTO'; +import { SQL_COLLATE } from './enitites/EntityUtils'; const LOG_TAG = '[PersonManager]'; @@ -19,8 +18,10 @@ export class PersonManager implements ISQLPersonManager { private static async updateCounts(): Promise { const connection = await SQLConnection.getConnection(); - await connection.query('UPDATE person_entry SET count = ' + - ' (SELECT COUNT(1) FROM face_region_entry WHERE face_region_entry.personId = person_entry.id)'); + await connection.query( + 'UPDATE person_entry SET count = ' + + ' (SELECT COUNT(1) FROM face_region_entry WHERE face_region_entry.personId = person_entry.id)' + ); // remove persons without photo await connection @@ -33,23 +34,28 @@ export class PersonManager implements ISQLPersonManager { private static async updateSamplePhotos(): Promise { const connection = await SQLConnection.getConnection(); - await connection.query('update person_entry set sampleRegionId = ' + - '(Select face_region_entry.id from media_entity ' + - 'left join face_region_entry on media_entity.id = face_region_entry.mediaId ' + - 'where face_region_entry.personId=person_entry.id ' + - 'order by media_entity.metadataCreationdate desc ' + - 'limit 1)'); - + await connection.query( + 'update person_entry set sampleRegionId = ' + + '(Select face_region_entry.id from media_entity ' + + 'left join face_region_entry on media_entity.id = face_region_entry.mediaId ' + + 'where face_region_entry.personId=person_entry.id ' + + 'order by media_entity.metadataCreationdate desc ' + + 'limit 1)' + ); } - async updatePerson(name: string, partialPerson: PersonDTO): Promise { + async updatePerson( + name: string, + partialPerson: PersonDTO + ): Promise { this.isDBValid = false; const connection = await SQLConnection.getConnection(); const repository = connection.getRepository(PersonEntry); - const person = await repository.createQueryBuilder('person') + const person = await repository + .createQueryBuilder('person') .limit(1) - .where('person.name LIKE :name COLLATE ' + SQL_COLLATE, {name}).getOne(); - + .where('person.name LIKE :name COLLATE ' + SQL_COLLATE, { name }) + .getOne(); if (typeof partialPerson.name !== 'undefined') { person.name = partialPerson.name; @@ -76,7 +82,8 @@ export class PersonManager implements ISQLPersonManager { */ public async countFaces(): Promise { const connection = await SQLConnection.getConnection(); - return await connection.getRepository(FaceRegionEntry) + return await connection + .getRepository(FaceRegionEntry) .createQueryBuilder('faceRegion') .getCount(); } @@ -88,8 +95,10 @@ export class PersonManager implements ISQLPersonManager { return this.persons.find((p): boolean => p.name === name); } - public async saveAll(persons: { name: string, faceRegion: FaceRegion }[]): Promise { - const toSave: { name: string, faceRegion: FaceRegion }[] = []; + public async saveAll( + persons: { name: string; faceRegion: FaceRegion }[] + ): Promise { + const toSave: { name: string; faceRegion: FaceRegion }[] = []; const connection = await SQLConnection.getConnection(); const personRepository = connection.getRepository(PersonEntry); const faceRegionRepository = connection.getRepository(FaceRegionEntry); @@ -97,27 +106,28 @@ export class PersonManager implements ISQLPersonManager { const savedPersons = await personRepository.find(); // filter already existing persons for (const personToSave of persons) { - - const person = savedPersons.find((p): boolean => p.name === personToSave.name); + const person = savedPersons.find( + (p): boolean => p.name === personToSave.name + ); if (!person) { toSave.push(personToSave); } } - if (toSave.length > 0) { for (let i = 0; i < toSave.length / 200; i++) { const saving = toSave.slice(i * 200, (i + 1) * 200); - const inserted = await personRepository.insert(saving.map(p => ({name: p.name}))); + const inserted = await personRepository.insert( + saving.map((p) => ({ name: p.name })) + ); // setting Person id inserted.identifiers.forEach((idObj: { id: number }, j: number) => { (saving[j].faceRegion as FaceRegionEntry).person = idObj as any; }); - await faceRegionRepository.insert(saving.map(p => p.faceRegion)); + await faceRegionRepository.insert(saving.map((p) => p.faceRegion)); } } this.isDBValid = false; - } public async onNewDataVersion(): Promise { @@ -134,9 +144,11 @@ export class PersonManager implements ISQLPersonManager { const connection = await SQLConnection.getConnection(); const personRepository = connection.getRepository(PersonEntry); this.persons = await personRepository.find({ - relations: ['sampleRegion', + relations: [ + 'sampleRegion', 'sampleRegion.media', - 'sampleRegion.media.directory'] + 'sampleRegion.media.directory', + ], }); } @@ -153,5 +165,4 @@ export class PersonManager implements ISQLPersonManager { await PersonManager.updateSamplePhotos(); this.isDBValid = false; } - } diff --git a/src/backend/model/database/sql/PreviewManager.ts b/src/backend/model/database/sql/PreviewManager.ts index fa3f09e7..84ae197f 100644 --- a/src/backend/model/database/sql/PreviewManager.ts +++ b/src/backend/model/database/sql/PreviewManager.ts @@ -1,26 +1,34 @@ -import {Config} from '../../../../common/config/private/Config'; -import {Brackets, SelectQueryBuilder, WhereExpression} from 'typeorm'; -import {MediaEntity} from './enitites/MediaEntity'; -import {DiskMangerWorker} from '../../threading/DiskMangerWorker'; -import {ObjectManagers} from '../../ObjectManagers'; -import {DatabaseType} from '../../../../common/config/private/PrivateConfig'; -import {SortingMethods} from '../../../../common/entities/SortingMethods'; -import {ISQLSearchManager} from './ISearchManager'; -import {IPreviewManager, PreviewPhotoDTOWithID} from '../interfaces/IPreviewManager'; -import {SQLConnection} from './SQLConnection'; -import {SearchQueryDTO, SearchQueryTypes, TextSearch} from '../../../../common/entities/SearchQueryDTO'; -import {DirectoryEntity} from './enitites/DirectoryEntity'; -import {ParentDirectoryDTO} from '../../../../common/entities/DirectoryDTO'; +import { Config } from '../../../../common/config/private/Config'; +import { Brackets, SelectQueryBuilder, WhereExpression } from 'typeorm'; +import { MediaEntity } from './enitites/MediaEntity'; +import { DiskMangerWorker } from '../../threading/DiskMangerWorker'; +import { ObjectManagers } from '../../ObjectManagers'; +import { DatabaseType } from '../../../../common/config/private/PrivateConfig'; +import { SortingMethods } from '../../../../common/entities/SortingMethods'; +import { ISQLSearchManager } from './ISearchManager'; +import { + IPreviewManager, + PreviewPhotoDTOWithID, +} from '../interfaces/IPreviewManager'; +import { SQLConnection } from './SQLConnection'; +import { + SearchQueryDTO, + SearchQueryTypes, + TextSearch, +} from '../../../../common/entities/SearchQueryDTO'; +import { DirectoryEntity } from './enitites/DirectoryEntity'; +import { ParentDirectoryDTO } from '../../../../common/entities/DirectoryDTO'; import * as path from 'path'; -import {Utils} from '../../../../common/Utils'; +import { Utils } from '../../../../common/Utils'; const LOG_TAG = '[PreviewManager]'; export class PreviewManager implements IPreviewManager { private static DIRECTORY_SELECT = ['directory.name', 'directory.path']; - private static setSorting(query: SelectQueryBuilder): SelectQueryBuilder { - + private static setSorting( + query: SelectQueryBuilder + ): SelectQueryBuilder { for (const sort of Config.Server.Preview.Sorting) { switch (sort) { case SortingMethods.descDate: @@ -41,7 +49,6 @@ export class PreviewManager implements IPreviewManager { case SortingMethods.ascName: query.addOrderBy('media.name', 'ASC'); break; - } } @@ -50,18 +57,22 @@ export class PreviewManager implements IPreviewManager { public async resetPreviews(): Promise { const connection = await SQLConnection.getConnection(); - await connection.createQueryBuilder() + await connection + .createQueryBuilder() .update(DirectoryEntity) - .set({validPreview: false}).execute(); + .set({ validPreview: false }) + .execute(); } public async onNewDataVersion(changedDir: ParentDirectoryDTO): Promise { // Invalidating Album preview - let fullPath = DiskMangerWorker.normalizeDirPath(path.join(changedDir.path, changedDir.name)); + let fullPath = DiskMangerWorker.normalizeDirPath( + path.join(changedDir.path, changedDir.name) + ); const query = (await SQLConnection.getConnection()) .createQueryBuilder() .update(DirectoryEntity) - .set({validPreview: false}); + .set({ validPreview: false }); let i = 0; const root = DiskMangerWorker.pathFromRelativeDirName('.'); @@ -70,32 +81,37 @@ export class PreviewManager implements IPreviewManager { const parentPath = DiskMangerWorker.pathFromRelativeDirName(fullPath); fullPath = parentPath; ++i; - query.orWhere(new Brackets((q: WhereExpression) => { - const param: { [key: string]: string } = {}; - param['name' + i] = name; - param['path' + i] = parentPath; - q.where(`path = :path${i}`, param); - q.andWhere(`name = :name${i}`, param); - })); + query.orWhere( + new Brackets((q: WhereExpression) => { + const param: { [key: string]: string } = {}; + param['name' + i] = name; + param['path' + i] = parentPath; + q.where(`path = :path${i}`, param); + q.andWhere(`name = :name${i}`, param); + }) + ); } ++i; - query.orWhere(new Brackets((q: WhereExpression) => { - const param: { [key: string]: string } = {}; - param['name' + i] = DiskMangerWorker.dirName('.'); - param['path' + i] = DiskMangerWorker.pathFromRelativeDirName('.'); - q.where(`path = :path${i}`, param); - q.andWhere(`name = :name${i}`, param); - })); - + query.orWhere( + new Brackets((q: WhereExpression) => { + const param: { [key: string]: string } = {}; + param['name' + i] = DiskMangerWorker.dirName('.'); + param['path' + i] = DiskMangerWorker.pathFromRelativeDirName('.'); + q.where(`path = :path${i}`, param); + q.andWhere(`name = :name${i}`, param); + }) + ); await query.execute(); } - public async getAlbumPreview(album: { searchQuery: SearchQueryDTO }): Promise { - - const albumQuery: Brackets = await (ObjectManagers.getInstance().SearchManager as ISQLSearchManager) - .prepareAndBuildWhereQuery(album.searchQuery); + public async getAlbumPreview(album: { + searchQuery: SearchQueryDTO; + }): Promise { + const albumQuery: Brackets = await ( + ObjectManagers.getInstance().SearchManager as ISQLSearchManager + ).prepareAndBuildWhereQuery(album.searchQuery); const connection = await SQLConnection.getConnection(); const previewQuery = (): SelectQueryBuilder => { @@ -110,10 +126,16 @@ export class PreviewManager implements IPreviewManager { }; let previewMedia = null; - if (Config.Server.Preview.SearchQuery && - !Utils.equalsFilter(Config.Server.Preview.SearchQuery, {type: SearchQueryTypes.any_text, text: ''} as TextSearch)) { - const previewFilterQuery = await (ObjectManagers.getInstance().SearchManager as ISQLSearchManager) - .prepareAndBuildWhereQuery(Config.Server.Preview.SearchQuery); + if ( + Config.Server.Preview.SearchQuery && + !Utils.equalsFilter(Config.Server.Preview.SearchQuery, { + type: SearchQueryTypes.any_text, + text: '', + } as TextSearch) + ) { + const previewFilterQuery = await ( + ObjectManagers.getInstance().SearchManager as ISQLSearchManager + ).prepareAndBuildWhereQuery(Config.Server.Preview.SearchQuery); previewMedia = await previewQuery() .andWhere(previewFilterQuery) .limit(1) @@ -121,24 +143,28 @@ export class PreviewManager implements IPreviewManager { } if (!previewMedia) { - previewMedia = await previewQuery() - .limit(1) - .getOne(); + previewMedia = await previewQuery().limit(1).getOne(); } return previewMedia || null; } - - public async getPartialDirsWithoutPreviews(): Promise<{ id: number, name: string, path: string }[]> { + public async getPartialDirsWithoutPreviews(): Promise< + { id: number; name: string; path: string }[] + > { const connection = await SQLConnection.getConnection(); return await connection .getRepository(DirectoryEntity) .createQueryBuilder('directory') - .where('directory.validPreview = :validPreview', {validPreview: 0}) // 0 === false - .select(['name', 'id', 'path']).getRawMany(); + .where('directory.validPreview = :validPreview', { validPreview: 0 }) // 0 === false + .select(['name', 'id', 'path']) + .getRawMany(); } - public async setAndGetPreviewForDirectory(dir: { id: number, name: string, path: string }): Promise { + public async setAndGetPreviewForDirectory(dir: { + id: number; + name: string; + path: string; + }): Promise { const connection = await SQLConnection.getConnection(); const previewQuery = (): SelectQueryBuilder => { const query = connection @@ -146,56 +172,65 @@ export class PreviewManager implements IPreviewManager { .createQueryBuilder('media') .innerJoin('media.directory', 'directory') .select(['media.name', 'media.id', ...PreviewManager.DIRECTORY_SELECT]) - .where(new Brackets((q: WhereExpression) => { - q.where('media.directory = :dir', { - dir: dir.id - }); - if (Config.Server.Database.type === DatabaseType.mysql) { - q.orWhere('directory.path like :path || \'%\'', { - path: (DiskMangerWorker.pathFromParent(dir)) + .where( + new Brackets((q: WhereExpression) => { + q.where('media.directory = :dir', { + dir: dir.id, }); - } else { - q.orWhere('directory.path GLOB :path', { - path: DiskMangerWorker.pathFromParent(dir) + '*' - }); - } - })); + if (Config.Server.Database.type === DatabaseType.mysql) { + q.orWhere("directory.path like :path || '%'", { + path: DiskMangerWorker.pathFromParent(dir), + }); + } else { + q.orWhere('directory.path GLOB :path', { + path: DiskMangerWorker.pathFromParent(dir) + '*', + }); + } + }) + ); // Select from the directory if any otherwise from any subdirectories. // (There is no priority between subdirectories) - query.orderBy(`CASE WHEN directory.id = ${dir.id} THEN 0 ELSE 1 END`, 'ASC'); - + query.orderBy( + `CASE WHEN directory.id = ${dir.id} THEN 0 ELSE 1 END`, + 'ASC' + ); PreviewManager.setSorting(query); return query; }; - let previewMedia: PreviewPhotoDTOWithID = null; - if (Config.Server.Preview.SearchQuery && + if ( + Config.Server.Preview.SearchQuery && !Utils.equalsFilter(Config.Server.Preview.SearchQuery, { type: SearchQueryTypes.any_text, - text: '' - } as TextSearch)) { + text: '', + } as TextSearch) + ) { previewMedia = await previewQuery() - .andWhere(await (ObjectManagers.getInstance().SearchManager as ISQLSearchManager) - .prepareAndBuildWhereQuery(Config.Server.Preview.SearchQuery)) + .andWhere( + await ( + ObjectManagers.getInstance().SearchManager as ISQLSearchManager + ).prepareAndBuildWhereQuery(Config.Server.Preview.SearchQuery) + ) .limit(1) .getOne(); } if (!previewMedia) { - previewMedia = await previewQuery() - .limit(1) - .getOne(); + previewMedia = await previewQuery().limit(1).getOne(); } // set validPreview bit to true even if there is no preview (to prevent future updates) - await connection.createQueryBuilder() - .update(DirectoryEntity).set({preview: previewMedia, validPreview: true}).where('id = :dir', { - dir: dir.id - }).execute(); + await connection + .createQueryBuilder() + .update(DirectoryEntity) + .set({ preview: previewMedia, validPreview: true }) + .where('id = :dir', { + dir: dir.id, + }) + .execute(); return previewMedia || null; } - } diff --git a/src/backend/model/database/sql/SQLConnection.ts b/src/backend/model/database/sql/SQLConnection.ts index bddf51cc..36013258 100644 --- a/src/backend/model/database/sql/SQLConnection.ts +++ b/src/backend/model/database/sql/SQLConnection.ts @@ -1,37 +1,43 @@ import 'reflect-metadata'; -import {Connection, DataSourceOptions, createConnection, getConnection} from 'typeorm'; -import {UserEntity} from './enitites/UserEntity'; -import {UserRoles} from '../../../../common/entities/UserDTO'; -import {PhotoEntity} from './enitites/PhotoEntity'; -import {DirectoryEntity} from './enitites/DirectoryEntity'; -import {Config} from '../../../../common/config/private/Config'; -import {SharingEntity} from './enitites/SharingEntity'; -import {PasswordHelper} from '../../PasswordHelper'; -import {ProjectPath} from '../../../ProjectPath'; -import {VersionEntity} from './enitites/VersionEntity'; -import {Logger} from '../../../Logger'; -import {MediaEntity} from './enitites/MediaEntity'; -import {VideoEntity} from './enitites/VideoEntity'; -import {DataStructureVersion} from '../../../../common/DataStructureVersion'; -import {FileEntity} from './enitites/FileEntity'; -import {FaceRegionEntry} from './enitites/FaceRegionEntry'; -import {PersonEntry} from './enitites/PersonEntry'; -import {Utils} from '../../../../common/Utils'; +import { + Connection, + createConnection, + DataSourceOptions, + getConnection, +} from 'typeorm'; +import { UserEntity } from './enitites/UserEntity'; +import { UserRoles } from '../../../../common/entities/UserDTO'; +import { PhotoEntity } from './enitites/PhotoEntity'; +import { DirectoryEntity } from './enitites/DirectoryEntity'; +import { Config } from '../../../../common/config/private/Config'; +import { SharingEntity } from './enitites/SharingEntity'; +import { PasswordHelper } from '../../PasswordHelper'; +import { ProjectPath } from '../../../ProjectPath'; +import { VersionEntity } from './enitites/VersionEntity'; +import { Logger } from '../../../Logger'; +import { MediaEntity } from './enitites/MediaEntity'; +import { VideoEntity } from './enitites/VideoEntity'; +import { DataStructureVersion } from '../../../../common/DataStructureVersion'; +import { FileEntity } from './enitites/FileEntity'; +import { FaceRegionEntry } from './enitites/FaceRegionEntry'; +import { PersonEntry } from './enitites/PersonEntry'; +import { Utils } from '../../../../common/Utils'; import * as path from 'path'; -import {DatabaseType, ServerDataBaseConfig, SQLLogLevel} from '../../../../common/config/private/PrivateConfig'; -import {AlbumBaseEntity} from './enitites/album/AlbumBaseEntity'; -import {SavedSearchEntity} from './enitites/album/SavedSearchEntity'; -import {NotificationManager} from '../../NotifocationManager'; +import { + DatabaseType, + ServerDataBaseConfig, + SQLLogLevel, +} from '../../../../common/config/private/PrivateConfig'; +import { AlbumBaseEntity } from './enitites/album/AlbumBaseEntity'; +import { SavedSearchEntity } from './enitites/album/SavedSearchEntity'; +import { NotificationManager } from '../../NotifocationManager'; const LOG_TAG = '[SQLConnection]'; export class SQLConnection { - - private static connection: Connection = null; - constructor() { - } + constructor() {} public static async getConnection(): Promise { if (this.connection == null) { @@ -49,24 +55,30 @@ export class SQLConnection { SharingEntity, AlbumBaseEntity, SavedSearchEntity, - VersionEntity + VersionEntity, ]; options.synchronize = false; if (Config.Server.Log.sqlLevel !== SQLLogLevel.none) { options.logging = SQLLogLevel[Config.Server.Log.sqlLevel]; } - Logger.debug(LOG_TAG, 'Creating connection: ' + DatabaseType[Config.Server.Database.type], ', with driver:', options.type); + Logger.debug( + LOG_TAG, + 'Creating connection: ' + DatabaseType[Config.Server.Database.type], + ', with driver:', + options.type + ); this.connection = await this.createConnection(options); await SQLConnection.schemeSync(this.connection); } return this.connection; } - public static async tryConnection(config: ServerDataBaseConfig): Promise { + public static async tryConnection( + config: ServerDataBaseConfig + ): Promise { try { await getConnection('test').close(); - } catch (err) { - } + } catch (err) {} const options: any = this.getDriver(config); options.name = 'test'; options.entities = [ @@ -81,7 +93,7 @@ export class SQLConnection { SharingEntity, AlbumBaseEntity, SavedSearchEntity, - VersionEntity + VersionEntity, ]; options.synchronize = false; if (Config.Server.Log.sqlLevel !== SQLLogLevel.none) { @@ -101,10 +113,12 @@ export class SQLConnection { } // Adding enforced users to the db const userRepository = connection.getRepository(UserEntity); - if (Array.isArray(Config.Server.Database.enforcedUsers) && - Config.Server.Database.enforcedUsers.length > 0) { + if ( + Array.isArray(Config.Server.Database.enforcedUsers) && + Config.Server.Database.enforcedUsers.length > 0 + ) { for (const uc of Config.Server.Database.enforcedUsers) { - const user = await userRepository.findOneBy({name: uc.name}); + const user = await userRepository.findOneBy({ name: uc.name }); if (!user) { Logger.info(LOG_TAG, 'Saving enforced user: ' + uc.name); const a = new UserEntity(); @@ -123,7 +137,7 @@ export class SQLConnection { } // Add dummy Admin to the db - const admins = await userRepository.findBy({role: UserRoles.Admin}); + const admins = await userRepository.findBy({ role: UserRoles.Admin }); if (admins.length === 0) { const a = new UserEntity(); a.name = 'admin'; @@ -132,11 +146,19 @@ export class SQLConnection { await userRepository.save(a); } - const defAdmin = await userRepository.findOneBy({name: 'admin', role: UserRoles.Admin}); - if (defAdmin && PasswordHelper.comparePassword('admin', defAdmin.password)) { - NotificationManager.error('Using default admin user!', 'You are using the default admin/admin user/password, please change or remove it.'); + const defAdmin = await userRepository.findOneBy({ + name: 'admin', + role: UserRoles.Admin, + }); + if ( + defAdmin && + PasswordHelper.comparePassword('admin', defAdmin.password) + ) { + NotificationManager.error( + 'Using default admin user!', + 'You are using the default admin/admin user/password, please change or remove it.' + ); } - } public static async close(): Promise { @@ -155,20 +177,24 @@ export class SQLConnection { return path.join(ProjectPath.getAbsolutePath(config.dbFolder), 'sqlite.db'); } - private static async createConnection(options: DataSourceOptions): Promise { + private static async createConnection( + options: DataSourceOptions + ): Promise { if (options.type === 'sqlite' || options.type === 'better-sqlite3') { return await createConnection(options); } try { return await createConnection(options); } catch (e) { - if (e.sqlMessage === 'Unknown database \'' + options.database + '\'') { + if (e.sqlMessage === "Unknown database '" + options.database + "'") { Logger.debug(LOG_TAG, 'creating database: ' + options.database); const tmpOption = Utils.clone(options); // @ts-ignore delete tmpOption.database; const tmpConn = await createConnection(tmpOption); - await tmpConn.query('CREATE DATABASE IF NOT EXISTS ' + options.database); + await tmpConn.query( + 'CREATE DATABASE IF NOT EXISTS ' + options.database + ); await tmpConn.close(); return await createConnection(options); } @@ -180,8 +206,7 @@ export class SQLConnection { let version = null; try { version = (await connection.getRepository(VersionEntity).find())[0]; - } catch (ex) { - } + } catch (ex) {} if (version && version.version === DataStructureVersion) { return; } @@ -193,9 +218,11 @@ export class SQLConnection { let users: UserEntity[] = []; try { - users = await connection.getRepository(UserEntity).createQueryBuilder('user').getMany(); - } catch (ex) { - } + users = await connection + .getRepository(UserEntity) + .createQueryBuilder('user') + .getMany(); + } catch (ex) {} await connection.dropDatabase(); await connection.synchronize(); await connection.getRepository(VersionEntity).save(version); @@ -205,7 +232,11 @@ export class SQLConnection { await connection.dropDatabase(); await connection.synchronize(); await connection.getRepository(VersionEntity).save(version); - Logger.warn(LOG_TAG, 'Could not move users to the new db scheme, deleting them. Details:' + e.toString()); + Logger.warn( + LOG_TAG, + 'Could not move users to the new db scheme, deleting them. Details:' + + e.toString() + ); } } @@ -219,16 +250,17 @@ export class SQLConnection { username: config.mysql.username, password: config.mysql.password, database: config.mysql.database, - charset: 'utf8mb4' + charset: 'utf8mb4', }; } else if (config.type === DatabaseType.sqlite) { driver = { type: 'better-sqlite3', - database: path.join(ProjectPath.getAbsolutePath(config.dbFolder), config.sqlite.DBFileName) + database: path.join( + ProjectPath.getAbsolutePath(config.dbFolder), + config.sqlite.DBFileName + ), }; - } return driver; } - } diff --git a/src/backend/model/database/sql/SearchManager.ts b/src/backend/model/database/sql/SearchManager.ts index 5920de5b..6fe9bf5d 100644 --- a/src/backend/model/database/sql/SearchManager.ts +++ b/src/backend/model/database/sql/SearchManager.ts @@ -1,12 +1,12 @@ -import {AutoCompleteItem} from '../../../../common/entities/AutoCompleteItem'; -import {SearchResultDTO} from '../../../../common/entities/SearchResultDTO'; -import {SQLConnection} from './SQLConnection'; -import {PhotoEntity} from './enitites/PhotoEntity'; -import {DirectoryEntity} from './enitites/DirectoryEntity'; -import {MediaEntity} from './enitites/MediaEntity'; -import {PersonEntry} from './enitites/PersonEntry'; -import {Brackets, SelectQueryBuilder, WhereExpression} from 'typeorm'; -import {Config} from '../../../../common/config/private/Config'; +import { AutoCompleteItem } from '../../../../common/entities/AutoCompleteItem'; +import { SearchResultDTO } from '../../../../common/entities/SearchResultDTO'; +import { SQLConnection } from './SQLConnection'; +import { PhotoEntity } from './enitites/PhotoEntity'; +import { DirectoryEntity } from './enitites/DirectoryEntity'; +import { MediaEntity } from './enitites/MediaEntity'; +import { PersonEntry } from './enitites/PersonEntry'; +import { Brackets, SelectQueryBuilder, WhereExpression } from 'typeorm'; +import { Config } from '../../../../common/config/private/Config'; import { ANDSearchQuery, DistanceSearch, @@ -23,31 +23,37 @@ import { SomeOfSearchQuery, TextSearch, TextSearchQueryMatchTypes, - ToDateSearch + ToDateSearch, } from '../../../../common/entities/SearchQueryDTO'; -import {GalleryManager} from './GalleryManager'; -import {ObjectManagers} from '../../ObjectManagers'; -import {PhotoDTO} from '../../../../common/entities/PhotoDTO'; -import {DatabaseType} from '../../../../common/config/private/PrivateConfig'; -import {ISQLGalleryManager} from './IGalleryManager'; -import {ISQLSearchManager} from './ISearchManager'; -import {Utils} from '../../../../common/Utils'; -import {FileEntity} from './enitites/FileEntity'; -import {SQL_COLLATE} from './enitites/EntityUtils'; +import { GalleryManager } from './GalleryManager'; +import { ObjectManagers } from '../../ObjectManagers'; +import { PhotoDTO } from '../../../../common/entities/PhotoDTO'; +import { DatabaseType } from '../../../../common/config/private/PrivateConfig'; +import { ISQLGalleryManager } from './IGalleryManager'; +import { ISQLSearchManager } from './ISearchManager'; +import { Utils } from '../../../../common/Utils'; +import { FileEntity } from './enitites/FileEntity'; +import { SQL_COLLATE } from './enitites/EntityUtils'; export class SearchManager implements ISQLSearchManager { - // This trick enables us to list less rows as faces will be concatenated into one row // Also typeorm does not support automatic mapping of nested foreign keys // (i.e: leftJoinAndSelect('media.metadata.faces', 'faces') does not work) - private FACE_SELECT = Config.Server.Database.type === DatabaseType.mysql ? - 'CONCAT(\'[\' , GROUP_CONCAT( \'{"name": "\' , person.name , \'", "box": {"top":\' , faces.box.top , \', "left":\' , faces.box.left , \', "height":\' , faces.box.height ,\', "width":\' , faces.box.width , \'}}\' ) ,\']\') as media_metadataFaces' : - '\'[\' || GROUP_CONCAT( \'{"name": "\' || person.name || \'", "box": {"top":\' || faces.box.top || \', "left":\' || faces.box.left || \', "height":\' || faces.box.height ||\', "width":\' || faces.box.width || \'}}\' ) ||\']\' as media_metadataFaces'; - private DIRECTORY_SELECT = ['directory.id', 'directory.name', 'directory.path']; + private FACE_SELECT = + Config.Server.Database.type === DatabaseType.mysql + ? "CONCAT('[' , GROUP_CONCAT( '{\"name\": \"' , person.name , '\", \"box\": {\"top\":' , faces.box.top , ', \"left\":' , faces.box.left , ', \"height\":' , faces.box.height ,', \"width\":' , faces.box.width , '}}' ) ,']') as media_metadataFaces" + : "'[' || GROUP_CONCAT( '{\"name\": \"' || person.name || '\", \"box\": {\"top\":' || faces.box.top || ', \"left\":' || faces.box.left || ', \"height\":' || faces.box.height ||', \"width\":' || faces.box.width || '}}' ) ||']' as media_metadataFaces"; + private DIRECTORY_SELECT = [ + 'directory.id', + 'directory.name', + 'directory.path', + ]; // makes all search query params unique, so typeorm wont mix them private queryIdBase = 0; - private static autoCompleteItemsUnique(array: Array): Array { + private static autoCompleteItemsUnique( + array: Array + ): Array { const a = array.concat(); for (let i = 0; i < a.length; ++i) { for (let j = i + 1; j < a.length; ++j) { @@ -60,8 +66,10 @@ export class SearchManager implements ISQLSearchManager { return a; } - async autocomplete(text: string, type: SearchQueryTypes): Promise { - + async autocomplete( + text: string, + type: SearchQueryTypes + ): Promise { const connection = await SQLConnection.getConnection(); const photoRepository = connection.getRepository(PhotoEntity); @@ -71,92 +79,202 @@ export class SearchManager implements ISQLSearchManager { const partialResult: AutoCompleteItem[][] = []; - if (type === SearchQueryTypes.any_text || type === SearchQueryTypes.keyword) { + if ( + type === SearchQueryTypes.any_text || + type === SearchQueryTypes.keyword + ) { const acList: AutoCompleteItem[] = []; - (await photoRepository - .createQueryBuilder('photo') - .select('DISTINCT(photo.metadata.keywords)') - .where('photo.metadata.keywords LIKE :text COLLATE ' + SQL_COLLATE, {text: '%' + text + '%'}) - .limit(Config.Client.Search.AutoComplete.targetItemsPerCategory * 2) - .getRawMany()) - .map((r): Array => (r.metadataKeywords as string).split(',') as Array) + ( + await photoRepository + .createQueryBuilder('photo') + .select('DISTINCT(photo.metadata.keywords)') + .where('photo.metadata.keywords LIKE :text COLLATE ' + SQL_COLLATE, { + text: '%' + text + '%', + }) + .limit(Config.Client.Search.AutoComplete.targetItemsPerCategory * 2) + .getRawMany() + ) + .map( + (r): Array => + (r.metadataKeywords as string).split(',') as Array + ) .forEach((keywords): void => { - acList.push(...this.encapsulateAutoComplete(keywords - .filter((k): boolean => k.toLowerCase().indexOf(text.toLowerCase()) !== -1), SearchQueryTypes.keyword)); + acList.push( + ...this.encapsulateAutoComplete( + keywords.filter( + (k): boolean => + k.toLowerCase().indexOf(text.toLowerCase()) !== -1 + ), + SearchQueryTypes.keyword + ) + ); }); partialResult.push(acList); } - if (type === SearchQueryTypes.any_text || type === SearchQueryTypes.person) { - partialResult.push(this.encapsulateAutoComplete((await personRepository - .createQueryBuilder('person') - .select('DISTINCT(person.name), person.count') - .where('person.name LIKE :text COLLATE ' + SQL_COLLATE, {text: '%' + text + '%'}) - .limit(Config.Client.Search.AutoComplete.targetItemsPerCategory * 2) - .orderBy('person.count', 'DESC') - .getRawMany()) - .map(r => r.name), SearchQueryTypes.person)); + if ( + type === SearchQueryTypes.any_text || + type === SearchQueryTypes.person + ) { + partialResult.push( + this.encapsulateAutoComplete( + ( + await personRepository + .createQueryBuilder('person') + .select('DISTINCT(person.name), person.count') + .where('person.name LIKE :text COLLATE ' + SQL_COLLATE, { + text: '%' + text + '%', + }) + .limit( + Config.Client.Search.AutoComplete.targetItemsPerCategory * 2 + ) + .orderBy('person.count', 'DESC') + .getRawMany() + ).map((r) => r.name), + SearchQueryTypes.person + ) + ); } - if (type === SearchQueryTypes.any_text || type === SearchQueryTypes.position || type === SearchQueryTypes.distance) { + if ( + type === SearchQueryTypes.any_text || + type === SearchQueryTypes.position || + type === SearchQueryTypes.distance + ) { const acList: AutoCompleteItem[] = []; - (await photoRepository - .createQueryBuilder('photo') - .select('photo.metadata.positionData.country as country, ' + - 'photo.metadata.positionData.state as state, photo.metadata.positionData.city as city') - .where('photo.metadata.positionData.country LIKE :text COLLATE ' + SQL_COLLATE, {text: '%' + text + '%'}) - .orWhere('photo.metadata.positionData.state LIKE :text COLLATE ' + SQL_COLLATE, {text: '%' + text + '%'}) - .orWhere('photo.metadata.positionData.city LIKE :text COLLATE ' + SQL_COLLATE, {text: '%' + text + '%'}) - .groupBy('photo.metadata.positionData.country, photo.metadata.positionData.state, photo.metadata.positionData.city') - .limit(Config.Client.Search.AutoComplete.targetItemsPerCategory * 2) - .getRawMany()) + ( + await photoRepository + .createQueryBuilder('photo') + .select( + 'photo.metadata.positionData.country as country, ' + + 'photo.metadata.positionData.state as state, photo.metadata.positionData.city as city' + ) + .where( + 'photo.metadata.positionData.country LIKE :text COLLATE ' + + SQL_COLLATE, + { text: '%' + text + '%' } + ) + .orWhere( + 'photo.metadata.positionData.state LIKE :text COLLATE ' + + SQL_COLLATE, + { text: '%' + text + '%' } + ) + .orWhere( + 'photo.metadata.positionData.city LIKE :text COLLATE ' + + SQL_COLLATE, + { text: '%' + text + '%' } + ) + .groupBy( + 'photo.metadata.positionData.country, photo.metadata.positionData.state, photo.metadata.positionData.city' + ) + .limit(Config.Client.Search.AutoComplete.targetItemsPerCategory * 2) + .getRawMany() + ) .filter((pm): boolean => !!pm) - .map((pm): Array => [pm.city || '', pm.country || '', pm.state || ''] as Array) + .map( + (pm): Array => + [pm.city || '', pm.country || '', pm.state || ''] as Array + ) .forEach((positions): void => { - acList.push(...this.encapsulateAutoComplete(positions - .filter((p): boolean => p.toLowerCase().indexOf(text.toLowerCase()) !== -1), - type === SearchQueryTypes.distance ? type : SearchQueryTypes.position)); + acList.push( + ...this.encapsulateAutoComplete( + positions.filter( + (p): boolean => + p.toLowerCase().indexOf(text.toLowerCase()) !== -1 + ), + type === SearchQueryTypes.distance + ? type + : SearchQueryTypes.position + ) + ); }); partialResult.push(acList); } - if (type === SearchQueryTypes.any_text || type === SearchQueryTypes.file_name) { - partialResult.push(this.encapsulateAutoComplete((await mediaRepository - .createQueryBuilder('media') - .select('DISTINCT(media.name)') - .where('media.name LIKE :text COLLATE ' + SQL_COLLATE, {text: '%' + text + '%'}) - .limit(Config.Client.Search.AutoComplete.targetItemsPerCategory * 2) - .getRawMany()) - .map(r => r.name), SearchQueryTypes.file_name)); + if ( + type === SearchQueryTypes.any_text || + type === SearchQueryTypes.file_name + ) { + partialResult.push( + this.encapsulateAutoComplete( + ( + await mediaRepository + .createQueryBuilder('media') + .select('DISTINCT(media.name)') + .where('media.name LIKE :text COLLATE ' + SQL_COLLATE, { + text: '%' + text + '%', + }) + .limit( + Config.Client.Search.AutoComplete.targetItemsPerCategory * 2 + ) + .getRawMany() + ).map((r) => r.name), + SearchQueryTypes.file_name + ) + ); } - if (type === SearchQueryTypes.any_text || type === SearchQueryTypes.caption) { - partialResult.push(this.encapsulateAutoComplete((await photoRepository - .createQueryBuilder('media') - .select('DISTINCT(media.metadata.caption) as caption') - .where('media.metadata.caption LIKE :text COLLATE ' + SQL_COLLATE, {text: '%' + text + '%'}) - .limit(Config.Client.Search.AutoComplete.targetItemsPerCategory * 2) - .getRawMany()) - .map(r => r.caption), SearchQueryTypes.caption)); + if ( + type === SearchQueryTypes.any_text || + type === SearchQueryTypes.caption + ) { + partialResult.push( + this.encapsulateAutoComplete( + ( + await photoRepository + .createQueryBuilder('media') + .select('DISTINCT(media.metadata.caption) as caption') + .where( + 'media.metadata.caption LIKE :text COLLATE ' + SQL_COLLATE, + { text: '%' + text + '%' } + ) + .limit( + Config.Client.Search.AutoComplete.targetItemsPerCategory * 2 + ) + .getRawMany() + ).map((r) => r.caption), + SearchQueryTypes.caption + ) + ); } - if (type === SearchQueryTypes.any_text || type === SearchQueryTypes.directory) { - partialResult.push(this.encapsulateAutoComplete((await directoryRepository - .createQueryBuilder('dir') - .select('DISTINCT(dir.name)') - .where('dir.name LIKE :text COLLATE ' + SQL_COLLATE, {text: '%' + text + '%'}) - .limit(Config.Client.Search.AutoComplete.targetItemsPerCategory * 2) - .getRawMany()) - .map(r => r.name), SearchQueryTypes.directory)); + if ( + type === SearchQueryTypes.any_text || + type === SearchQueryTypes.directory + ) { + partialResult.push( + this.encapsulateAutoComplete( + ( + await directoryRepository + .createQueryBuilder('dir') + .select('DISTINCT(dir.name)') + .where('dir.name LIKE :text COLLATE ' + SQL_COLLATE, { + text: '%' + text + '%', + }) + .limit( + Config.Client.Search.AutoComplete.targetItemsPerCategory * 2 + ) + .getRawMany() + ).map((r) => r.name), + SearchQueryTypes.directory + ) + ); } let result: AutoCompleteItem[]; // if not enough items are available, load more from one category - if ([].concat(...partialResult).length < Config.Client.Search.AutoComplete.maxItems) { - result = [].concat(...(partialResult)); + if ( + [].concat(...partialResult).length < + Config.Client.Search.AutoComplete.maxItems + ) { + result = [].concat(...partialResult); } else { - result = [].concat(...(partialResult.map(l => l.slice(0, Config.Client.Search.AutoComplete.targetItemsPerCategory)))); + result = [].concat( + ...partialResult.map((l) => + l.slice(0, Config.Client.Search.AutoComplete.targetItemsPerCategory) + ) + ); } return SearchManager.autoCompleteItemsUnique(result); @@ -171,10 +289,9 @@ export class SearchManager implements ISQLSearchManager { directories: [], media: [], metaFile: [], - resultOverflow: false + resultOverflow: false, }; - const rawAndEntries = await connection .getRepository(MediaEntity) .createQueryBuilder('media') @@ -189,7 +306,9 @@ export class SearchManager implements ISQLSearchManager { for (let i = 0; i < rawAndEntries.entities.length; ++i) { if (rawAndEntries.raw[i].media_metadataFaces) { - rawAndEntries.entities[i].metadata.faces = JSON.parse(rawAndEntries.raw[i].media_metadataFaces); + rawAndEntries.entities[i].metadata.faces = JSON.parse( + rawAndEntries.raw[i].media_metadataFaces + ); } } @@ -200,15 +319,20 @@ export class SearchManager implements ISQLSearchManager { } if (Config.Client.Search.listMetafiles === true) { - result.metaFile = await connection.getRepository(FileEntity) + result.metaFile = await connection + .getRepository(FileEntity) .createQueryBuilder('file') .select(['file', ...this.DIRECTORY_SELECT]) - .innerJoin(q => q.from(MediaEntity, 'media') - .select('distinct directory.id') - .where(this.buildWhereQuery(query)) - .leftJoin('media.directory', 'directory'), + .innerJoin( + (q) => + q + .from(MediaEntity, 'media') + .select('distinct directory.id') + .where(this.buildWhereQuery(query)) + .leftJoin('media.directory', 'directory'), 'dir', - 'file.directory=dir.id') + 'file.directory=dir.id' + ) .leftJoin('file.directory', 'directory') .getMany(); } @@ -223,20 +347,25 @@ export class SearchManager implements ISQLSearchManager { .leftJoinAndSelect('directory.preview', 'preview') .leftJoinAndSelect('preview.directory', 'previewDirectory') .limit(Config.Client.Search.maxDirectoryResult + 1) - .select(['directory', + .select([ + 'directory', 'preview.name', 'previewDirectory.name', - 'previewDirectory.path']) + 'previewDirectory.path', + ]) .getMany(); // setting previews if (result.directories) { for (const item of result.directories) { - await (ObjectManagers.getInstance().GalleryManager as ISQLGalleryManager) - .fillPreviewForSubDir(connection, item as DirectoryEntity); + await ( + ObjectManagers.getInstance().GalleryManager as ISQLGalleryManager + ).fillPreviewForSubDir(connection, item as DirectoryEntity); } } - if (result.directories.length > Config.Client.Search.maxDirectoryResult) { + if ( + result.directories.length > Config.Client.Search.maxDirectoryResult + ) { result.resultOverflow = true; } } @@ -254,12 +383,10 @@ export class SearchManager implements ISQLSearchManager { .innerJoin('media.directory', 'directory') .where(await this.prepareAndBuildWhereQuery(query)); - if (Config.Server.Database.type === DatabaseType.mysql) { return await sqlQuery.groupBy('RAND(), media.id').limit(1).getOne(); } return await sqlQuery.groupBy('RANDOM()').limit(1).getOne(); - } public async getCount(query: SearchQueryDTO): Promise { @@ -273,7 +400,10 @@ export class SearchManager implements ISQLSearchManager { .getCount(); } - public async prepareAndBuildWhereQuery(queryIN: SearchQueryDTO, directoryOnly = false): Promise { + public async prepareAndBuildWhereQuery( + queryIN: SearchQueryDTO, + directoryOnly = false + ): Promise { const query = await this.prepareQuery(queryIN); return this.buildWhereQuery(query, directoryOnly); } @@ -291,21 +421,27 @@ export class SearchManager implements ISQLSearchManager { * @param directoryOnly Only builds directory related queries * @private */ - public buildWhereQuery(query: SearchQueryDTO, directoryOnly = false): Brackets { + public buildWhereQuery( + query: SearchQueryDTO, + directoryOnly = false + ): Brackets { const queryId = (query as SearchQueryDTOWithID).queryId; switch (query.type) { case SearchQueryTypes.AND: return new Brackets((q): any => { - (query as ANDSearchQuery).list.forEach((sq): any => q.andWhere(this.buildWhereQuery(sq, directoryOnly))); + (query as ANDSearchQuery).list.forEach((sq): any => + q.andWhere(this.buildWhereQuery(sq, directoryOnly)) + ); return q; }); case SearchQueryTypes.OR: return new Brackets((q): any => { - (query as ANDSearchQuery).list.forEach((sq): any => q.orWhere(this.buildWhereQuery(sq, directoryOnly))); + (query as ANDSearchQuery).list.forEach((sq): any => + q.orWhere(this.buildWhereQuery(sq, directoryOnly)) + ); return q; }); - case SearchQueryTypes.distance: if (directoryOnly) { throw new Error('not supported in directoryOnly mode'); @@ -314,24 +450,41 @@ export class SearchManager implements ISQLSearchManager { * This is a best effort calculation, not fully accurate in order to have higher performance. * see: https://stackoverflow.com/a/50506609 */ - const earth = 6378.137; // radius of the earth in kilometer - const latDelta = (1 / ((2 * Math.PI / 360) * earth)); // 1 km in degree - const lonDelta = (1 / ((2 * Math.PI / 360) * earth)); // 1 km in degree + const earth = 6378.137; // radius of the earth in kilometer + const latDelta = 1 / (((2 * Math.PI) / 360) * earth); // 1 km in degree + const lonDelta = 1 / (((2 * Math.PI) / 360) * earth); // 1 km in degree // TODO: properly handle latitude / longitude boundaries const trimRange = (value: number, min: number, max: number): number => { return Math.min(Math.max(value, min), max); }; - const minLat = trimRange((query as DistanceSearch).from.GPSData.latitude - - ((query as DistanceSearch).distance * latDelta), -90, 90); - const maxLat = trimRange((query as DistanceSearch).from.GPSData.latitude + - ((query as DistanceSearch).distance * latDelta), -90, 90); - const minLon = trimRange((query as DistanceSearch).from.GPSData.longitude - - ((query as DistanceSearch).distance * lonDelta) / Math.cos(minLat * (Math.PI / 180)), -180, 180); - const maxLon = trimRange((query as DistanceSearch).from.GPSData.longitude + - ((query as DistanceSearch).distance * lonDelta) / Math.cos(maxLat * (Math.PI / 180)), -180, 180); - + const minLat = trimRange( + (query as DistanceSearch).from.GPSData.latitude - + (query as DistanceSearch).distance * latDelta, + -90, + 90 + ); + const maxLat = trimRange( + (query as DistanceSearch).from.GPSData.latitude + + (query as DistanceSearch).distance * latDelta, + -90, + 90 + ); + const minLon = trimRange( + (query as DistanceSearch).from.GPSData.longitude - + ((query as DistanceSearch).distance * lonDelta) / + Math.cos(minLat * (Math.PI / 180)), + -180, + 180 + ); + const maxLon = trimRange( + (query as DistanceSearch).from.GPSData.longitude + + ((query as DistanceSearch).distance * lonDelta) / + Math.cos(maxLat * (Math.PI / 180)), + -180, + 180 + ); return new Brackets((q): any => { const textParam: any = {}; @@ -340,15 +493,39 @@ export class SearchManager implements ISQLSearchManager { textParam['maxLon' + queryId] = maxLon; textParam['minLon' + queryId] = minLon; if (!(query as DistanceSearch).negate) { - q.where(`media.metadata.positionData.GPSData.latitude < :maxLat${queryId}`, textParam); - q.andWhere(`media.metadata.positionData.GPSData.latitude > :minLat${queryId}`, textParam); - q.andWhere(`media.metadata.positionData.GPSData.longitude < :maxLon${queryId}`, textParam); - q.andWhere(`media.metadata.positionData.GPSData.longitude > :minLon${queryId}`, textParam); + q.where( + `media.metadata.positionData.GPSData.latitude < :maxLat${queryId}`, + textParam + ); + q.andWhere( + `media.metadata.positionData.GPSData.latitude > :minLat${queryId}`, + textParam + ); + q.andWhere( + `media.metadata.positionData.GPSData.longitude < :maxLon${queryId}`, + textParam + ); + q.andWhere( + `media.metadata.positionData.GPSData.longitude > :minLon${queryId}`, + textParam + ); } else { - q.where(`media.metadata.positionData.GPSData.latitude > :maxLat${queryId}`, textParam); - q.orWhere(`media.metadata.positionData.GPSData.latitude < :minLat${queryId}`, textParam); - q.orWhere(`media.metadata.positionData.GPSData.longitude > :maxLon${queryId}`, textParam); - q.orWhere(`media.metadata.positionData.GPSData.longitude < :minLon${queryId}`, textParam); + q.where( + `media.metadata.positionData.GPSData.latitude > :maxLat${queryId}`, + textParam + ); + q.orWhere( + `media.metadata.positionData.GPSData.latitude < :minLat${queryId}`, + textParam + ); + q.orWhere( + `media.metadata.positionData.GPSData.longitude > :maxLon${queryId}`, + textParam + ); + q.orWhere( + `media.metadata.positionData.GPSData.longitude < :minLon${queryId}`, + textParam + ); } return q; }); @@ -359,13 +536,18 @@ export class SearchManager implements ISQLSearchManager { } return new Brackets((q): any => { if (typeof (query as FromDateSearch).value === 'undefined') { - throw new Error('Invalid search query: Date Query should contain from value'); + throw new Error( + 'Invalid search query: Date Query should contain from value' + ); } const relation = (query as TextSearch).negate ? '<' : '>='; const textParam: any = {}; textParam['from' + queryId] = (query as FromDateSearch).value; - q.where(`media.metadata.creationDate ${relation} :from${queryId}`, textParam); + q.where( + `media.metadata.creationDate ${relation} :from${queryId}`, + textParam + ); return q; }); @@ -376,13 +558,18 @@ export class SearchManager implements ISQLSearchManager { } return new Brackets((q): any => { if (typeof (query as ToDateSearch).value === 'undefined') { - throw new Error('Invalid search query: Date Query should contain to value'); + throw new Error( + 'Invalid search query: Date Query should contain to value' + ); } const relation = (query as TextSearch).negate ? '>' : '<='; const textParam: any = {}; textParam['to' + queryId] = (query as ToDateSearch).value; - q.where(`media.metadata.creationDate ${relation} :to${queryId}`, textParam); + q.where( + `media.metadata.creationDate ${relation} :to${queryId}`, + textParam + ); return q; }); @@ -393,14 +580,19 @@ export class SearchManager implements ISQLSearchManager { } return new Brackets((q): any => { if (typeof (query as MinRatingSearch).value === 'undefined') { - throw new Error('Invalid search query: Rating Query should contain minvalue'); + throw new Error( + 'Invalid search query: Rating Query should contain minvalue' + ); } const relation = (query as TextSearch).negate ? '<' : '>='; const textParam: any = {}; textParam['min' + queryId] = (query as MinRatingSearch).value; - q.where(`media.metadata.rating ${relation} :min${queryId}`, textParam); + q.where( + `media.metadata.rating ${relation} :min${queryId}`, + textParam + ); return q; }); @@ -410,7 +602,9 @@ export class SearchManager implements ISQLSearchManager { } return new Brackets((q): any => { if (typeof (query as MaxRatingSearch).value === 'undefined') { - throw new Error('Invalid search query: Rating Query should contain max value'); + throw new Error( + 'Invalid search query: Rating Query should contain max value' + ); } const relation = (query as TextSearch).negate ? '>' : '<='; @@ -418,7 +612,10 @@ export class SearchManager implements ISQLSearchManager { if (typeof (query as MaxRatingSearch).value !== 'undefined') { const textParam: any = {}; textParam['max' + queryId] = (query as MaxRatingSearch).value; - q.where(`media.metadata.rating ${relation} :max${queryId}`, textParam); + q.where( + `media.metadata.rating ${relation} :max${queryId}`, + textParam + ); } return q; }); @@ -429,15 +626,20 @@ export class SearchManager implements ISQLSearchManager { } return new Brackets((q): any => { if (typeof (query as MinResolutionSearch).value === 'undefined') { - throw new Error('Invalid search query: Resolution Query should contain min value'); + throw new Error( + 'Invalid search query: Resolution Query should contain min value' + ); } const relation = (query as TextSearch).negate ? '<' : '>='; const textParam: any = {}; - textParam['min' + queryId] = (query as MinResolutionSearch).value * 1000 * 1000; - q.where(`media.metadata.size.width * media.metadata.size.height ${relation} :min${queryId}`, textParam); - + textParam['min' + queryId] = + (query as MinResolutionSearch).value * 1000 * 1000; + q.where( + `media.metadata.size.width * media.metadata.size.height ${relation} :min${queryId}`, + textParam + ); return q; }); @@ -448,14 +650,20 @@ export class SearchManager implements ISQLSearchManager { } return new Brackets((q): any => { if (typeof (query as MaxResolutionSearch).value === 'undefined') { - throw new Error('Invalid search query: Rating Query should contain min or max value'); + throw new Error( + 'Invalid search query: Rating Query should contain min or max value' + ); } const relation = (query as TextSearch).negate ? '>' : '<='; const textParam: any = {}; - textParam['max' + queryId] = (query as MaxResolutionSearch).value * 1000 * 1000; - q.where(`media.metadata.size.width * media.metadata.size.height ${relation} :max${queryId}`, textParam); + textParam['max' + queryId] = + (query as MaxResolutionSearch).value * 1000 * 1000; + q.where( + `media.metadata.size.width * media.metadata.size.height ${relation} :max${queryId}`, + textParam + ); return q; }); @@ -473,16 +681,16 @@ export class SearchManager implements ISQLSearchManager { return q; }); - case SearchQueryTypes.SOME_OF: throw new Error('Some of not supported'); - } return new Brackets((q: WhereExpression) => { - const createMatchString = (str: string): string => { - if ((query as TextSearch).matchType === TextSearchQueryMatchTypes.exact_match) { + if ( + (query as TextSearch).matchType === + TextSearchQueryMatchTypes.exact_match + ) { return str; } // MySQL uses C escape syntax in strings, details: @@ -500,85 +708,152 @@ export class SearchManager implements ISQLSearchManager { const whereFNRev = (query as TextSearch).negate ? 'orWhere' : 'andWhere'; const textParam: any = {}; - textParam['text' + queryId] = createMatchString((query as TextSearch).text); - - if (query.type === SearchQueryTypes.any_text || - query.type === SearchQueryTypes.directory) { - const dirPathStr = ((query as TextSearch).text).replace(new RegExp('\\\\', 'g'), '/'); + textParam['text' + queryId] = createMatchString( + (query as TextSearch).text + ); + if ( + query.type === SearchQueryTypes.any_text || + query.type === SearchQueryTypes.directory + ) { + const dirPathStr = (query as TextSearch).text.replace( + new RegExp('\\\\', 'g'), + '/' + ); textParam['fullPath' + queryId] = createMatchString(dirPathStr); - q[whereFN](`directory.path ${LIKE} :fullPath${queryId} COLLATE ` + SQL_COLLATE, - textParam); + q[whereFN]( + `directory.path ${LIKE} :fullPath${queryId} COLLATE ` + SQL_COLLATE, + textParam + ); const directoryPath = GalleryManager.parseRelativeDirePath(dirPathStr); - q[whereFN](new Brackets((dq): any => { - textParam['dirName' + queryId] = createMatchString(directoryPath.name); - dq[whereFNRev](`directory.name ${LIKE} :dirName${queryId} COLLATE ${SQL_COLLATE}`, - textParam); - if (dirPathStr.includes('/')) { - textParam['parentName' + queryId] = createMatchString(directoryPath.parent); - dq[whereFNRev](`directory.path ${LIKE} :parentName${queryId} COLLATE ${SQL_COLLATE}`, - textParam); - } - return dq; - })); + q[whereFN]( + new Brackets((dq): any => { + textParam['dirName' + queryId] = createMatchString( + directoryPath.name + ); + dq[whereFNRev]( + `directory.name ${LIKE} :dirName${queryId} COLLATE ${SQL_COLLATE}`, + textParam + ); + if (dirPathStr.includes('/')) { + textParam['parentName' + queryId] = createMatchString( + directoryPath.parent + ); + dq[whereFNRev]( + `directory.path ${LIKE} :parentName${queryId} COLLATE ${SQL_COLLATE}`, + textParam + ); + } + return dq; + }) + ); } - if ((query.type === SearchQueryTypes.any_text && !directoryOnly) || query.type === SearchQueryTypes.file_name) { - q[whereFN](`media.name ${LIKE} :text${queryId} COLLATE ${SQL_COLLATE}`, - textParam); + if ( + (query.type === SearchQueryTypes.any_text && !directoryOnly) || + query.type === SearchQueryTypes.file_name + ) { + q[whereFN]( + `media.name ${LIKE} :text${queryId} COLLATE ${SQL_COLLATE}`, + textParam + ); } - if ((query.type === SearchQueryTypes.any_text && !directoryOnly) || query.type === SearchQueryTypes.caption) { - q[whereFN](`media.metadata.caption ${LIKE} :text${queryId} COLLATE ${SQL_COLLATE}`, - textParam); + if ( + (query.type === SearchQueryTypes.any_text && !directoryOnly) || + query.type === SearchQueryTypes.caption + ) { + q[whereFN]( + `media.metadata.caption ${LIKE} :text${queryId} COLLATE ${SQL_COLLATE}`, + textParam + ); } - if ((query.type === SearchQueryTypes.any_text && !directoryOnly) || query.type === SearchQueryTypes.position) { - q[whereFN](`media.metadata.positionData.country ${LIKE} :text${queryId} COLLATE ${SQL_COLLATE}`, - textParam) - [whereFN](`media.metadata.positionData.state ${LIKE} :text${queryId} COLLATE ${SQL_COLLATE}`, - textParam) - [whereFN](`media.metadata.positionData.city ${LIKE} :text${queryId} COLLATE ${SQL_COLLATE}`, - textParam); + if ( + (query.type === SearchQueryTypes.any_text && !directoryOnly) || + query.type === SearchQueryTypes.position + ) { + q[whereFN]( + `media.metadata.positionData.country ${LIKE} :text${queryId} COLLATE ${SQL_COLLATE}`, + textParam + ) + [whereFN]( + `media.metadata.positionData.state ${LIKE} :text${queryId} COLLATE ${SQL_COLLATE}`, + textParam + ) + [whereFN]( + `media.metadata.positionData.city ${LIKE} :text${queryId} COLLATE ${SQL_COLLATE}`, + textParam + ); } // Matching for array type fields const matchArrayField = (fieldName: string): void => { - q[whereFN](new Brackets((qbr): void => { - if ((query as TextSearch).matchType !== TextSearchQueryMatchTypes.exact_match) { - qbr[whereFN](`${fieldName} ${LIKE} :text${queryId} COLLATE ${SQL_COLLATE}`, - textParam); - } else { - qbr[whereFN](new Brackets((qb): void => { - textParam['CtextC' + queryId] = `%,${(query as TextSearch).text},%`; - textParam['Ctext' + queryId] = `%,${(query as TextSearch).text}`; - textParam['textC' + queryId] = `${(query as TextSearch).text},%`; - textParam['text_exact' + queryId] = `${(query as TextSearch).text}`; + q[whereFN]( + new Brackets((qbr): void => { + if ( + (query as TextSearch).matchType !== + TextSearchQueryMatchTypes.exact_match + ) { + qbr[whereFN]( + `${fieldName} ${LIKE} :text${queryId} COLLATE ${SQL_COLLATE}`, + textParam + ); + } else { + qbr[whereFN]( + new Brackets((qb): void => { + textParam['CtextC' + queryId] = `%,${ + (query as TextSearch).text + },%`; + textParam['Ctext' + queryId] = `%,${ + (query as TextSearch).text + }`; + textParam['textC' + queryId] = `${ + (query as TextSearch).text + },%`; + textParam['text_exact' + queryId] = `${ + (query as TextSearch).text + }`; - qb[whereFN](`${fieldName} ${LIKE} :CtextC${queryId} COLLATE ${SQL_COLLATE}`, - textParam); - qb[whereFN](`${fieldName} ${LIKE} :Ctext${queryId} COLLATE ${SQL_COLLATE}`, - textParam); - qb[whereFN](`${fieldName} ${LIKE} :textC${queryId} COLLATE ${SQL_COLLATE}`, - textParam); - qb[whereFN](`${fieldName} ${LIKE} :text_exact${queryId} COLLATE ${SQL_COLLATE}`, - textParam); - })); - } - if ((query as TextSearch).negate) { - qbr.orWhere(`${fieldName} IS NULL`); - } - })); + qb[whereFN]( + `${fieldName} ${LIKE} :CtextC${queryId} COLLATE ${SQL_COLLATE}`, + textParam + ); + qb[whereFN]( + `${fieldName} ${LIKE} :Ctext${queryId} COLLATE ${SQL_COLLATE}`, + textParam + ); + qb[whereFN]( + `${fieldName} ${LIKE} :textC${queryId} COLLATE ${SQL_COLLATE}`, + textParam + ); + qb[whereFN]( + `${fieldName} ${LIKE} :text_exact${queryId} COLLATE ${SQL_COLLATE}`, + textParam + ); + }) + ); + } + if ((query as TextSearch).negate) { + qbr.orWhere(`${fieldName} IS NULL`); + } + }) + ); }; - - if ((query.type === SearchQueryTypes.any_text && !directoryOnly) || query.type === SearchQueryTypes.person) { + if ( + (query.type === SearchQueryTypes.any_text && !directoryOnly) || + query.type === SearchQueryTypes.person + ) { matchArrayField('media.metadata.persons'); } - if ((query.type === SearchQueryTypes.any_text && !directoryOnly) || query.type === SearchQueryTypes.keyword) { + if ( + (query.type === SearchQueryTypes.any_text && !directoryOnly) || + query.type === SearchQueryTypes.keyword + ) { matchArrayField('media.metadata.keywords'); } return q; @@ -591,7 +866,9 @@ export class SearchManager implements ISQLSearchManager { case SearchQueryTypes.OR: return { type: query.type, - list: (query as SearchListQuery).list.map((q): SearchQueryDTO => this.flattenSameOfQueries(q)) + list: (query as SearchListQuery).list.map( + (q): SearchQueryDTO => this.flattenSameOfQueries(q) + ), } as SearchListQuery; case SearchQueryTypes.SOME_OF: const someOfQ = query as SomeOfSearchQuery; @@ -600,18 +877,22 @@ export class SearchManager implements ISQLSearchManager { if (someOfQ.min === 1) { return this.flattenSameOfQueries({ type: SearchQueryTypes.OR, - list: (someOfQ as SearchListQuery).list + list: (someOfQ as SearchListQuery).list, } as ORSearchQuery); } if (someOfQ.min === (query as SearchListQuery).list.length) { return this.flattenSameOfQueries({ type: SearchQueryTypes.AND, - list: (someOfQ as SearchListQuery).list + list: (someOfQ as SearchListQuery).list, } as ANDSearchQuery); } - const getAllCombinations = (num: number, arr: SearchQueryDTO[], start = 0): SearchQueryDTO[] => { + const getAllCombinations = ( + num: number, + arr: SearchQueryDTO[], + start = 0 + ): SearchQueryDTO[] => { if (num <= 0 || num > arr.length || start >= arr.length) { return null; } @@ -619,10 +900,12 @@ export class SearchManager implements ISQLSearchManager { return arr.slice(start); } if (num === arr.length - start) { - return [{ - type: SearchQueryTypes.AND, - list: arr.slice(start) - } as ANDSearchQuery]; + return [ + { + type: SearchQueryTypes.AND, + list: arr.slice(start), + } as ANDSearchQuery, + ]; } const ret: ANDSearchQuery[] = []; for (let i = start; i < arr.length; ++i) { @@ -632,9 +915,7 @@ export class SearchManager implements ISQLSearchManager { } const and: ANDSearchQuery = { type: SearchQueryTypes.AND, - list: [ - arr[i] - ] + list: [arr[i]], }; if (subRes.length === 1) { if (subRes[0].type === SearchQueryTypes.AND) { @@ -645,11 +926,10 @@ export class SearchManager implements ISQLSearchManager { } else { and.list.push({ type: SearchQueryTypes.OR, - list: subRes + list: subRes, } as ORSearchQuery); } ret.push(and); - } if (ret.length === 0) { @@ -658,12 +938,13 @@ export class SearchManager implements ISQLSearchManager { return ret; }; - return this.flattenSameOfQueries({ type: SearchQueryTypes.OR, - list: getAllCombinations(someOfQ.min, (query as SearchListQuery).list) + list: getAllCombinations( + someOfQ.min, + (query as SearchListQuery).list + ), } as ORSearchQuery); - } return query; } @@ -673,8 +954,10 @@ export class SearchManager implements ISQLSearchManager { * so less parameters are needed to pass down to SQL. * Witch SOME_OF query the number of WHERE constrains have O(N!) complexity */ - private assignQueryIDs(queryIN: SearchQueryDTO, id = {value: 1}): SearchQueryDTO { - + private assignQueryIDs( + queryIN: SearchQueryDTO, + id = { value: 1 } + ): SearchQueryDTO { // It is possible that one SQL query contains multiple searchQueries // (like: where ( AND ()) // lets make params unique across multiple queries @@ -685,10 +968,13 @@ export class SearchManager implements ISQLSearchManager { } } if ((queryIN as SearchListQuery).list) { - (queryIN as SearchListQuery).list.forEach(q => this.assignQueryIDs(q, id)); + (queryIN as SearchListQuery).list.forEach((q) => + this.assignQueryIDs(q, id) + ); return queryIN; } - (queryIN as SearchQueryDTOWithID).queryId = this.queryIdBase + '_' + id.value; + (queryIN as SearchQueryDTOWithID).queryId = + this.queryIdBase + '_' + id.value; id.value++; return queryIN; } @@ -701,7 +987,9 @@ export class SearchManager implements ISQLSearchManager { case SearchQueryTypes.AND: const andRet = { type: SearchQueryTypes.AND, - list: (query as SearchListQuery).list.map(q => this.filterDirectoryQuery(q)) + list: (query as SearchListQuery).list.map((q) => + this.filterDirectoryQuery(q) + ), } as ANDSearchQuery; // if any of the queries contain non dir query thw whole and query is a non dir query if (andRet.list.indexOf(null) !== -1) { @@ -712,7 +1000,9 @@ export class SearchManager implements ISQLSearchManager { case SearchQueryTypes.OR: const orRet = { type: SearchQueryTypes.OR, - list: (query as SearchListQuery).list.map(q => this.filterDirectoryQuery(q)).filter(q => q !== null) + list: (query as SearchListQuery).list + .map((q) => this.filterDirectoryQuery(q)) + .filter((q) => q !== null), } as ORSearchQuery; if (orRet.list.length === 0) { return null; @@ -732,27 +1022,39 @@ export class SearchManager implements ISQLSearchManager { private async getGPSData(query: SearchQueryDTO): Promise { if ((query as ANDSearchQuery | ORSearchQuery).list) { - for (let i = 0; i < (query as ANDSearchQuery | ORSearchQuery).list.length; ++i) { + for ( + let i = 0; + i < (query as ANDSearchQuery | ORSearchQuery).list.length; + ++i + ) { (query as ANDSearchQuery | ORSearchQuery).list[i] = - await this.getGPSData((query as ANDSearchQuery | ORSearchQuery).list[i]); + await this.getGPSData( + (query as ANDSearchQuery | ORSearchQuery).list[i] + ); } } - if (query.type === SearchQueryTypes.distance && (query as DistanceSearch).from.text) { + if ( + query.type === SearchQueryTypes.distance && + (query as DistanceSearch).from.text + ) { (query as DistanceSearch).from.GPSData = - await ObjectManagers.getInstance().LocationManager.getGPSData((query as DistanceSearch).from.text); + await ObjectManagers.getInstance().LocationManager.getGPSData( + (query as DistanceSearch).from.text + ); } return query; } - private encapsulateAutoComplete(values: string[], type: SearchQueryTypes): Array { + private encapsulateAutoComplete( + values: string[], + type: SearchQueryTypes + ): Array { const res: AutoCompleteItem[] = []; values.forEach((value): void => { res.push(new AutoCompleteItem(value, type)); }); return res; } - - } export interface SearchQueryDTOWithID extends SearchQueryDTO { diff --git a/src/backend/model/database/sql/SharingManager.ts b/src/backend/model/database/sql/SharingManager.ts index bc0bcd5d..fb6a692d 100644 --- a/src/backend/model/database/sql/SharingManager.ts +++ b/src/backend/model/database/sql/SharingManager.ts @@ -1,35 +1,38 @@ -import {ISharingManager} from '../interfaces/ISharingManager'; -import {SharingDTO} from '../../../../common/entities/SharingDTO'; -import {SQLConnection} from './SQLConnection'; -import {SharingEntity} from './enitites/SharingEntity'; -import {Config} from '../../../../common/config/private/Config'; -import {PasswordHelper} from '../../PasswordHelper'; -import {DeleteResult, FindOptionsWhere} from 'typeorm'; +import { ISharingManager } from '../interfaces/ISharingManager'; +import { SharingDTO } from '../../../../common/entities/SharingDTO'; +import { SQLConnection } from './SQLConnection'; +import { SharingEntity } from './enitites/SharingEntity'; +import { Config } from '../../../../common/config/private/Config'; +import { PasswordHelper } from '../../PasswordHelper'; +import { DeleteResult, FindOptionsWhere } from 'typeorm'; export class SharingManager implements ISharingManager { - private static async removeExpiredLink(): Promise { const connection = await SQLConnection.getConnection(); return await connection .getRepository(SharingEntity) .createQueryBuilder('share') - .where('expires < :now', {now: Date.now()}) + .where('expires < :now', { now: Date.now() }) .delete() .execute(); } async deleteSharing(sharingKey: string): Promise { const connection = await SQLConnection.getConnection(); - const sharing = await connection.getRepository(SharingEntity).findOneBy({sharingKey}); + const sharing = await connection + .getRepository(SharingEntity) + .findOneBy({ sharingKey }); await connection.getRepository(SharingEntity).remove(sharing); } async listAll(): Promise { await SharingManager.removeExpiredLink(); const connection = await SQLConnection.getConnection(); - return await connection.getRepository(SharingEntity) + return await connection + .getRepository(SharingEntity) .createQueryBuilder('share') - .leftJoinAndSelect('share.creator', 'creator').getMany(); + .leftJoinAndSelect('share.creator', 'creator') + .getMany(); } async findOne(filter: FindOptionsWhere): Promise { @@ -47,17 +50,23 @@ export class SharingManager implements ISharingManager { return connection.getRepository(SharingEntity).save(sharing); } - async updateSharing(inSharing: SharingDTO, forceUpdate: boolean): Promise { + async updateSharing( + inSharing: SharingDTO, + forceUpdate: boolean + ): Promise { const connection = await SQLConnection.getConnection(); const sharing = await connection.getRepository(SharingEntity).findOneBy({ id: inSharing.id, creator: inSharing.creator.id as any, - path: inSharing.path + path: inSharing.path, }); - if (sharing.timeStamp < Date.now() - Config.Server.Sharing.updateTimeout && forceUpdate !== true) { - throw new Error('Sharing is locked, can\'t update anymore'); + if ( + sharing.timeStamp < Date.now() - Config.Server.Sharing.updateTimeout && + forceUpdate !== true + ) { + throw new Error("Sharing is locked, can't update anymore"); } if (inSharing.password == null) { sharing.password = null; @@ -69,6 +78,4 @@ export class SharingManager implements ISharingManager { return connection.getRepository(SharingEntity).save(sharing); } - - } diff --git a/src/backend/model/database/sql/UserManager.ts b/src/backend/model/database/sql/UserManager.ts index 088f5f91..3e58742b 100644 --- a/src/backend/model/database/sql/UserManager.ts +++ b/src/backend/model/database/sql/UserManager.ts @@ -1,28 +1,23 @@ -import {UserDTO, UserRoles} from '../../../../common/entities/UserDTO'; -import {IUserManager} from '../interfaces/IUserManager'; -import {UserEntity} from './enitites/UserEntity'; -import {SQLConnection} from './SQLConnection'; -import {PasswordHelper} from '../../PasswordHelper'; -import {FindOptionsWhere} from 'typeorm'; - +import { UserDTO, UserRoles } from '../../../../common/entities/UserDTO'; +import { IUserManager } from '../interfaces/IUserManager'; +import { UserEntity } from './enitites/UserEntity'; +import { SQLConnection } from './SQLConnection'; +import { PasswordHelper } from '../../PasswordHelper'; +import { FindOptionsWhere } from 'typeorm'; export class UserManager implements IUserManager { - - constructor() { - } - + constructor() {} public async findOne(filter: FindOptionsWhere): Promise { const connection = await SQLConnection.getConnection(); const pass = filter.password as string; delete filter.password; - const user = (await connection.getRepository(UserEntity).findOneBy(filter)); + const user = await connection.getRepository(UserEntity).findOneBy(filter); if (pass && !PasswordHelper.comparePassword(pass, user.password)) { throw new Error('No entry found'); } return user; - } public async find(filter: FindOptionsWhere): Promise { @@ -38,22 +33,19 @@ export class UserManager implements IUserManager { public async deleteUser(id: number): Promise { const connection = await SQLConnection.getConnection(); - const user = await connection.getRepository(UserEntity).findOneBy({id}); + const user = await connection.getRepository(UserEntity).findOneBy({ id }); return await connection.getRepository(UserEntity).remove(user); } public async changeRole(id: number, newRole: UserRoles): Promise { - const connection = await SQLConnection.getConnection(); const userRepository = connection.getRepository(UserEntity); - const user = await userRepository.findOneBy({id}); + const user = await userRepository.findOneBy({ id }); user.role = newRole; return userRepository.save(user); - } public async changePassword(request: any): Promise { throw new Error('not implemented'); // TODO: implement } - } diff --git a/src/backend/model/database/sql/VersionManager.ts b/src/backend/model/database/sql/VersionManager.ts index 5495c2b5..ff8ba976 100644 --- a/src/backend/model/database/sql/VersionManager.ts +++ b/src/backend/model/database/sql/VersionManager.ts @@ -1,17 +1,16 @@ import * as crypto from 'crypto'; -import {IVersionManager} from '../interfaces/IVersionManager'; -import {DataStructureVersion} from '../../../../common/DataStructureVersion'; -import {SQLConnection} from './SQLConnection'; -import {DirectoryEntity} from './enitites/DirectoryEntity'; -import {MediaEntity} from './enitites/MediaEntity'; +import { IVersionManager } from '../interfaces/IVersionManager'; +import { DataStructureVersion } from '../../../../common/DataStructureVersion'; +import { SQLConnection } from './SQLConnection'; +import { DirectoryEntity } from './enitites/DirectoryEntity'; +import { MediaEntity } from './enitites/MediaEntity'; export class VersionManager implements IVersionManager { - private allMediaCount = 0; private latestDirectoryStatus: { - name: string, - lastModified: number, - mediaCount: number + name: string; + lastModified: number; + mediaCount: number; } = null; async getDataVersion(): Promise { @@ -23,22 +22,31 @@ export class VersionManager implements IVersionManager { return DataStructureVersion.toString(); } - const versionString = DataStructureVersion + '_' + - this.latestDirectoryStatus.name + '_' + - this.latestDirectoryStatus.lastModified + '_' + - this.latestDirectoryStatus.mediaCount + '_' + + const versionString = + DataStructureVersion + + '_' + + this.latestDirectoryStatus.name + + '_' + + this.latestDirectoryStatus.lastModified + + '_' + + this.latestDirectoryStatus.mediaCount + + '_' + this.allMediaCount; return crypto.createHash('md5').update(versionString).digest('hex'); } async onNewDataVersion(): Promise { const connection = await SQLConnection.getConnection(); - const dir = await connection.getRepository(DirectoryEntity) + const dir = await connection + .getRepository(DirectoryEntity) .createQueryBuilder('directory') .limit(1) - .orderBy('directory.lastModified').getOne(); - this.allMediaCount = await connection.getRepository(MediaEntity) - .createQueryBuilder('media').getCount(); + .orderBy('directory.lastModified') + .getOne(); + this.allMediaCount = await connection + .getRepository(MediaEntity) + .createQueryBuilder('media') + .getCount(); if (!dir) { return; @@ -46,9 +54,7 @@ export class VersionManager implements IVersionManager { this.latestDirectoryStatus = { mediaCount: dir.mediaCount, lastModified: dir.lastModified, - name: dir.name + name: dir.name, }; } - - } diff --git a/src/backend/model/database/sql/enitites/DirectoryEntity.ts b/src/backend/model/database/sql/enitites/DirectoryEntity.ts index 7281d358..70e7d3d1 100644 --- a/src/backend/model/database/sql/enitites/DirectoryEntity.ts +++ b/src/backend/model/database/sql/enitites/DirectoryEntity.ts @@ -1,16 +1,28 @@ -import {Column, Entity, Index, ManyToOne, OneToMany, PrimaryGeneratedColumn, Unique} from 'typeorm'; -import {ParentDirectoryDTO, SubDirectoryDTO} from '../../../../../common/entities/DirectoryDTO'; -import {MediaEntity} from './MediaEntity'; -import {FileEntity} from './FileEntity'; -import {columnCharsetCS} from './EntityUtils'; -import {MediaDTO} from '../../../../../common/entities/MediaDTO'; +import { + Column, + Entity, + Index, + ManyToOne, + OneToMany, + PrimaryGeneratedColumn, + Unique, +} from 'typeorm'; +import { + ParentDirectoryDTO, + SubDirectoryDTO, +} from '../../../../../common/entities/DirectoryDTO'; +import { MediaEntity } from './MediaEntity'; +import { FileEntity } from './FileEntity'; +import { columnCharsetCS } from './EntityUtils'; +import { MediaDTO } from '../../../../../common/entities/MediaDTO'; @Entity() @Unique(['name', 'path']) -export class DirectoryEntity implements ParentDirectoryDTO, SubDirectoryDTO { - +export class DirectoryEntity + implements ParentDirectoryDTO, SubDirectoryDTO +{ @Index() - @PrimaryGeneratedColumn({unsigned: true}) + @PrimaryGeneratedColumn({ unsigned: true }) id: number; @Index() @@ -25,10 +37,11 @@ export class DirectoryEntity implements ParentDirectoryDTO, SubDirecto * last time the directory was modified (from outside, eg.: a new media was added) */ @Column('bigint', { - unsigned: true, transformer: { - from: v => parseInt(v, 10), - to: v => v - } + unsigned: true, + transformer: { + from: (v) => parseInt(v, 10), + to: (v) => v, + }, }) public lastModified: number; @@ -36,37 +49,41 @@ export class DirectoryEntity implements ParentDirectoryDTO, SubDirecto * Last time the directory was fully scanned, not only for a few media to create a preview */ @Column({ - type: 'bigint', nullable: true, unsigned: true, transformer: { - from: v => parseInt(v, 10) || null, - to: v => v - } + type: 'bigint', + nullable: true, + unsigned: true, + transformer: { + from: (v) => parseInt(v, 10) || null, + to: (v) => v, + }, }) public lastScanned: number; isPartial?: boolean; - @Column('smallint', {unsigned: true}) + @Column('smallint', { unsigned: true }) mediaCount: number; @Index() - @ManyToOne(type => DirectoryEntity, directory => directory.directories, {onDelete: 'CASCADE'}) + @ManyToOne((type) => DirectoryEntity, (directory) => directory.directories, { + onDelete: 'CASCADE', + }) public parent: DirectoryEntity; - @OneToMany(type => DirectoryEntity, dir => dir.parent) + @OneToMany((type) => DirectoryEntity, (dir) => dir.parent) public directories: DirectoryEntity[]; // not saving to database, it is only assigned when querying the DB - @ManyToOne(type => MediaEntity, {onDelete: 'SET NULL'}) + @ManyToOne((type) => MediaEntity, { onDelete: 'SET NULL' }) public preview: MediaEntity; // On galley change, preview will be invalid - @Column({type: 'boolean', default: false}) + @Column({ type: 'boolean', default: false }) validPreview: boolean; - @OneToMany(type => MediaEntity, media => media.directory) + @OneToMany((type) => MediaEntity, (media) => media.directory) public media: MediaEntity[]; - @OneToMany(type => FileEntity, file => file.directory) + @OneToMany((type) => FileEntity, (file) => file.directory) public metaFile: FileEntity[]; - } diff --git a/src/backend/model/database/sql/enitites/EntityUtils.ts b/src/backend/model/database/sql/enitites/EntityUtils.ts index 99b4a5de..0cda99e9 100644 --- a/src/backend/model/database/sql/enitites/EntityUtils.ts +++ b/src/backend/model/database/sql/enitites/EntityUtils.ts @@ -1,16 +1,18 @@ -import {Config} from '../../../../../common/config/private/Config'; -import {ColumnOptions} from 'typeorm/decorator/options/ColumnOptions'; -import {DatabaseType} from '../../../../../common/config/private/PrivateConfig'; +import { Config } from '../../../../../common/config/private/Config'; +import { ColumnOptions } from 'typeorm/decorator/options/ColumnOptions'; +import { DatabaseType } from '../../../../../common/config/private/PrivateConfig'; export class ColumnCharsetCS implements ColumnOptions { - public get charset(): string { - return Config.Server.Database.type === DatabaseType.mysql ? 'utf8mb4' : 'utf8'; + return Config.Server.Database.type === DatabaseType.mysql + ? 'utf8mb4' + : 'utf8'; } public get collation(): string { - return Config.Server.Database.type === DatabaseType.mysql ? 'utf8mb4_bin' : null; - + return Config.Server.Database.type === DatabaseType.mysql + ? 'utf8mb4_bin' + : null; } } diff --git a/src/backend/model/database/sql/enitites/FaceRegionEntry.ts b/src/backend/model/database/sql/enitites/FaceRegionEntry.ts index ee32c6f4..b0a08d90 100644 --- a/src/backend/model/database/sql/enitites/FaceRegionEntry.ts +++ b/src/backend/model/database/sql/enitites/FaceRegionEntry.ts @@ -1,7 +1,7 @@ -import {FaceRegion, FaceRegionBox} from '../../../../../common/entities/PhotoDTO'; -import {Column, Entity, ManyToOne, PrimaryGeneratedColumn} from 'typeorm'; -import {PersonEntry} from './PersonEntry'; -import {MediaEntity} from './MediaEntity'; +import { FaceRegionBox } from '../../../../../common/entities/PhotoDTO'; +import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; +import { PersonEntry } from './PersonEntry'; +import { MediaEntity } from './MediaEntity'; export class FaceRegionBoxEntry implements FaceRegionBox { @Column('int') @@ -19,19 +19,23 @@ export class FaceRegionBoxEntry implements FaceRegionBox { */ @Entity() export class FaceRegionEntry { - - @PrimaryGeneratedColumn({unsigned: true}) + @PrimaryGeneratedColumn({ unsigned: true }) id: number; - @Column(type => FaceRegionBoxEntry) + @Column((type) => FaceRegionBoxEntry) box: FaceRegionBoxEntry; - @ManyToOne(type => MediaEntity, media => media.metadata.faces, {onDelete: 'CASCADE', nullable: false}) + @ManyToOne((type) => MediaEntity, (media) => media.metadata.faces, { + onDelete: 'CASCADE', + nullable: false, + }) media: MediaEntity; - @ManyToOne(type => PersonEntry, person => person.faces, {onDelete: 'CASCADE', nullable: false}) + @ManyToOne((type) => PersonEntry, (person) => person.faces, { + onDelete: 'CASCADE', + nullable: false, + }) person: PersonEntry; name: string; - } diff --git a/src/backend/model/database/sql/enitites/FileEntity.ts b/src/backend/model/database/sql/enitites/FileEntity.ts index 40ffb017..cff4f083 100644 --- a/src/backend/model/database/sql/enitites/FileEntity.ts +++ b/src/backend/model/database/sql/enitites/FileEntity.ts @@ -1,20 +1,27 @@ -import {Column, Entity, Index, ManyToOne, PrimaryGeneratedColumn} from 'typeorm'; -import {DirectoryEntity} from './DirectoryEntity'; -import {FileDTO} from '../../../../../common/entities/FileDTO'; -import {columnCharsetCS} from './EntityUtils'; - +import { + Column, + Entity, + Index, + ManyToOne, + PrimaryGeneratedColumn, +} from 'typeorm'; +import { DirectoryEntity } from './DirectoryEntity'; +import { FileDTO } from '../../../../../common/entities/FileDTO'; +import { columnCharsetCS } from './EntityUtils'; @Entity() export class FileEntity implements FileDTO { - @Index() - @PrimaryGeneratedColumn({unsigned: true}) + @PrimaryGeneratedColumn({ unsigned: true }) id: number; @Column(columnCharsetCS) name: string; @Index() - @ManyToOne(type => DirectoryEntity, directory => directory.metaFile, {onDelete: 'CASCADE', nullable: false}) + @ManyToOne((type) => DirectoryEntity, (directory) => directory.metaFile, { + onDelete: 'CASCADE', + nullable: false, + }) directory: DirectoryEntity; } diff --git a/src/backend/model/database/sql/enitites/MediaEntity.ts b/src/backend/model/database/sql/enitites/MediaEntity.ts index fd8b2868..d56f43c5 100644 --- a/src/backend/model/database/sql/enitites/MediaEntity.ts +++ b/src/backend/model/database/sql/enitites/MediaEntity.ts @@ -1,12 +1,28 @@ -import {Column, Entity, Index, ManyToOne, OneToMany, PrimaryGeneratedColumn, TableInheritance, Unique} from 'typeorm'; -import {DirectoryEntity} from './DirectoryEntity'; -import {MediaDimension, MediaDTO, MediaMetadata} from '../../../../../common/entities/MediaDTO'; -import {FaceRegionEntry} from './FaceRegionEntry'; -import {columnCharsetCS} from './EntityUtils'; -import {CameraMetadata, GPSMetadata, PositionMetaData} from '../../../../../common/entities/PhotoDTO'; +import { + Column, + Entity, + Index, + ManyToOne, + OneToMany, + PrimaryGeneratedColumn, + TableInheritance, + Unique, +} from 'typeorm'; +import { DirectoryEntity } from './DirectoryEntity'; +import { + MediaDimension, + MediaDTO, + MediaMetadata, +} from '../../../../../common/entities/MediaDTO'; +import { FaceRegionEntry } from './FaceRegionEntry'; +import { columnCharsetCS } from './EntityUtils'; +import { + CameraMetadata, + GPSMetadata, + PositionMetaData, +} from '../../../../../common/entities/PhotoDTO'; export class MediaDimensionEntity implements MediaDimension { - @Column('int') width: number; @@ -14,84 +30,80 @@ export class MediaDimensionEntity implements MediaDimension { height: number; } - export class CameraMetadataEntity implements CameraMetadata { - - @Column('int', {nullable: true, unsigned: true}) + @Column('int', { nullable: true, unsigned: true }) ISO: number; - @Column({ - type: 'text', nullable: true, + type: 'text', + nullable: true, charset: columnCharsetCS.charset, - collation: columnCharsetCS.collation + collation: columnCharsetCS.collation, }) model: string; - @Column({ - type: 'text', nullable: true, + type: 'text', + nullable: true, charset: columnCharsetCS.charset, - collation: columnCharsetCS.collation + collation: columnCharsetCS.collation, }) make: string; - @Column('float', {nullable: true}) + @Column('float', { nullable: true }) fStop: number; - @Column('float', {nullable: true}) + @Column('float', { nullable: true }) exposure: number; - @Column('float', {nullable: true}) + @Column('float', { nullable: true }) focalLength: number; - @Column('text', {nullable: true}) + @Column('text', { nullable: true }) lens: string; } - export class GPSMetadataEntity implements GPSMetadata { - - @Column('float', {nullable: true}) + @Column('float', { nullable: true }) latitude: number; - @Column('float', {nullable: true}) + @Column('float', { nullable: true }) longitude: number; } - export class PositionMetaDataEntity implements PositionMetaData { - - @Column(type => GPSMetadataEntity) + @Column((type) => GPSMetadataEntity) GPSData: GPSMetadataEntity; @Column({ - type: 'text', nullable: true, + type: 'text', + nullable: true, charset: columnCharsetCS.charset, - collation: columnCharsetCS.collation + collation: columnCharsetCS.collation, }) country: string; @Column({ - type: 'text', nullable: true, + type: 'text', + nullable: true, charset: columnCharsetCS.charset, - collation: columnCharsetCS.collation + collation: columnCharsetCS.collation, }) state: string; @Column({ - type: 'text', nullable: true, + type: 'text', + nullable: true, charset: columnCharsetCS.charset, - collation: columnCharsetCS.collation + collation: columnCharsetCS.collation, }) city: string; } - export class MediaMetadataEntity implements MediaMetadata { @Column('text') caption: string; - @Column(type => MediaDimensionEntity) + @Column((type) => MediaDimensionEntity) size: MediaDimensionEntity; /** @@ -101,70 +113,73 @@ export class MediaMetadataEntity implements MediaMetadata { */ @Column('bigint', { transformer: { - from: v => parseInt(v, 10), - to: v => v - } + from: (v) => parseInt(v, 10), + to: (v) => v, + }, }) creationDate: number; - @Column('int', {unsigned: true}) + @Column('int', { unsigned: true }) fileSize: number; @Column({ type: 'simple-array', charset: columnCharsetCS.charset, - collation: columnCharsetCS.collation + collation: columnCharsetCS.collation, }) keywords: string[]; - @Column(type => CameraMetadataEntity) + @Column((type) => CameraMetadataEntity) cameraData: CameraMetadataEntity; - @Column(type => PositionMetaDataEntity) + @Column((type) => PositionMetaDataEntity) positionData: PositionMetaDataEntity; - @Column('tinyint', {unsigned: true}) + @Column('tinyint', { unsigned: true }) rating: 0 | 1 | 2 | 3 | 4 | 5; - @OneToMany(type => FaceRegionEntry, faceRegion => faceRegion.media) + @OneToMany((type) => FaceRegionEntry, (faceRegion) => faceRegion.media) faces: FaceRegionEntry[]; /** * Caches the list of persons. Only used for searching */ @Column({ - type: 'simple-array', select: false, nullable: true, + type: 'simple-array', + select: false, + nullable: true, charset: columnCharsetCS.charset, - collation: columnCharsetCS.collation + collation: columnCharsetCS.collation, }) persons: string[]; - @Column('int', {unsigned: true}) + @Column('int', { unsigned: true }) bitRate: number; - @Column('int', {unsigned: true}) + @Column('int', { unsigned: true }) duration: number; } - // TODO: fix inheritance once its working in typeorm @Entity() @Unique(['name', 'directory']) -@TableInheritance({column: {type: 'varchar', name: 'type', length: 16}}) +@TableInheritance({ column: { type: 'varchar', name: 'type', length: 16 } }) export abstract class MediaEntity implements MediaDTO { - @Index() - @PrimaryGeneratedColumn({unsigned: true}) + @PrimaryGeneratedColumn({ unsigned: true }) id: number; @Column(columnCharsetCS) name: string; @Index() - @ManyToOne(type => DirectoryEntity, directory => directory.media, {onDelete: 'CASCADE', nullable: false}) + @ManyToOne((type) => DirectoryEntity, (directory) => directory.media, { + onDelete: 'CASCADE', + nullable: false, + }) directory: DirectoryEntity; - @Column(type => MediaMetadataEntity) + @Column((type) => MediaMetadataEntity) metadata: MediaMetadataEntity; missingThumbnails: number; diff --git a/src/backend/model/database/sql/enitites/PersonEntry.ts b/src/backend/model/database/sql/enitites/PersonEntry.ts index 74f5f178..777725dc 100644 --- a/src/backend/model/database/sql/enitites/PersonEntry.ts +++ b/src/backend/model/database/sql/enitites/PersonEntry.ts @@ -1,31 +1,38 @@ -import {Column, Entity, Index, ManyToOne, OneToMany, PrimaryGeneratedColumn, Unique} from 'typeorm'; -import {FaceRegionEntry} from './FaceRegionEntry'; -import {columnCharsetCS} from './EntityUtils'; -import {PersonWithSampleRegion} from '../../../../../common/entities/PersonDTO'; - +import { + Column, + Entity, + Index, + ManyToOne, + OneToMany, + PrimaryGeneratedColumn, + Unique, +} from 'typeorm'; +import { FaceRegionEntry } from './FaceRegionEntry'; +import { columnCharsetCS } from './EntityUtils'; +import { PersonWithSampleRegion } from '../../../../../common/entities/PersonDTO'; @Entity() @Unique(['name']) export class PersonEntry implements PersonWithSampleRegion { - @Index() - @PrimaryGeneratedColumn({unsigned: true}) + @PrimaryGeneratedColumn({ unsigned: true }) id: number; @Column(columnCharsetCS) name: string; - @Column('int', {unsigned: true, default: 0}) + @Column('int', { unsigned: true, default: 0 }) count: number; - @Column({default: false}) + @Column({ default: false }) isFavourite: boolean; - @OneToMany(type => FaceRegionEntry, faceRegion => faceRegion.person) + @OneToMany((type) => FaceRegionEntry, (faceRegion) => faceRegion.person) public faces: FaceRegionEntry[]; - @ManyToOne(type => FaceRegionEntry, {onDelete: 'SET NULL', nullable: true}) + @ManyToOne((type) => FaceRegionEntry, { + onDelete: 'SET NULL', + nullable: true, + }) sampleRegion: FaceRegionEntry; - - } diff --git a/src/backend/model/database/sql/enitites/PhotoEntity.ts b/src/backend/model/database/sql/enitites/PhotoEntity.ts index dae4a539..266f9fe2 100644 --- a/src/backend/model/database/sql/enitites/PhotoEntity.ts +++ b/src/backend/model/database/sql/enitites/PhotoEntity.ts @@ -1,12 +1,13 @@ -import {ChildEntity, Column} from 'typeorm'; -import {CameraMetadata, GPSMetadata, PhotoDTO, PhotoMetadata, PositionMetaData} from '../../../../../common/entities/PhotoDTO'; -import {MediaEntity, MediaMetadataEntity} from './MediaEntity'; -import {columnCharsetCS} from './EntityUtils'; +import { ChildEntity, Column } from 'typeorm'; +import { + PhotoDTO, + PhotoMetadata, +} from '../../../../../common/entities/PhotoDTO'; +import { MediaEntity, MediaMetadataEntity } from './MediaEntity'; - - - -export class PhotoMetadataEntity extends MediaMetadataEntity implements PhotoMetadata { +export class PhotoMetadataEntity + extends MediaMetadataEntity + implements PhotoMetadata { /* @Column('simple-array') keywords: string[]; @@ -22,9 +23,8 @@ export class PhotoMetadataEntity extends MediaMetadataEntity implements PhotoMet */ } - @ChildEntity() export class PhotoEntity extends MediaEntity implements PhotoDTO { - @Column(type => PhotoMetadataEntity) + @Column((type) => PhotoMetadataEntity) metadata: PhotoMetadataEntity; } diff --git a/src/backend/model/database/sql/enitites/SharingEntity.ts b/src/backend/model/database/sql/enitites/SharingEntity.ts index 481ecdab..5c077d2e 100644 --- a/src/backend/model/database/sql/enitites/SharingEntity.ts +++ b/src/backend/model/database/sql/enitites/SharingEntity.ts @@ -1,11 +1,11 @@ -import {Column, Entity, ManyToOne, PrimaryGeneratedColumn} from 'typeorm'; -import {SharingDTO} from '../../../../../common/entities/SharingDTO'; -import {UserEntity} from './UserEntity'; -import {UserDTO} from '../../../../../common/entities/UserDTO'; +import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm'; +import { SharingDTO } from '../../../../../common/entities/SharingDTO'; +import { UserEntity } from './UserEntity'; +import { UserDTO } from '../../../../../common/entities/UserDTO'; @Entity() export class SharingEntity implements SharingDTO { - @PrimaryGeneratedColumn({unsigned: true}) + @PrimaryGeneratedColumn({ unsigned: true }) id: number; @Column() @@ -14,28 +14,30 @@ export class SharingEntity implements SharingDTO { @Column() path: string; - @Column({type: 'text', nullable: true}) + @Column({ type: 'text', nullable: true }) password: string; @Column('bigint', { - unsigned: true, transformer: { - from: v => parseInt(v, 10), - to: v => v - } + unsigned: true, + transformer: { + from: (v) => parseInt(v, 10), + to: (v) => v, + }, }) expires: number; @Column('bigint', { - unsigned: true, transformer: { - from: v => parseInt(v, 10), - to: v => v - } + unsigned: true, + transformer: { + from: (v) => parseInt(v, 10), + to: (v) => v, + }, }) timeStamp: number; @Column() includeSubfolders: boolean; - @ManyToOne(type => UserEntity, {onDelete: 'CASCADE', nullable: false}) + @ManyToOne((type) => UserEntity, { onDelete: 'CASCADE', nullable: false }) creator: UserDTO; } diff --git a/src/backend/model/database/sql/enitites/UserEntity.ts b/src/backend/model/database/sql/enitites/UserEntity.ts index dd0bc9ca..5a2edda4 100644 --- a/src/backend/model/database/sql/enitites/UserEntity.ts +++ b/src/backend/model/database/sql/enitites/UserEntity.ts @@ -1,10 +1,9 @@ -import {UserDTO, UserRoles} from '../../../../../common/entities/UserDTO'; -import {Column, Entity, PrimaryGeneratedColumn, Unique} from 'typeorm'; +import { UserDTO, UserRoles } from '../../../../../common/entities/UserDTO'; +import { Column, Entity, PrimaryGeneratedColumn, Unique } from 'typeorm'; @Entity() @Unique(['name']) export class UserEntity implements UserDTO { - @PrimaryGeneratedColumn() id: number; @@ -17,7 +16,6 @@ export class UserEntity implements UserDTO { @Column('smallint') role: UserRoles; - @Column('simple-array', {nullable: true}) + @Column('simple-array', { nullable: true }) permissions: string[]; - } diff --git a/src/backend/model/database/sql/enitites/VersionEntity.ts b/src/backend/model/database/sql/enitites/VersionEntity.ts index 6ff466df..f668fc01 100644 --- a/src/backend/model/database/sql/enitites/VersionEntity.ts +++ b/src/backend/model/database/sql/enitites/VersionEntity.ts @@ -1,12 +1,10 @@ -import {Column, Entity, PrimaryGeneratedColumn} from 'typeorm'; +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; @Entity() export class VersionEntity { - @PrimaryGeneratedColumn() id: number; @Column() version: number; - } diff --git a/src/backend/model/database/sql/enitites/VideoEntity.ts b/src/backend/model/database/sql/enitites/VideoEntity.ts index eaa0fa81..60a57f8b 100644 --- a/src/backend/model/database/sql/enitites/VideoEntity.ts +++ b/src/backend/model/database/sql/enitites/VideoEntity.ts @@ -1,29 +1,33 @@ -import {ChildEntity, Column} from 'typeorm'; -import {MediaEntity, MediaMetadataEntity} from './MediaEntity'; -import {VideoDTO, VideoMetadata} from '../../../../../common/entities/VideoDTO'; - - -export class VideoMetadataEntity extends MediaMetadataEntity implements VideoMetadata { +import { ChildEntity, Column } from 'typeorm'; +import { MediaEntity, MediaMetadataEntity } from './MediaEntity'; +import { + VideoDTO, + VideoMetadata, +} from '../../../../../common/entities/VideoDTO'; +export class VideoMetadataEntity + extends MediaMetadataEntity + implements VideoMetadata +{ @Column('int') bitRate: number; @Column('bigint', { - unsigned: true, nullable: true, transformer: { - from: v => parseInt(v, 10) || null, - to: v => v - } + unsigned: true, + nullable: true, + transformer: { + from: (v) => parseInt(v, 10) || null, + to: (v) => v, + }, }) duration: number; @Column('int') fps: number; - } - @ChildEntity() export class VideoEntity extends MediaEntity implements VideoDTO { - @Column(type => VideoMetadataEntity) + @Column((type) => VideoMetadataEntity) metadata: VideoMetadataEntity; } diff --git a/src/backend/model/database/sql/enitites/album/AlbumBaseEntity.ts b/src/backend/model/database/sql/enitites/album/AlbumBaseEntity.ts index 34407c05..e5722bf0 100644 --- a/src/backend/model/database/sql/enitites/album/AlbumBaseEntity.ts +++ b/src/backend/model/database/sql/enitites/album/AlbumBaseEntity.ts @@ -1,14 +1,20 @@ -import {Column, Entity, Index, ManyToOne, PrimaryGeneratedColumn, TableInheritance} from 'typeorm'; -import {MediaEntity} from '../MediaEntity'; -import {columnCharsetCS} from '../EntityUtils'; -import {AlbumBaseDTO} from '../../../../../../common/entities/album/AlbumBaseDTO'; +import { + Column, + Entity, + Index, + ManyToOne, + PrimaryGeneratedColumn, + TableInheritance, +} from 'typeorm'; +import { MediaEntity } from '../MediaEntity'; +import { columnCharsetCS } from '../EntityUtils'; +import { AlbumBaseDTO } from '../../../../../../common/entities/album/AlbumBaseDTO'; @Entity() -@TableInheritance({column: {type: 'varchar', name: 'type', length: 24}}) +@TableInheritance({ column: { type: 'varchar', name: 'type', length: 24 } }) export class AlbumBaseEntity implements AlbumBaseDTO { - @Index() - @PrimaryGeneratedColumn({unsigned: true}) + @PrimaryGeneratedColumn({ unsigned: true }) id: number; @Index() @@ -18,13 +24,12 @@ export class AlbumBaseEntity implements AlbumBaseDTO { /** * Locked albums are not possible to remove */ - @Column({default: false}) + @Column({ default: false }) locked: boolean; - @Column('int', {unsigned: true, default: 0}) + @Column('int', { unsigned: true, default: 0 }) count: number; - @ManyToOne(type => MediaEntity, {onDelete: 'SET NULL', nullable: true}) + @ManyToOne((type) => MediaEntity, { onDelete: 'SET NULL', nullable: true }) public preview: MediaEntity; - } diff --git a/src/backend/model/database/sql/enitites/album/SavedSearchEntity.ts b/src/backend/model/database/sql/enitites/album/SavedSearchEntity.ts index 5e7c89d3..ebf54e71 100644 --- a/src/backend/model/database/sql/enitites/album/SavedSearchEntity.ts +++ b/src/backend/model/database/sql/enitites/album/SavedSearchEntity.ts @@ -1,10 +1,13 @@ -import {ChildEntity, Column} from 'typeorm'; -import {AlbumBaseEntity} from './AlbumBaseEntity'; -import {SavedSearchDTO} from '../../../../../../common/entities/album/SavedSearchDTO'; -import {SearchQueryDTO} from '../../../../../../common/entities/SearchQueryDTO'; +import { ChildEntity, Column } from 'typeorm'; +import { AlbumBaseEntity } from './AlbumBaseEntity'; +import { SavedSearchDTO } from '../../../../../../common/entities/album/SavedSearchDTO'; +import { SearchQueryDTO } from '../../../../../../common/entities/SearchQueryDTO'; @ChildEntity() -export class SavedSearchEntity extends AlbumBaseEntity implements SavedSearchDTO { +export class SavedSearchEntity + extends AlbumBaseEntity + implements SavedSearchDTO +{ @Column({ type: 'text', nullable: false, @@ -16,8 +19,8 @@ export class SavedSearchEntity extends AlbumBaseEntity implements SavedSearchDTO // used to serialize your data to db field to: (val: object) => { return JSON.stringify(val); - } - } + }, + }, }) searchQuery: SearchQueryDTO; } diff --git a/src/backend/model/diagnostics/ConfigDiagnostics.ts b/src/backend/model/diagnostics/ConfigDiagnostics.ts index e662fd5e..4bd13b5e 100644 --- a/src/backend/model/diagnostics/ConfigDiagnostics.ts +++ b/src/backend/model/diagnostics/ConfigDiagnostics.ts @@ -1,9 +1,9 @@ -import {Config} from '../../../common/config/private/Config'; -import {Logger} from '../../Logger'; -import {NotificationManager} from '../NotifocationManager'; -import {SQLConnection} from '../database/sql/SQLConnection'; +import { Config } from '../../../common/config/private/Config'; +import { Logger } from '../../Logger'; +import { NotificationManager } from '../NotifocationManager'; +import { SQLConnection } from '../database/sql/SQLConnection'; import * as fs from 'fs'; -import {FFmpegFactory} from '../FFmpegFactory'; +import { FFmpegFactory } from '../FFmpegFactory'; import { ClientAlbumConfig, ClientFacesConfig, @@ -16,7 +16,7 @@ import { ClientThumbnailConfig, ClientVideoConfig, MapLayers, - MapProviders + MapProviders, } from '../../../common/config/public/ClientConfig'; import { DatabaseType, @@ -25,26 +25,34 @@ import { ServerJobConfig, ServerPhotoConfig, ServerPreviewConfig, - ServerVideoConfig + ServerThumbnailConfig, + ServerVideoConfig, } from '../../../common/config/private/PrivateConfig'; -import {SearchQueryParser} from '../../../common/SearchQueryParser'; -import {SearchQueryTypes, TextSearch} from '../../../common/entities/SearchQueryDTO'; -import {Utils} from '../../../common/Utils'; +import { SearchQueryParser } from '../../../common/SearchQueryParser'; +import { + SearchQueryTypes, + TextSearch, +} from '../../../common/entities/SearchQueryDTO'; +import { Utils } from '../../../common/Utils'; const LOG_TAG = '[ConfigDiagnostics]'; - export class ConfigDiagnostics { - static testAlbumsConfig(albumConfig: ClientAlbumConfig, original: IPrivateConfig): void { - if (albumConfig.enabled === true && - original.Server.Database.type === DatabaseType.memory) { + static testAlbumsConfig( + albumConfig: ClientAlbumConfig, + original: IPrivateConfig + ): void { + if ( + albumConfig.enabled === true && + original.Server.Database.type === DatabaseType.memory + ) { throw new Error('Memory Database does not support albums'); } } static checkReadWritePermission(path: string): Promise { return new Promise((resolve, reject) => { - // tslint:disable-next-line:no-bitwise + // eslint-disable-next-line no-bitwise fs.access(path, fs.constants.R_OK | fs.constants.W_OK, (err) => { if (err) { return reject(err); @@ -54,28 +62,35 @@ export class ConfigDiagnostics { }); } - static async testDatabase(databaseConfig: ServerDataBaseConfig): Promise { + static async testDatabase( + databaseConfig: ServerDataBaseConfig + ): Promise { if (databaseConfig.type !== DatabaseType.memory) { await SQLConnection.tryConnection(databaseConfig); } if (databaseConfig.type === DatabaseType.sqlite) { try { - await this.checkReadWritePermission(SQLConnection.getSQLiteDB(databaseConfig)); + await this.checkReadWritePermission( + SQLConnection.getSQLiteDB(databaseConfig) + ); } catch (e) { - throw new Error('Cannot read or write sqlite storage file: ' + SQLConnection.getSQLiteDB(databaseConfig)); + throw new Error( + 'Cannot read or write sqlite storage file: ' + + SQLConnection.getSQLiteDB(databaseConfig) + ); } } } - - static async testMetaFileConfig(metaFileConfig: ClientMetaFileConfig, config: IPrivateConfig): Promise { - if (metaFileConfig.gpx === true && - config.Client.Map.enabled === false) { + static async testMetaFileConfig( + metaFileConfig: ClientMetaFileConfig, + config: IPrivateConfig + ): Promise { + if (metaFileConfig.gpx === true && config.Client.Map.enabled === false) { throw new Error('*.gpx meta files are not supported without MAP'); } } - static testClientVideoConfig(videoConfig: ClientVideoConfig): Promise { return new Promise((resolve, reject) => { try { @@ -83,11 +98,21 @@ export class ConfigDiagnostics { const ffmpeg = FFmpegFactory.get(); ffmpeg().getAvailableCodecs((err: Error) => { if (err) { - return reject(new Error('Error accessing ffmpeg, cant find executable: ' + err.toString())); + return reject( + new Error( + 'Error accessing ffmpeg, cant find executable: ' + + err.toString() + ) + ); } ffmpeg(__dirname + '/blank.jpg').ffprobe((err2: Error) => { if (err2) { - return reject(new Error('Error accessing ffmpeg-probe, cant find executable: ' + err2.toString())); + return reject( + new Error( + 'Error accessing ffmpeg-probe, cant find executable: ' + + err2.toString() + ) + ); } return resolve(); }); @@ -101,7 +126,10 @@ export class ConfigDiagnostics { }); } - static async testServerVideoConfig(videoConfig: ServerVideoConfig, config: IPrivateConfig): Promise { + static async testServerVideoConfig( + videoConfig: ServerVideoConfig, + config: IPrivateConfig + ): Promise { if (config.Client.Media.Video.enabled === true) { if (videoConfig.transcoding.fps <= 0) { throw new Error('fps should be grater than 0'); @@ -114,7 +142,6 @@ export class ConfigDiagnostics { sharp(); } - static async testTempFolder(folder: string): Promise { await this.checkReadWritePermission(folder); } @@ -122,36 +149,43 @@ export class ConfigDiagnostics { static testImageFolder(folder: string): Promise { return new Promise((resolve, reject) => { if (!fs.existsSync(folder)) { - reject('Images folder not exists: \'' + folder + '\''); + reject("Images folder not exists: '" + folder + "'"); } fs.access(folder, fs.constants.R_OK, (err) => { if (err) { - reject({message: 'Error during getting read access to images folder', error: err.toString()}); + reject({ + message: 'Error during getting read access to images folder', + error: err.toString(), + }); } }); resolve(); }); } - static async testServerPhotoConfig(server: ServerPhotoConfig): Promise { - + return; } static async testClientPhotoConfig(client: ClientPhotoConfig): Promise { - + return; } - // @ts-ignore - public static async testServerThumbnailConfig(server: ServerThumbnailConfig): Promise { + public static async testServerThumbnailConfig( + server: ServerThumbnailConfig + ): Promise { if (server.personFaceMargin < 0 || server.personFaceMargin > 1) { throw new Error('personFaceMargin should be between 0 and 1'); } } - static async testClientThumbnailConfig(thumbnailConfig: ClientThumbnailConfig): Promise { + static async testClientThumbnailConfig( + thumbnailConfig: ClientThumbnailConfig + ): Promise { if (isNaN(thumbnailConfig.iconSize) || thumbnailConfig.iconSize <= 0) { - throw new Error('IconSize has to be >= 0 integer, got: ' + thumbnailConfig.iconSize); + throw new Error( + 'IconSize has to be >= 0 integer, got: ' + thumbnailConfig.iconSize + ); } if (!thumbnailConfig.thumbnailSizes.length) { @@ -164,12 +198,17 @@ export class ConfigDiagnostics { } } - - static async testTasksConfig(task: ServerJobConfig, config: IPrivateConfig): Promise { - + static async testTasksConfig( + task: ServerJobConfig, + config: IPrivateConfig + ): Promise { + return; } - static async testFacesConfig(faces: ClientFacesConfig, config: IPrivateConfig): Promise { + static async testFacesConfig( + faces: ClientFacesConfig, + config: IPrivateConfig + ): Promise { if (faces.enabled === true) { if (config.Server.Database.type === DatabaseType.memory) { throw new Error('Memory Database do not support faces'); @@ -180,43 +219,62 @@ export class ConfigDiagnostics { } } - static async testSearchConfig(search: ClientSearchConfig, config: IPrivateConfig): Promise { - if (search.enabled === true && - config.Server.Database.type === DatabaseType.memory) { + static async testSearchConfig( + search: ClientSearchConfig, + config: IPrivateConfig + ): Promise { + if ( + search.enabled === true && + config.Server.Database.type === DatabaseType.memory + ) { throw new Error('Memory Database do not support searching'); } } - - static async testSharingConfig(sharing: ClientSharingConfig, config: IPrivateConfig): Promise { - if (sharing.enabled === true && - config.Server.Database.type === DatabaseType.memory) { + static async testSharingConfig( + sharing: ClientSharingConfig, + config: IPrivateConfig + ): Promise { + if ( + sharing.enabled === true && + config.Server.Database.type === DatabaseType.memory + ) { throw new Error('Memory Database do not support sharing'); } - if (sharing.enabled === true && - config.Client.authenticationRequired === false) { + if ( + sharing.enabled === true && + config.Client.authenticationRequired === false + ) { throw new Error('In case of no authentication, sharing is not supported'); } } - static async testRandomPhotoConfig(sharing: ClientRandomPhotoConfig, config: IPrivateConfig): Promise { - if (sharing.enabled === true && - config.Server.Database.type === DatabaseType.memory) { + static async testRandomPhotoConfig( + sharing: ClientRandomPhotoConfig, + config: IPrivateConfig + ): Promise { + if ( + sharing.enabled === true && + config.Server.Database.type === DatabaseType.memory + ) { throw new Error('Memory Database do not support random photo'); } } - static async testMapConfig(map: ClientMapConfig): Promise { if (map.enabled === false) { return; } - if (map.mapProvider === MapProviders.Mapbox && - (!map.mapboxAccessToken || map.mapboxAccessToken.length === 0)) { + if ( + map.mapProvider === MapProviders.Mapbox && + (!map.mapboxAccessToken || map.mapboxAccessToken.length === 0) + ) { throw new Error('Mapbox needs a valid api key.'); } - if (map.mapProvider === MapProviders.Custom && - (!map.customLayers || map.customLayers.length === 0)) { + if ( + map.mapProvider === MapProviders.Custom && + (!map.customLayers || map.customLayers.length === 0) + ) { throw new Error('Custom maps need at least one valid layer'); } if (map.mapProvider === MapProviders.Custom) { @@ -228,23 +286,29 @@ export class ConfigDiagnostics { } } - static async testPreviewConfig(settings: ServerPreviewConfig): Promise { const sp = new SearchQueryParser(); - if (!Utils.equalsFilter(sp.parse(sp.stringify(settings.SearchQuery)), settings.SearchQuery)) { + if ( + !Utils.equalsFilter( + sp.parse(sp.stringify(settings.SearchQuery)), + settings.SearchQuery + ) + ) { throw new Error('SearchQuery is not valid'); } } static async runDiagnostics(): Promise { - if (Config.Server.Database.type !== DatabaseType.memory) { try { await ConfigDiagnostics.testDatabase(Config.Server.Database); } catch (ex) { const err: Error = ex; Logger.warn(LOG_TAG, '[SQL error]', err.toString()); - Logger.error(LOG_TAG, 'Error during initializing SQL DB, check DB connection and settings'); + Logger.error( + LOG_TAG, + 'Error during initializing SQL DB, check DB connection and settings' + ); process.exit(1); } } @@ -254,14 +318,20 @@ export class ConfigDiagnostics { } catch (ex) { const err: Error = ex; - Logger.warn(LOG_TAG, '[Thumbnail hardware acceleration] module error: ', err.toString()); - Logger.warn(LOG_TAG, 'Thumbnail hardware acceleration is not possible.' + - ' \'sharp\' node module is not found.' + - ' Falling back temporally to JS based thumbnail generation'); + Logger.warn( + LOG_TAG, + '[Thumbnail hardware acceleration] module error: ', + err.toString() + ); + Logger.warn( + LOG_TAG, + 'Thumbnail hardware acceleration is not possible.' + + " 'sharp' node module is not found." + + ' Falling back temporally to JS based thumbnail generation' + ); process.exit(1); } - try { await ConfigDiagnostics.testTempFolder(Config.Server.Media.tempFolder); } catch (ex) { @@ -270,23 +340,42 @@ export class ConfigDiagnostics { Logger.error(LOG_TAG, 'Thumbnail folder error', err.toString()); } - try { await ConfigDiagnostics.testClientVideoConfig(Config.Client.Media.Video); - await ConfigDiagnostics.testServerVideoConfig(Config.Server.Media.Video, Config); + await ConfigDiagnostics.testServerVideoConfig( + Config.Server.Media.Video, + Config + ); } catch (ex) { const err: Error = ex; - NotificationManager.warning('Video support error, switching off..', err.toString()); - Logger.warn(LOG_TAG, 'Video support error, switching off..', err.toString()); + NotificationManager.warning( + 'Video support error, switching off..', + err.toString() + ); + Logger.warn( + LOG_TAG, + 'Video support error, switching off..', + err.toString() + ); Config.Client.Media.Video.enabled = false; } try { - await ConfigDiagnostics.testMetaFileConfig(Config.Client.MetaFile, Config); + await ConfigDiagnostics.testMetaFileConfig( + Config.Client.MetaFile, + Config + ); } catch (ex) { const err: Error = ex; - NotificationManager.warning('Meta file support error, switching off gpx..', err.toString()); - Logger.warn(LOG_TAG, 'Meta file support error, switching off..', err.toString()); + NotificationManager.warning( + 'Meta file support error, switching off gpx..', + err.toString() + ); + Logger.warn( + LOG_TAG, + 'Meta file support error, switching off..', + err.toString() + ); Config.Client.MetaFile.gpx = false; } @@ -294,12 +383,18 @@ export class ConfigDiagnostics { await ConfigDiagnostics.testAlbumsConfig(Config.Client.Album, Config); } catch (ex) { const err: Error = ex; - NotificationManager.warning('Albums support error, switching off..', err.toString()); - Logger.warn(LOG_TAG, 'Meta file support error, switching off..', err.toString()); + NotificationManager.warning( + 'Albums support error, switching off..', + err.toString() + ); + Logger.warn( + LOG_TAG, + 'Meta file support error, switching off..', + err.toString() + ); Config.Client.Album.enabled = false; } - try { await ConfigDiagnostics.testImageFolder(Config.Server.Media.folder); } catch (ex) { @@ -308,52 +403,82 @@ export class ConfigDiagnostics { Logger.error(LOG_TAG, 'Images folder error', err.toString()); } try { - await ConfigDiagnostics.testClientThumbnailConfig(Config.Client.Media.Thumbnail); + await ConfigDiagnostics.testClientThumbnailConfig( + Config.Client.Media.Thumbnail + ); } catch (ex) { const err: Error = ex; NotificationManager.error('Thumbnail settings error', err.toString()); Logger.error(LOG_TAG, 'Thumbnail settings error', err.toString()); } - try { await ConfigDiagnostics.testSearchConfig(Config.Client.Search, Config); } catch (ex) { const err: Error = ex; - NotificationManager.warning('Search is not supported with these settings. Disabling temporally. ' + - 'Please adjust the config properly.', err.toString()); - Logger.warn(LOG_TAG, 'Search is not supported with these settings, switching off..', err.toString()); + NotificationManager.warning( + 'Search is not supported with these settings. Disabling temporally. ' + + 'Please adjust the config properly.', + err.toString() + ); + Logger.warn( + LOG_TAG, + 'Search is not supported with these settings, switching off..', + err.toString() + ); Config.Client.Search.enabled = false; } - try { await ConfigDiagnostics.testPreviewConfig(Config.Server.Preview); } catch (ex) { const err: Error = ex; - NotificationManager.warning('Preview settings are not valid, resetting search query', err.toString()); - Logger.warn(LOG_TAG, 'Preview settings are not valid, resetting search query', err.toString()); - Config.Server.Preview.SearchQuery = {type: SearchQueryTypes.any_text, text: ''} as TextSearch; + NotificationManager.warning( + 'Preview settings are not valid, resetting search query', + err.toString() + ); + Logger.warn( + LOG_TAG, + 'Preview settings are not valid, resetting search query', + err.toString() + ); + Config.Server.Preview.SearchQuery = { + type: SearchQueryTypes.any_text, + text: '', + } as TextSearch; } try { await ConfigDiagnostics.testFacesConfig(Config.Client.Faces, Config); } catch (ex) { const err: Error = ex; - NotificationManager.warning('Faces are not supported with these settings. Disabling temporally. ' + - 'Please adjust the config properly.', err.toString()); - Logger.warn(LOG_TAG, 'Faces are not supported with these settings, switching off..', err.toString()); + NotificationManager.warning( + 'Faces are not supported with these settings. Disabling temporally. ' + + 'Please adjust the config properly.', + err.toString() + ); + Logger.warn( + LOG_TAG, + 'Faces are not supported with these settings, switching off..', + err.toString() + ); Config.Client.Faces.enabled = false; } - try { await ConfigDiagnostics.testTasksConfig(Config.Server.Jobs, Config); } catch (ex) { const err: Error = ex; - NotificationManager.warning('Some Tasks are not supported with these settings. Disabling temporally. ' + - 'Please adjust the config properly.', err.toString()); - Logger.warn(LOG_TAG, 'Some Tasks not supported with these settings, switching off..', err.toString()); + NotificationManager.warning( + 'Some Tasks are not supported with these settings. Disabling temporally. ' + + 'Please adjust the config properly.', + err.toString() + ); + Logger.warn( + LOG_TAG, + 'Some Tasks not supported with these settings, switching off..', + err.toString() + ); Config.Client.Faces.enabled = false; } @@ -361,34 +486,55 @@ export class ConfigDiagnostics { await ConfigDiagnostics.testSharingConfig(Config.Client.Sharing, Config); } catch (ex) { const err: Error = ex; - NotificationManager.warning('Sharing is not supported with these settings. Disabling temporally. ' + - 'Please adjust the config properly.', err.toString()); - Logger.warn(LOG_TAG, 'Sharing is not supported with these settings, switching off..', err.toString()); + NotificationManager.warning( + 'Sharing is not supported with these settings. Disabling temporally. ' + + 'Please adjust the config properly.', + err.toString() + ); + Logger.warn( + LOG_TAG, + 'Sharing is not supported with these settings, switching off..', + err.toString() + ); Config.Client.Sharing.enabled = false; } try { - await ConfigDiagnostics.testRandomPhotoConfig(Config.Client.Sharing, Config); + await ConfigDiagnostics.testRandomPhotoConfig( + Config.Client.Sharing, + Config + ); } catch (ex) { const err: Error = ex; - NotificationManager.warning('Random Media is not supported with these settings. Disabling temporally. ' + - 'Please adjust the config properly.', err.toString()); - Logger.warn(LOG_TAG, 'Random Media is not supported with these settings, switching off..', err.toString()); + NotificationManager.warning( + 'Random Media is not supported with these settings. Disabling temporally. ' + + 'Please adjust the config properly.', + err.toString() + ); + Logger.warn( + LOG_TAG, + 'Random Media is not supported with these settings, switching off..', + err.toString() + ); Config.Client.Sharing.enabled = false; } - try { await ConfigDiagnostics.testMapConfig(Config.Client.Map); } catch (ex) { const err: Error = ex; - NotificationManager.warning('Maps is not supported with these settings. Using open street maps temporally. ' + - 'Please adjust the config properly.', err.toString()); - Logger.warn(LOG_TAG, 'Maps is not supported with these settings. Using open street maps temporally ' + - 'Please adjust the config properly.', err.toString()); + NotificationManager.warning( + 'Maps is not supported with these settings. Using open street maps temporally. ' + + 'Please adjust the config properly.', + err.toString() + ); + Logger.warn( + LOG_TAG, + 'Maps is not supported with these settings. Using open street maps temporally ' + + 'Please adjust the config properly.', + err.toString() + ); Config.Client.Map.mapProvider = MapProviders.OpenStreetMap; } - } - } diff --git a/src/backend/model/fileprocessing/PhotoProcessing.ts b/src/backend/model/fileprocessing/PhotoProcessing.ts index 7e4ffa2b..4656c53b 100644 --- a/src/backend/model/fileprocessing/PhotoProcessing.ts +++ b/src/backend/model/fileprocessing/PhotoProcessing.ts @@ -1,18 +1,20 @@ import * as path from 'path'; -import {constants as fsConstants, promises as fsp} from 'fs'; +import { constants as fsConstants, promises as fsp } from 'fs'; import * as os from 'os'; import * as crypto from 'crypto'; -import {ProjectPath} from '../../ProjectPath'; -import {Config} from '../../../common/config/private/Config'; -import {PhotoWorker, RendererInput, ThumbnailSourceType} from '../threading/PhotoWorker'; -import {ITaskExecuter, TaskExecuter} from '../threading/TaskExecuter'; -import {FaceRegion, PhotoDTO} from '../../../common/entities/PhotoDTO'; -import {SupportedFormats} from '../../../common/SupportedFormats'; -import {PersonWithSampleRegion} from '../../../common/entities/PersonDTO'; - +import { ProjectPath } from '../../ProjectPath'; +import { Config } from '../../../common/config/private/Config'; +import { + PhotoWorker, + RendererInput, + ThumbnailSourceType, +} from '../threading/PhotoWorker'; +import { ITaskExecuter, TaskExecuter } from '../threading/TaskExecuter'; +import { FaceRegion, PhotoDTO } from '../../../common/entities/PhotoDTO'; +import { SupportedFormats } from '../../../common/SupportedFormats'; +import { PersonWithSampleRegion } from '../../../common/entities/PersonDTO'; export class PhotoProcessing { - private static initDone = false; private static taskQue: ITaskExecuter = null; @@ -21,49 +23,64 @@ export class PhotoProcessing { return; } - if (Config.Server.Threading.enabled === true) { if (Config.Server.Threading.thumbnailThreads > 0) { - Config.Client.Media.Thumbnail.concurrentThumbnailGenerations = Config.Server.Threading.thumbnailThreads; + Config.Client.Media.Thumbnail.concurrentThumbnailGenerations = + Config.Server.Threading.thumbnailThreads; } else { - Config.Client.Media.Thumbnail.concurrentThumbnailGenerations = Math.max(1, os.cpus().length - 1); + Config.Client.Media.Thumbnail.concurrentThumbnailGenerations = Math.max( + 1, + os.cpus().length - 1 + ); } } else { Config.Client.Media.Thumbnail.concurrentThumbnailGenerations = 1; } - - this.taskQue = new TaskExecuter(Config.Client.Media.Thumbnail.concurrentThumbnailGenerations, - ((input): Promise => PhotoWorker.render(input))); + this.taskQue = new TaskExecuter( + Config.Client.Media.Thumbnail.concurrentThumbnailGenerations, + (input): Promise => PhotoWorker.render(input) + ); this.initDone = true; } - - public static async generatePersonThumbnail(person: PersonWithSampleRegion): Promise { - + public static async generatePersonThumbnail( + person: PersonWithSampleRegion + ): Promise { // load parameters const photo: PhotoDTO = person.sampleRegion.media; - const mediaPath = path.join(ProjectPath.ImageFolder, photo.directory.path, photo.directory.name, photo.name); + const mediaPath = path.join( + ProjectPath.ImageFolder, + photo.directory.path, + photo.directory.name, + photo.name + ); const size: number = Config.Client.Media.Thumbnail.personThumbnailSize; // generate thumbnail path - const thPath = PhotoProcessing.generatePersonThumbnailPath(mediaPath, person.sampleRegion, size); - + const thPath = PhotoProcessing.generatePersonThumbnailPath( + mediaPath, + person.sampleRegion, + size + ); // check if thumbnail already exist try { await fsp.access(thPath, fsConstants.R_OK); return thPath; - } catch (e) { - } - + } catch (e) {} const margin = { - x: Math.round(person.sampleRegion.box.width * (Config.Server.Media.Thumbnail.personFaceMargin)), - y: Math.round(person.sampleRegion.box.height * (Config.Server.Media.Thumbnail.personFaceMargin)) + x: Math.round( + person.sampleRegion.box.width * + Config.Server.Media.Thumbnail.personFaceMargin + ), + y: Math.round( + person.sampleRegion.box.height * + Config.Server.Media.Thumbnail.personFaceMargin + ), }; - // run on other thread const input = { type: ThumbnailSourceType.Photo, @@ -72,49 +89,88 @@ export class PhotoProcessing { outPath: thPath, makeSquare: false, cut: { - left: Math.round(Math.max(0, person.sampleRegion.box.left - margin.x / 2)), - top: Math.round(Math.max(0, person.sampleRegion.box.top - margin.y / 2)), + left: Math.round( + Math.max(0, person.sampleRegion.box.left - margin.x / 2) + ), + top: Math.round( + Math.max(0, person.sampleRegion.box.top - margin.y / 2) + ), width: person.sampleRegion.box.width + margin.x, - height: person.sampleRegion.box.height + margin.y + height: person.sampleRegion.box.height + margin.y, }, - qualityPriority: Config.Server.Media.Thumbnail.qualityPriority + qualityPriority: Config.Server.Media.Thumbnail.qualityPriority, } as RendererInput; - input.cut.width = Math.min(input.cut.width, photo.metadata.size.width - input.cut.left); - input.cut.height = Math.min(input.cut.height, photo.metadata.size.height - input.cut.top); + input.cut.width = Math.min( + input.cut.width, + photo.metadata.size.width - input.cut.left + ); + input.cut.height = Math.min( + input.cut.height, + photo.metadata.size.height - input.cut.top + ); - await fsp.mkdir(ProjectPath.FacesFolder, {recursive: true}); + await fsp.mkdir(ProjectPath.FacesFolder, { recursive: true }); await PhotoProcessing.taskQue.execute(input); return thPath; } - public static generateConvertedPath(mediaPath: string, size: number): string { const file = path.basename(mediaPath); - return path.join(ProjectPath.TranscodedFolder, + return path.join( + ProjectPath.TranscodedFolder, ProjectPath.getRelativePathToImages(path.dirname(mediaPath)), - file + '_' + size + '.jpg'); + file + '_' + size + '.jpg' + ); } - public static generatePersonThumbnailPath(mediaPath: string, faceRegion: FaceRegion, size: number): string { - return path.join(ProjectPath.FacesFolder, - crypto.createHash('md5').update(mediaPath + '_' + faceRegion.name + '_' + faceRegion.box.left + '_' + faceRegion.box.top) - .digest('hex') + '_' + size + '.jpg'); + public static generatePersonThumbnailPath( + mediaPath: string, + faceRegion: FaceRegion, + size: number + ): string { + return path.join( + ProjectPath.FacesFolder, + crypto + .createHash('md5') + .update( + mediaPath + + '_' + + faceRegion.name + + '_' + + faceRegion.box.left + + '_' + + faceRegion.box.top + ) + .digest('hex') + + '_' + + size + + '.jpg' + ); } + public static async isValidConvertedPath( + convertedPath: string + ): Promise { + const origFilePath = path.join( + ProjectPath.ImageFolder, + path.relative( + ProjectPath.TranscodedFolder, + convertedPath.substring(0, convertedPath.lastIndexOf('_')) + ) + ); - public static async isValidConvertedPath(convertedPath: string): Promise { - const origFilePath = path.join(ProjectPath.ImageFolder, - path.relative(ProjectPath.TranscodedFolder, - convertedPath.substring(0, convertedPath.lastIndexOf('_')))); - - const sizeStr = convertedPath.substring(convertedPath.lastIndexOf('_') + 1, - convertedPath.length - path.extname(convertedPath).length); + const sizeStr = convertedPath.substring( + convertedPath.lastIndexOf('_') + 1, + convertedPath.length - path.extname(convertedPath).length + ); const size = parseInt(sizeStr, 10); - if ((size + '').length !== sizeStr.length || + if ( + (size + '').length !== sizeStr.length || (Config.Client.Media.Thumbnail.thumbnailSizes.indexOf(size) === -1 && - Config.Server.Media.Photo.Converting.resolution !== size)) { + Config.Server.Media.Photo.Converting.resolution !== size) + ) { return false; } @@ -124,49 +180,47 @@ export class PhotoProcessing { return false; } - return true; } - public static async convertPhoto(mediaPath: string): Promise { - return this.generateThumbnail(mediaPath, + return this.generateThumbnail( + mediaPath, Config.Server.Media.Photo.Converting.resolution, ThumbnailSourceType.Photo, - false); + false + ); } - - static async convertedPhotoExist(mediaPath: string, size: number): Promise { - + static async convertedPhotoExist( + mediaPath: string, + size: number + ): Promise { // generate thumbnail path const outPath = PhotoProcessing.generateConvertedPath(mediaPath, size); - // check if file already exist try { await fsp.access(outPath, fsConstants.R_OK); return true; - } catch (e) { - } + } catch (e) {} return false; } - public static async generateThumbnail(mediaPath: string, - size: number, - sourceType: ThumbnailSourceType, - makeSquare: boolean): Promise { + public static async generateThumbnail( + mediaPath: string, + size: number, + sourceType: ThumbnailSourceType, + makeSquare: boolean + ): Promise { // generate thumbnail path const outPath = PhotoProcessing.generateConvertedPath(mediaPath, size); - // check if file already exist try { await fsp.access(outPath, fsConstants.R_OK); return outPath; - } catch (e) { - } - + } catch (e) {} // run on other thread const input = { @@ -175,12 +229,12 @@ export class PhotoProcessing { size, outPath, makeSquare, - qualityPriority: Config.Server.Media.Thumbnail.qualityPriority + qualityPriority: Config.Server.Media.Thumbnail.qualityPriority, } as RendererInput; const outDir = path.dirname(input.outPath); - await fsp.mkdir(outDir, {recursive: true}); + await fsp.mkdir(outDir, { recursive: true }); await this.taskQue.execute(input); return outPath; } @@ -189,6 +243,5 @@ export class PhotoProcessing { const extension = path.extname(fullPath).toLowerCase(); return SupportedFormats.WithDots.Photos.indexOf(extension) !== -1; } - } diff --git a/src/backend/model/fileprocessing/VideoProcessing.ts b/src/backend/model/fileprocessing/VideoProcessing.ts index 7decd0b5..d7aca7dd 100644 --- a/src/backend/model/fileprocessing/VideoProcessing.ts +++ b/src/backend/model/fileprocessing/VideoProcessing.ts @@ -1,30 +1,45 @@ import * as path from 'path'; -import {constants as fsConstants, promises as fsp} from 'fs'; -import {ITaskExecuter, TaskExecuter} from '../threading/TaskExecuter'; -import {VideoConverterInput, VideoConverterWorker} from '../threading/VideoConverterWorker'; -import {MetadataLoader} from '../threading/MetadataLoader'; -import {Config} from '../../../common/config/private/Config'; -import {ProjectPath} from '../../ProjectPath'; -import {SupportedFormats} from '../../../common/SupportedFormats'; - +import { constants as fsConstants, promises as fsp } from 'fs'; +import { ITaskExecuter, TaskExecuter } from '../threading/TaskExecuter'; +import { + VideoConverterInput, + VideoConverterWorker, +} from '../threading/VideoConverterWorker'; +import { MetadataLoader } from '../threading/MetadataLoader'; +import { Config } from '../../../common/config/private/Config'; +import { ProjectPath } from '../../ProjectPath'; +import { SupportedFormats } from '../../../common/SupportedFormats'; export class VideoProcessing { private static taskQue: ITaskExecuter = - new TaskExecuter(1, ((input): Promise => VideoConverterWorker.convert(input))); + new TaskExecuter( + 1, + (input): Promise => VideoConverterWorker.convert(input) + ); public static generateConvertedFilePath(videoPath: string): string { - return path.join(ProjectPath.TranscodedFolder, + return path.join( + ProjectPath.TranscodedFolder, ProjectPath.getRelativePathToImages(path.dirname(videoPath)), - path.basename(videoPath) + '_' + this.getConvertedFilePostFix()); + path.basename(videoPath) + '_' + this.getConvertedFilePostFix() + ); } - public static async isValidConvertedPath(convertedPath: string): Promise { + public static async isValidConvertedPath( + convertedPath: string + ): Promise { + const origFilePath = path.join( + ProjectPath.ImageFolder, + path.relative( + ProjectPath.TranscodedFolder, + convertedPath.substring(0, convertedPath.lastIndexOf('_')) + ) + ); - const origFilePath = path.join(ProjectPath.ImageFolder, - path.relative(ProjectPath.TranscodedFolder, - convertedPath.substring(0, convertedPath.lastIndexOf('_')))); - - const postfix = convertedPath.substring(convertedPath.lastIndexOf('_') + 1, convertedPath.length); + const postfix = convertedPath.substring( + convertedPath.lastIndexOf('_') + 1, + convertedPath.length + ); if (postfix !== this.getConvertedFilePostFix()) { return false; @@ -36,33 +51,27 @@ export class VideoProcessing { return false; } - return true; } - static async convertedVideoExist(videoPath: string): Promise { const outPath = this.generateConvertedFilePath(videoPath); try { await fsp.access(outPath, fsConstants.R_OK); return true; - } catch (e) { - } + } catch (e) {} return false; } public static async convertVideo(videoPath: string): Promise { - - const outPath = this.generateConvertedFilePath(videoPath); try { await fsp.access(outPath, fsConstants.R_OK); return; - } catch (e) { - } + } catch (e) {} const metaData = await MetadataLoader.loadVideoMetadata(videoPath); @@ -75,25 +84,28 @@ export class VideoProcessing { crf: Config.Server.Media.Video.transcoding.crf, preset: Config.Server.Media.Video.transcoding.preset, customOptions: Config.Server.Media.Video.transcoding.customOptions, - } + }, }; if (metaData.bitRate > Config.Server.Media.Video.transcoding.bitRate) { - renderInput.output.bitRate = Config.Server.Media.Video.transcoding.bitRate; + renderInput.output.bitRate = + Config.Server.Media.Video.transcoding.bitRate; } if (metaData.fps > Config.Server.Media.Video.transcoding.fps) { renderInput.output.fps = Config.Server.Media.Video.transcoding.fps; } - if (Config.Server.Media.Video.transcoding.resolution < metaData.size.height) { - renderInput.output.resolution = Config.Server.Media.Video.transcoding.resolution; + if ( + Config.Server.Media.Video.transcoding.resolution < metaData.size.height + ) { + renderInput.output.resolution = + Config.Server.Media.Video.transcoding.resolution; } const outDir = path.dirname(renderInput.output.path); - await fsp.mkdir(outDir, {recursive: true}); + await fsp.mkdir(outDir, { recursive: true }); await VideoProcessing.taskQue.execute(renderInput); - } public static isVideo(fullPath: string): boolean { @@ -102,11 +114,14 @@ export class VideoProcessing { } protected static getConvertedFilePostFix(): string { - return Math.round(Config.Server.Media.Video.transcoding.bitRate / 1024) + 'k' + + return ( + Math.round(Config.Server.Media.Video.transcoding.bitRate / 1024) + + 'k' + Config.Server.Media.Video.transcoding.codec.toString().toLowerCase() + Config.Server.Media.Video.transcoding.resolution + - '.' + Config.Server.Media.Video.transcoding.format.toLowerCase(); + '.' + + Config.Server.Media.Video.transcoding.format.toLowerCase() + ); } - } diff --git a/src/backend/model/jobs/JobManager.ts b/src/backend/model/jobs/JobManager.ts index 232295d8..2ca98e37 100644 --- a/src/backend/model/jobs/JobManager.ts +++ b/src/backend/model/jobs/JobManager.ts @@ -1,20 +1,27 @@ -import {IJobManager} from '../database/interfaces/IJobManager'; -import {JobProgressDTO, JobProgressStates} from '../../../common/entities/job/JobProgressDTO'; -import {IJob} from './jobs/IJob'; -import {JobRepository} from './JobRepository'; -import {Config} from '../../../common/config/private/Config'; -import {AfterJobTrigger, JobScheduleDTO, JobScheduleDTOUtils, JobTriggerType} from '../../../common/entities/job/JobScheduleDTO'; -import {Logger} from '../../Logger'; -import {NotificationManager} from '../NotifocationManager'; -import {IJobListener} from './jobs/IJobListener'; -import {JobProgress} from './jobs/JobProgress'; -import {JobProgressManager} from './JobProgressManager'; - +import { IJobManager } from '../database/interfaces/IJobManager'; +import { + JobProgressDTO, + JobProgressStates, +} from '../../../common/entities/job/JobProgressDTO'; +import { IJob } from './jobs/IJob'; +import { JobRepository } from './JobRepository'; +import { Config } from '../../../common/config/private/Config'; +import { + AfterJobTrigger, + JobScheduleDTO, + JobScheduleDTOUtils, + JobTriggerType, +} from '../../../common/entities/job/JobScheduleDTO'; +import { Logger } from '../../Logger'; +import { NotificationManager } from '../NotifocationManager'; +import { IJobListener } from './jobs/IJobListener'; +import { JobProgress } from './jobs/JobProgress'; +import { JobProgressManager } from './JobProgressManager'; const LOG_TAG = '[JobManager]'; export class JobManager implements IJobManager, IJobListener { - protected timers: { schedule: JobScheduleDTO, timer: NodeJS.Timeout }[] = []; + protected timers: { schedule: JobScheduleDTO; timer: NodeJS.Timeout }[] = []; protected progressManager: JobProgressManager = null; constructor() { @@ -23,21 +30,36 @@ export class JobManager implements IJobManager, IJobListener { } protected get JobRunning(): boolean { - return JobRepository.Instance.getAvailableJobs().findIndex((j): boolean => j.InProgress === true) !== -1; + return ( + JobRepository.Instance.getAvailableJobs().findIndex( + (j): boolean => j.InProgress === true + ) !== -1 + ); } protected get JobNoParallelRunning(): boolean { - return JobRepository.Instance.getAvailableJobs() - .findIndex((j): boolean => j.InProgress === true && j.allowParallelRun) !== -1; + return ( + JobRepository.Instance.getAvailableJobs().findIndex( + (j): boolean => j.InProgress === true && j.allowParallelRun + ) !== -1 + ); } getProgresses(): { [id: string]: JobProgressDTO } { return this.progressManager.Progresses; } - async run(jobName: string, config: T, soloRun: boolean, allowParallelRun: boolean): Promise { - if ((allowParallelRun === false && this.JobRunning === true) || this.JobNoParallelRunning === true) { - throw new Error('Can\'t start this job while an other is running'); + async run( + jobName: string, + config: T, + soloRun: boolean, + allowParallelRun: boolean + ): Promise { + if ( + (allowParallelRun === false && this.JobRunning === true) || + this.JobNoParallelRunning === true + ) { + throw new Error("Can't start this job while an other is running"); } const t = this.findJob(jobName); @@ -62,20 +84,37 @@ export class JobManager implements IJobManager, IJobListener { this.progressManager.onJobProgressUpdate(progress.toDTO()); }; - onJobFinished = async (job: IJob, state: JobProgressStates, soloRun: boolean): Promise => { + onJobFinished = async ( + job: IJob, + state: JobProgressStates, + soloRun: boolean + ): Promise => { // if it was not finished peacefully or was a soloRun, do not start the next one if (state !== JobProgressStates.finished || soloRun === true) { return; } - const sch = Config.Server.Jobs.scheduled.find((s): boolean => s.jobName === job.Name); + const sch = Config.Server.Jobs.scheduled.find( + (s): boolean => s.jobName === job.Name + ); if (sch) { - const children = Config.Server.Jobs.scheduled.filter((s): boolean => s.trigger.type === JobTriggerType.after && - (s.trigger as AfterJobTrigger).afterScheduleName === sch.name); + const children = Config.Server.Jobs.scheduled.filter( + (s): boolean => + s.trigger.type === JobTriggerType.after && + (s.trigger as AfterJobTrigger).afterScheduleName === sch.name + ); for (const item of children) { try { - await this.run(item.jobName, item.config, false, item.allowParallelRun); + await this.run( + item.jobName, + item.config, + false, + item.allowParallelRun + ); } catch (e) { - NotificationManager.warning('Job running error:' + item.name, e.toString()); + NotificationManager.warning( + 'Job running error:' + item.name, + e.toString() + ); } } } @@ -107,21 +146,32 @@ export class JobManager implements IJobManager, IJobListener { * Schedules a single job to run */ private runSchedule(schedule: JobScheduleDTO): void { - const nextDate = JobScheduleDTOUtils.getNextRunningDate(new Date(), schedule); + const nextDate = JobScheduleDTOUtils.getNextRunningDate( + new Date(), + schedule + ); if (nextDate && nextDate.getTime() > Date.now()) { - Logger.debug(LOG_TAG, 'running schedule: ' + schedule.jobName + - ' at ' + nextDate.toLocaleString(undefined, {hour12: false})); + Logger.debug( + LOG_TAG, + 'running schedule: ' + + schedule.jobName + + ' at ' + + nextDate.toLocaleString(undefined, { hour12: false }) + ); const timer: NodeJS.Timeout = setTimeout(async (): Promise => { this.timers = this.timers.filter((t): boolean => t.timer !== timer); - await this.run(schedule.jobName, schedule.config, false, schedule.allowParallelRun); + await this.run( + schedule.jobName, + schedule.config, + false, + schedule.allowParallelRun + ); this.runSchedule(schedule); }, nextDate.getTime() - Date.now()); - this.timers.push({schedule, timer}); - + this.timers.push({ schedule, timer }); } else { Logger.debug(LOG_TAG, 'skipping schedule:' + schedule.jobName); } } - } diff --git a/src/backend/model/jobs/JobProgressManager.ts b/src/backend/model/jobs/JobProgressManager.ts index 2671be4d..4c2ac514 100644 --- a/src/backend/model/jobs/JobProgressManager.ts +++ b/src/backend/model/jobs/JobProgressManager.ts @@ -1,17 +1,22 @@ -import {promises as fsp} from 'fs'; +import { promises as fsp } from 'fs'; import * as path from 'path'; -import {ProjectPath} from '../../ProjectPath'; -import {Config} from '../../../common/config/private/Config'; -import {JobProgressDTO, JobProgressStates} from '../../../common/entities/job/JobProgressDTO'; +import { ProjectPath } from '../../ProjectPath'; +import { Config } from '../../../common/config/private/Config'; +import { + JobProgressDTO, + JobProgressStates, +} from '../../../common/entities/job/JobProgressDTO'; export class JobProgressManager { private static readonly VERSION = 3; private db: { - version: number, - progresses: { [key: string]: { progress: JobProgressDTO, timestamp: number } } + version: number; + progresses: { + [key: string]: { progress: JobProgressDTO; timestamp: number }; + }; } = { version: JobProgressManager.VERSION, - progresses: {} + progresses: {}, }; private readonly dbPath: string; private timer: NodeJS.Timeout = null; @@ -25,16 +30,17 @@ export class JobProgressManager { const m: { [key: string]: JobProgressDTO } = {}; for (const key of Object.keys(this.db.progresses)) { m[key] = this.db.progresses[key].progress; - if (this.db.progresses[key].progress.state === JobProgressStates.running) { + if ( + this.db.progresses[key].progress.state === JobProgressStates.running + ) { m[key].time.end = Date.now(); } } return m; } - onJobProgressUpdate(progress: JobProgressDTO): void { - this.db.progresses[progress.HashName] = {progress, timestamp: Date.now()}; + this.db.progresses[progress.HashName] = { progress, timestamp: Date.now() }; this.delayedSave(); } @@ -51,10 +57,16 @@ export class JobProgressManager { } this.db = db; - while (Object.keys(this.db.progresses).length > Config.Server.Jobs.maxSavedProgress) { + while ( + Object.keys(this.db.progresses).length > + Config.Server.Jobs.maxSavedProgress + ) { let min: string = null; for (const key of Object.keys(this.db.progresses)) { - if (min === null || this.db.progresses[min].timestamp > this.db.progresses[key].timestamp) { + if ( + min === null || + this.db.progresses[min].timestamp > this.db.progresses[key].timestamp + ) { min = key; } } @@ -62,8 +74,10 @@ export class JobProgressManager { } for (const key of Object.keys(this.db.progresses)) { - if (this.db.progresses[key].progress.state === JobProgressStates.running || - this.db.progresses[key].progress.state === JobProgressStates.cancelling) { + if ( + this.db.progresses[key].progress.state === JobProgressStates.running || + this.db.progresses[key].progress.state === JobProgressStates.cancelling + ) { this.db.progresses[key].progress.state = JobProgressStates.interrupted; } } @@ -82,5 +96,4 @@ export class JobProgressManager { this.timer = null; }, 5000); } - } diff --git a/src/backend/model/jobs/JobRepository.ts b/src/backend/model/jobs/JobRepository.ts index f5f1e5f4..40c4298d 100644 --- a/src/backend/model/jobs/JobRepository.ts +++ b/src/backend/model/jobs/JobRepository.ts @@ -1,15 +1,14 @@ -import {IJob} from './jobs/IJob'; -import {IndexingJob} from './jobs/IndexingJob'; -import {DBRestJob} from './jobs/DBResetJob'; -import {VideoConvertingJob} from './jobs/VideoConvertingJob'; -import {PhotoConvertingJob} from './jobs/PhotoConvertingJob'; -import {ThumbnailGenerationJob} from './jobs/ThumbnailGenerationJob'; -import {TempFolderCleaningJob} from './jobs/TempFolderCleaningJob'; -import {PreviewFillingJob} from './jobs/PreviewFillingJob'; -import {PreviewRestJob} from './jobs/PreviewResetJob'; +import { IJob } from './jobs/IJob'; +import { IndexingJob } from './jobs/IndexingJob'; +import { DBRestJob } from './jobs/DBResetJob'; +import { VideoConvertingJob } from './jobs/VideoConvertingJob'; +import { PhotoConvertingJob } from './jobs/PhotoConvertingJob'; +import { ThumbnailGenerationJob } from './jobs/ThumbnailGenerationJob'; +import { TempFolderCleaningJob } from './jobs/TempFolderCleaningJob'; +import { PreviewFillingJob } from './jobs/PreviewFillingJob'; +import { PreviewRestJob } from './jobs/PreviewResetJob'; export class JobRepository { - private static instance: JobRepository = null; availableJobs: { [key: string]: IJob } = {}; @@ -21,7 +20,7 @@ export class JobRepository { } getAvailableJobs(): IJob[] { - return Object.values(this.availableJobs).filter(t => t.Supported); + return Object.values(this.availableJobs).filter((t) => t.Supported); } register(job: IJob): void { @@ -32,7 +31,6 @@ export class JobRepository { } } - JobRepository.Instance.register(new IndexingJob()); JobRepository.Instance.register(new DBRestJob()); JobRepository.Instance.register(new PreviewFillingJob()); diff --git a/src/backend/model/jobs/jobs/DBResetJob.ts b/src/backend/model/jobs/jobs/DBResetJob.ts index d97e7ea1..39a31b01 100644 --- a/src/backend/model/jobs/jobs/DBResetJob.ts +++ b/src/backend/model/jobs/jobs/DBResetJob.ts @@ -1,9 +1,11 @@ -import {ObjectManagers} from '../../ObjectManagers'; -import {Config} from '../../../../common/config/private/Config'; -import {ConfigTemplateEntry, DefaultsJobs} from '../../../../common/entities/job/JobDTO'; -import {Job} from './Job'; -import {DatabaseType} from '../../../../common/config/private/PrivateConfig'; - +import { ObjectManagers } from '../../ObjectManagers'; +import { Config } from '../../../../common/config/private/Config'; +import { + ConfigTemplateEntry, + DefaultsJobs, +} from '../../../../common/entities/job/JobDTO'; +import { Job } from './Job'; +import { DatabaseType } from '../../../../common/config/private/PrivateConfig'; export class DBRestJob extends Job { public readonly Name = DefaultsJobs[DefaultsJobs['Database Reset']]; @@ -14,8 +16,7 @@ export class DBRestJob extends Job { return Config.Server.Database.type !== DatabaseType.memory; } - protected async init(): Promise { - } + protected async init(): Promise {} protected async step(): Promise { this.Progress.Left = 1; @@ -23,6 +24,4 @@ export class DBRestJob extends Job { await ObjectManagers.getInstance().IndexingManager.resetDB(); return false; } - - } diff --git a/src/backend/model/jobs/jobs/FileJob.ts b/src/backend/model/jobs/jobs/FileJob.ts index 87772cb3..e4071e7c 100644 --- a/src/backend/model/jobs/jobs/FileJob.ts +++ b/src/backend/model/jobs/jobs/FileJob.ts @@ -1,31 +1,31 @@ -import {ConfigTemplateEntry} from '../../../../common/entities/job/JobDTO'; -import {Job} from './Job'; +import { ConfigTemplateEntry } from '../../../../common/entities/job/JobDTO'; +import { Job } from './Job'; import * as path from 'path'; -import {DiskManager} from '../../DiskManger'; -import {DirectoryScanSettings} from '../../threading/DiskMangerWorker'; -import {Logger} from '../../../Logger'; -import {Config} from '../../../../common/config/private/Config'; -import {FileDTO} from '../../../../common/entities/FileDTO'; -import {SQLConnection} from '../../database/sql/SQLConnection'; -import {MediaEntity} from '../../database/sql/enitites/MediaEntity'; -import {PhotoEntity} from '../../database/sql/enitites/PhotoEntity'; -import {VideoEntity} from '../../database/sql/enitites/VideoEntity'; -import {backendTexts} from '../../../../common/BackendTexts'; -import {ProjectPath} from '../../../ProjectPath'; -import {DatabaseType} from '../../../../common/config/private/PrivateConfig'; - +import { DiskManager } from '../../DiskManger'; +import { DirectoryScanSettings } from '../../threading/DiskMangerWorker'; +import { Logger } from '../../../Logger'; +import { Config } from '../../../../common/config/private/Config'; +import { FileDTO } from '../../../../common/entities/FileDTO'; +import { SQLConnection } from '../../database/sql/SQLConnection'; +import { MediaEntity } from '../../database/sql/enitites/MediaEntity'; +import { PhotoEntity } from '../../database/sql/enitites/PhotoEntity'; +import { VideoEntity } from '../../database/sql/enitites/VideoEntity'; +import { backendTexts } from '../../../../common/BackendTexts'; +import { ProjectPath } from '../../../ProjectPath'; +import { DatabaseType } from '../../../../common/config/private/PrivateConfig'; const LOG_TAG = '[FileJob]'; /** * Abstract class for thumbnail creation, file deleting etc. */ -export abstract class FileJob extends Job { +export abstract class FileJob< + S extends { indexedOnly: boolean } = { indexedOnly: boolean } +> extends Job { public readonly ConfigTemplate: ConfigTemplateEntry[] = []; directoryQueue: string[] = []; fileQueue: string[] = []; - protected constructor(private scanFilter: DirectoryScanSettings) { super(); this.scanFilter.noChildDirPhotos = true; @@ -35,7 +35,7 @@ export abstract class FileJob { return files; } @@ -65,9 +64,10 @@ export abstract class FileJob 0) { - - if (this.config.indexedOnly === true && - Config.Server.Database.type !== DatabaseType.memory) { + if ( + this.config.indexedOnly === true && + Config.Server.Database.type !== DatabaseType.memory + ) { await this.loadAllMediaFilesFromDB(); this.directoryQueue = []; } else { @@ -87,8 +87,13 @@ export abstract class FileJob { const directory = this.directoryQueue.shift(); this.Progress.log('scanning directory: ' + directory); - const scanned = await DiskManager.scanDirectoryNoMetadata(directory, this.scanFilter); + const scanned = await DiskManager.scanDirectoryNoMetadata( + directory, + this.scanFilter + ); for (const item of scanned.directories) { this.directoryQueue.push(path.join(item.path, item.name)); } if (this.scanFilter.noPhoto !== true || this.scanFilter.noVideo !== true) { const scannedAndFiltered = await this.filterMediaFiles(scanned.media); for (const item of scannedAndFiltered) { - this.fileQueue.push(path.join(ProjectPath.ImageFolder, item.directory.path, item.directory.name, item.name)); + this.fileQueue.push( + path.join( + ProjectPath.ImageFolder, + item.directory.path, + item.directory.name, + item.name + ) + ); } } if (this.scanFilter.noMetaFile !== true) { const scannedAndFiltered = await this.filterMetaFiles(scanned.metaFile); for (const item of scannedAndFiltered) { - this.fileQueue.push(path.join(ProjectPath.ImageFolder, item.directory.path, item.directory.name, item.name)); + this.fileQueue.push( + path.join( + ProjectPath.ImageFolder, + item.directory.path, + item.directory.name, + item.name + ) + ); } } } private async loadAllMediaFilesFromDB(): Promise { - if (this.scanFilter.noVideo === true && this.scanFilter.noPhoto === true) { return; } @@ -141,7 +162,14 @@ export abstract class FileJob extends JobDTO { Name: string; diff --git a/src/backend/model/jobs/jobs/IJobListener.ts b/src/backend/model/jobs/jobs/IJobListener.ts index 9dc978e8..997f2739 100644 --- a/src/backend/model/jobs/jobs/IJobListener.ts +++ b/src/backend/model/jobs/jobs/IJobListener.ts @@ -1,9 +1,13 @@ -import {JobProgress} from './JobProgress'; -import {IJob} from './IJob'; -import {JobProgressStates} from '../../../../common/entities/job/JobProgressDTO'; +import { JobProgress } from './JobProgress'; +import { IJob } from './IJob'; +import { JobProgressStates } from '../../../../common/entities/job/JobProgressDTO'; export interface IJobListener { - onJobFinished(job: IJob, state: JobProgressStates, soloRun: boolean): void; + onJobFinished( + job: IJob, + state: JobProgressStates, + soloRun: boolean + ): void; onProgressUpdate(progress: JobProgress): void; } diff --git a/src/backend/model/jobs/jobs/IndexingJob.ts b/src/backend/model/jobs/jobs/IndexingJob.ts index ab17ae60..c0d0f3d6 100644 --- a/src/backend/model/jobs/jobs/IndexingJob.ts +++ b/src/backend/model/jobs/jobs/IndexingJob.ts @@ -1,36 +1,41 @@ -import {ObjectManagers} from '../../ObjectManagers'; +import { ObjectManagers } from '../../ObjectManagers'; import * as path from 'path'; import * as fs from 'fs'; -import {Config} from '../../../../common/config/private/Config'; -import {Job} from './Job'; -import {ConfigTemplateEntry, DefaultsJobs} from '../../../../common/entities/job/JobDTO'; -import {JobProgressStates} from '../../../../common/entities/job/JobProgressDTO'; -import {DatabaseType} from '../../../../common/config/private/PrivateConfig'; -import {DiskMangerWorker} from '../../threading/DiskMangerWorker'; -import {ProjectPath} from '../../../ProjectPath'; -import {backendTexts} from '../../../../common/BackendTexts'; -import {ParentDirectoryDTO} from '../../../../common/entities/DirectoryDTO'; -import {ISQLGalleryManager} from '../../database/sql/IGalleryManager'; -import {Logger} from '../../../Logger'; -import {FileDTO} from '../../../../common/entities/FileDTO'; +import { Config } from '../../../../common/config/private/Config'; +import { Job } from './Job'; +import { + ConfigTemplateEntry, + DefaultsJobs, +} from '../../../../common/entities/job/JobDTO'; +import { JobProgressStates } from '../../../../common/entities/job/JobProgressDTO'; +import { DatabaseType } from '../../../../common/config/private/PrivateConfig'; +import { DiskMangerWorker } from '../../threading/DiskMangerWorker'; +import { ProjectPath } from '../../../ProjectPath'; +import { backendTexts } from '../../../../common/BackendTexts'; +import { ParentDirectoryDTO } from '../../../../common/entities/DirectoryDTO'; +import { ISQLGalleryManager } from '../../database/sql/IGalleryManager'; +import { Logger } from '../../../Logger'; +import { FileDTO } from '../../../../common/entities/FileDTO'; - -export class IndexingJob extends Job { +export class IndexingJob< + S extends { indexChangesOnly: boolean } = { indexChangesOnly: boolean } +> extends Job { public readonly Name = DefaultsJobs[DefaultsJobs.Indexing]; directoriesToIndex: string[] = []; - public readonly ConfigTemplate: ConfigTemplateEntry[] = [{ - id: 'indexChangesOnly', - type: 'boolean', - name: backendTexts.indexChangesOnly.name, - description: backendTexts.indexChangesOnly.description, - defaultValue: true - }]; + public readonly ConfigTemplate: ConfigTemplateEntry[] = [ + { + id: 'indexChangesOnly', + type: 'boolean', + name: backendTexts.indexChangesOnly.name, + description: backendTexts.indexChangesOnly.description, + defaultValue: true, + }, + ]; public get Supported(): boolean { return Config.Server.Database.type !== DatabaseType.memory; } - protected async init(): Promise { this.directoriesToIndex.push('/'); } @@ -53,9 +58,15 @@ export class IndexingJob implements IJob { public abstract get ConfigTemplate(): ConfigTemplateEntry[]; - public get Progress(): JobProgress { return this.progress; } public get InProgress(): boolean { - return this.Progress !== null && (this.Progress.State === JobProgressStates.running || - this.Progress.State === JobProgressStates.cancelling); + return ( + this.Progress !== null && + (this.Progress.State === JobProgressStates.running || + this.Progress.State === JobProgressStates.cancelling) + ); } - public start(config: T, soloRun = false, allowParallelRun = false): Promise { + public start( + config: T, + soloRun = false, + allowParallelRun = false + ): Promise { if (this.InProgress === false && this.Supported === true) { - Logger.info(LOG_TAG, 'Running job ' + (soloRun === true ? 'solo' : '') + ': ' + this.Name); + Logger.info( + LOG_TAG, + 'Running job ' + (soloRun === true ? 'solo' : '') + ': ' + this.Name + ); this.soloRun = soloRun; this.allowParallelRun = allowParallelRun; this.config = config; - this.progress = new JobProgress(this.Name, JobDTOUtils.getHashName(this.Name, this.config)); + this.progress = new JobProgress( + this.Name, + JobDTOUtils.getHashName(this.Name, this.config) + ); this.progress.OnChange = this.jobListener.onProgressUpdate; const pr = new Promise((resolve): void => { this.prResolve = resolve; }); this.init().catch(console.error); this.run(); - if (!this.IsInstant) { // if instant, wait for execution, otherwise, return right away + if (!this.IsInstant) { + // if instant, wait for execution, otherwise, return right away return Promise.resolve(); } return pr; } else { - Logger.info(LOG_TAG, 'Job already running or not supported: ' + this.Name); - return Promise.reject('Job already running or not supported: ' + this.Name); + Logger.info( + LOG_TAG, + 'Job already running or not supported: ' + this.Name + ); + return Promise.reject( + 'Job already running or not supported: ' + this.Name + ); } } @@ -73,7 +95,7 @@ export abstract class Job implements IJob { public toJSON(): JobDTO { return { Name: this.Name, - ConfigTemplate: this.ConfigTemplate + ConfigTemplate: this.ConfigTemplate, }; } @@ -110,11 +132,15 @@ export abstract class Job implements IJob { private run(): void { process.nextTick(async (): Promise => { try { - if (this.Progress == null || this.Progress.State !== JobProgressStates.running) { + if ( + this.Progress == null || + this.Progress.State !== JobProgressStates.running + ) { this.onFinish(); return; } - if (await this.step() === false) { // finished + if ((await this.step()) === false) { + // finished this.onFinish(); return; } diff --git a/src/backend/model/jobs/jobs/JobProgress.ts b/src/backend/model/jobs/jobs/JobProgress.ts index 12d4e3a2..d181244a 100644 --- a/src/backend/model/jobs/jobs/JobProgress.ts +++ b/src/backend/model/jobs/jobs/JobProgress.ts @@ -1,5 +1,8 @@ -import {JobProgressDTO, JobProgressLogDTO, JobProgressStates} from '../../../../common/entities/job/JobProgressDTO'; - +import { + JobProgressDTO, + JobProgressLogDTO, + JobProgressStates, +} from '../../../../common/entities/job/JobProgressDTO'; export class JobProgress { private steps = { @@ -13,11 +16,12 @@ export class JobProgress { end: null as number, }; private logCounter = 0; - private logs: { id: number, timestamp: string, comment: string }[] = []; + private logs: { id: number; timestamp: string; comment: string }[] = []; - - constructor(public readonly jobName: string, public readonly HashName: string) { - } + constructor( + public readonly jobName: string, + public readonly HashName: string + ) {} set OnChange(val: (progress: JobProgress) => void) { this.onChange = val; @@ -77,14 +81,17 @@ export class JobProgress { return this.logs; } - onChange = (progress: JobProgress): void => { - }; + onChange = (progress: JobProgress): void => {}; log(log: string): void { while (this.logs.length > 10) { this.logs.shift(); } - this.logs.push({id: this.logCounter++, timestamp: (new Date()).toISOString(), comment: log}); + this.logs.push({ + id: this.logCounter++, + timestamp: new Date().toISOString(), + comment: log, + }); this.onChange(this); } @@ -95,10 +102,10 @@ export class JobProgress { state: this.state, time: { start: this.time.start, - end: this.time.end + end: this.time.end, }, logs: this.logs, - steps: this.steps + steps: this.steps, }; } } diff --git a/src/backend/model/jobs/jobs/PhotoConvertingJob.ts b/src/backend/model/jobs/jobs/PhotoConvertingJob.ts index 50c76d26..7ab9d34d 100644 --- a/src/backend/model/jobs/jobs/PhotoConvertingJob.ts +++ b/src/backend/model/jobs/jobs/PhotoConvertingJob.ts @@ -1,29 +1,27 @@ -import {Config} from '../../../../common/config/private/Config'; -import {DefaultsJobs} from '../../../../common/entities/job/JobDTO'; -import {FileJob} from './FileJob'; -import {PhotoProcessing} from '../../fileprocessing/PhotoProcessing'; - +import { Config } from '../../../../common/config/private/Config'; +import { DefaultsJobs } from '../../../../common/entities/job/JobDTO'; +import { FileJob } from './FileJob'; +import { PhotoProcessing } from '../../fileprocessing/PhotoProcessing'; export class PhotoConvertingJob extends FileJob { public readonly Name = DefaultsJobs[DefaultsJobs['Photo Converting']]; constructor() { - super({noVideo: true, noMetaFile: true}); + super({ noVideo: true, noMetaFile: true }); } public get Supported(): boolean { return Config.Client.Media.Photo.Converting.enabled === true; } - protected async shouldProcess(mPath: string): Promise { - return !(await PhotoProcessing.convertedPhotoExist(mPath, Config.Server.Media.Photo.Converting.resolution)); + return !(await PhotoProcessing.convertedPhotoExist( + mPath, + Config.Server.Media.Photo.Converting.resolution + )); } - protected async processFile(mPath: string): Promise { await PhotoProcessing.convertPhoto(mPath); } - - } diff --git a/src/backend/model/jobs/jobs/PreviewFillingJob.ts b/src/backend/model/jobs/jobs/PreviewFillingJob.ts index 261dde6b..fcc2e092 100644 --- a/src/backend/model/jobs/jobs/PreviewFillingJob.ts +++ b/src/backend/model/jobs/jobs/PreviewFillingJob.ts @@ -1,27 +1,29 @@ -import {ObjectManagers} from '../../ObjectManagers'; -import {ConfigTemplateEntry, DefaultsJobs} from '../../../../common/entities/job/JobDTO'; -import {Job} from './Job'; -import {Config} from '../../../../common/config/private/Config'; -import {DatabaseType} from '../../../../common/config/private/PrivateConfig'; - +import { ObjectManagers } from '../../ObjectManagers'; +import { + ConfigTemplateEntry, + DefaultsJobs, +} from '../../../../common/entities/job/JobDTO'; +import { Job } from './Job'; +import { Config } from '../../../../common/config/private/Config'; +import { DatabaseType } from '../../../../common/config/private/PrivateConfig'; export class PreviewFillingJob extends Job { public readonly Name = DefaultsJobs[DefaultsJobs['Preview Filling']]; public readonly ConfigTemplate: ConfigTemplateEntry[] = null; - directoryToSetPreview: { id: number, name: string, path: string }[] = null; + directoryToSetPreview: { id: number; name: string; path: string }[] = null; status: 'Persons' | 'Albums' | 'Directory' = 'Persons'; public get Supported(): boolean { return Config.Server.Database.type !== DatabaseType.memory; } - protected async init(): Promise { - } + protected async init(): Promise {} protected async step(): Promise { if (!this.directoryToSetPreview) { this.Progress.log('Loading Directories to process'); - this.directoryToSetPreview = await ObjectManagers.getInstance().PreviewManager.getPartialDirsWithoutPreviews(); + this.directoryToSetPreview = + await ObjectManagers.getInstance().PreviewManager.getPartialDirsWithoutPreviews(); this.Progress.Left = this.directoryToSetPreview.length + 2; return true; } @@ -57,7 +59,8 @@ export class PreviewFillingJob extends Job { private async stepDirectoryPreview(): Promise { if (this.directoryToSetPreview.length === 0) { - this.directoryToSetPreview = await ObjectManagers.getInstance().PreviewManager.getPartialDirsWithoutPreviews(); + this.directoryToSetPreview = + await ObjectManagers.getInstance().PreviewManager.getPartialDirsWithoutPreviews(); // double check if there is really no more if (this.directoryToSetPreview.length > 0) { return true; // continue @@ -66,14 +69,13 @@ export class PreviewFillingJob extends Job { return false; } const directory = this.directoryToSetPreview.shift(); - this.Progress.log('Setting preview: ' + directory.path + directory.name); + this.Progress.log('Setting preview: ' + directory.path + directory.name); this.Progress.Left = this.directoryToSetPreview.length; - await ObjectManagers.getInstance().PreviewManager.setAndGetPreviewForDirectory(directory); + await ObjectManagers.getInstance().PreviewManager.setAndGetPreviewForDirectory( + directory + ); this.Progress.Processed++; return true; - } - - } diff --git a/src/backend/model/jobs/jobs/PreviewResetJob.ts b/src/backend/model/jobs/jobs/PreviewResetJob.ts index dd0a0a14..ca746b50 100644 --- a/src/backend/model/jobs/jobs/PreviewResetJob.ts +++ b/src/backend/model/jobs/jobs/PreviewResetJob.ts @@ -1,9 +1,11 @@ -import {ObjectManagers} from '../../ObjectManagers'; -import {Config} from '../../../../common/config/private/Config'; -import {ConfigTemplateEntry, DefaultsJobs} from '../../../../common/entities/job/JobDTO'; -import {Job} from './Job'; -import {DatabaseType} from '../../../../common/config/private/PrivateConfig'; - +import { ObjectManagers } from '../../ObjectManagers'; +import { Config } from '../../../../common/config/private/Config'; +import { + ConfigTemplateEntry, + DefaultsJobs, +} from '../../../../common/entities/job/JobDTO'; +import { Job } from './Job'; +import { DatabaseType } from '../../../../common/config/private/PrivateConfig'; export class PreviewRestJob extends Job { public readonly Name = DefaultsJobs[DefaultsJobs['Preview Reset']]; @@ -14,8 +16,7 @@ export class PreviewRestJob extends Job { return Config.Server.Database.type !== DatabaseType.memory; } - protected async init(): Promise { - } + protected async init(): Promise {} protected async step(): Promise { this.Progress.Left = 1; @@ -25,6 +26,4 @@ export class PreviewRestJob extends Job { await ObjectManagers.getInstance().PersonManager.resetPreviews(); return false; } - - } diff --git a/src/backend/model/jobs/jobs/TempFolderCleaningJob.ts b/src/backend/model/jobs/jobs/TempFolderCleaningJob.ts index 7ef77189..06f53897 100644 --- a/src/backend/model/jobs/jobs/TempFolderCleaningJob.ts +++ b/src/backend/model/jobs/jobs/TempFolderCleaningJob.ts @@ -1,11 +1,13 @@ -import {ConfigTemplateEntry, DefaultsJobs} from '../../../../common/entities/job/JobDTO'; +import { + ConfigTemplateEntry, + DefaultsJobs, +} from '../../../../common/entities/job/JobDTO'; import * as path from 'path'; import * as fs from 'fs'; -import {Job} from './Job'; -import {ProjectPath} from '../../../ProjectPath'; -import {PhotoProcessing} from '../../fileprocessing/PhotoProcessing'; -import {VideoProcessing} from '../../fileprocessing/VideoProcessing'; - +import { Job } from './Job'; +import { ProjectPath } from '../../../ProjectPath'; +import { PhotoProcessing } from '../../fileprocessing/PhotoProcessing'; +import { VideoProcessing } from '../../fileprocessing/VideoProcessing'; export class TempFolderCleaningJob extends Job { public readonly Name = DefaultsJobs[DefaultsJobs['Temp Folder Cleaning']]; @@ -14,14 +16,12 @@ export class TempFolderCleaningJob extends Job { directoryQueue: string[] = []; private tempRootCleaned = false; - protected async init(): Promise { this.tempRootCleaned = false; this.directoryQueue = []; this.directoryQueue.push(ProjectPath.TranscodedFolder); } - protected async isValidFile(filePath: string): Promise { if (PhotoProcessing.isPhoto(filePath)) { return PhotoProcessing.isValidConvertedPath(filePath); @@ -35,18 +35,21 @@ export class TempFolderCleaningJob extends Job { } protected async isValidDirectory(filePath: string): Promise { - const originalPath = path.join(ProjectPath.ImageFolder, - path.relative(ProjectPath.TranscodedFolder, filePath)); + const originalPath = path.join( + ProjectPath.ImageFolder, + path.relative(ProjectPath.TranscodedFolder, filePath) + ); try { await fs.promises.access(originalPath); return true; - } catch (e) { - } + } catch (e) {} return false; } protected async readDir(dirPath: string): Promise { - return (await fs.promises.readdir(dirPath)).map(f => path.normalize(path.join(dirPath, f))); + return (await fs.promises.readdir(dirPath)).map((f) => + path.normalize(path.join(dirPath, f)) + ); } protected async stepTempDirectory(): Promise { @@ -57,7 +60,7 @@ export class TempFolderCleaningJob extends Job { this.Progress.log('processing: ' + file); this.Progress.Processed++; if ((await fs.promises.stat(file)).isDirectory()) { - await fs.promises.rm(file, {recursive: true}); + await fs.promises.rm(file, { recursive: true }); } else { await fs.promises.unlink(file); } @@ -67,30 +70,28 @@ export class TempFolderCleaningJob extends Job { } } - return true; - - } protected async stepConvertedDirectory(): Promise { - const filePath = this.directoryQueue.shift(); const stat = await fs.promises.stat(filePath); this.Progress.Left = this.directoryQueue.length; if (stat.isDirectory()) { - if (await this.isValidDirectory(filePath) === false) { + if ((await this.isValidDirectory(filePath)) === false) { this.Progress.log('processing: ' + filePath); this.Progress.Processed++; - await fs.promises.rm(filePath, {recursive: true}); + await fs.promises.rm(filePath, { recursive: true }); } else { this.Progress.log('skipping: ' + filePath); this.Progress.Skipped++; - this.directoryQueue = this.directoryQueue.concat(await this.readDir(filePath)); + this.directoryQueue = this.directoryQueue.concat( + await this.readDir(filePath) + ); } } else { - if (await this.isValidFile(filePath) === false) { + if ((await this.isValidFile(filePath)) === false) { this.Progress.log('processing: ' + filePath); this.Progress.Processed++; await fs.promises.unlink(filePath); @@ -98,7 +99,6 @@ export class TempFolderCleaningJob extends Job { this.Progress.log('skipping: ' + filePath); this.Progress.Skipped++; } - } return true; } @@ -114,5 +114,4 @@ export class TempFolderCleaningJob extends Job { } return this.stepConvertedDirectory(); } - } diff --git a/src/backend/model/jobs/jobs/ThumbnailGenerationJob.ts b/src/backend/model/jobs/jobs/ThumbnailGenerationJob.ts index 65aee256..408281a4 100644 --- a/src/backend/model/jobs/jobs/ThumbnailGenerationJob.ts +++ b/src/backend/model/jobs/jobs/ThumbnailGenerationJob.ts @@ -1,25 +1,26 @@ -import {Config} from '../../../../common/config/private/Config'; -import {DefaultsJobs} from '../../../../common/entities/job/JobDTO'; -import {FileJob} from './FileJob'; -import {PhotoProcessing} from '../../fileprocessing/PhotoProcessing'; -import {ThumbnailSourceType} from '../../threading/PhotoWorker'; -import {MediaDTO, MediaDTOUtils} from '../../../../common/entities/MediaDTO'; -import {FileDTO} from '../../../../common/entities/FileDTO'; -import {backendTexts} from '../../../../common/BackendTexts'; - - -export class ThumbnailGenerationJob extends FileJob<{ sizes: number[], indexedOnly: boolean }> { +import { Config } from '../../../../common/config/private/Config'; +import { DefaultsJobs } from '../../../../common/entities/job/JobDTO'; +import { FileJob } from './FileJob'; +import { PhotoProcessing } from '../../fileprocessing/PhotoProcessing'; +import { ThumbnailSourceType } from '../../threading/PhotoWorker'; +import { MediaDTOUtils } from '../../../../common/entities/MediaDTO'; +import { FileDTO } from '../../../../common/entities/FileDTO'; +import { backendTexts } from '../../../../common/BackendTexts'; +export class ThumbnailGenerationJob extends FileJob<{ + sizes: number[]; + indexedOnly: boolean; +}> { public readonly Name = DefaultsJobs[DefaultsJobs['Thumbnail Generation']]; constructor() { - super({noMetaFile: true}); + super({ noMetaFile: true }); this.ConfigTemplate.push({ id: 'sizes', type: 'number-array', name: backendTexts.sizeToGenerate.name, description: backendTexts.sizeToGenerate.description, - defaultValue: [Config.Client.Media.Thumbnail.thumbnailSizes[0]] + defaultValue: [Config.Client.Media.Thumbnail.thumbnailSizes[0]], }); } @@ -27,10 +28,18 @@ export class ThumbnailGenerationJob extends FileJob<{ sizes: number[], indexedOn return true; } - start(config: { sizes: number[], indexedOnly: boolean }, soloRun = false, allowParallelRun = false): Promise { + start( + config: { sizes: number[]; indexedOnly: boolean }, + soloRun = false, + allowParallelRun = false + ): Promise { for (const item of config.sizes) { if (Config.Client.Media.Thumbnail.thumbnailSizes.indexOf(item) === -1) { - throw new Error('unknown thumbnails size: ' + item + '. Add it to the possible thumbnail sizes.'); + throw new Error( + 'unknown thumbnails size: ' + + item + + '. Add it to the possible thumbnail sizes.' + ); } } @@ -55,13 +64,14 @@ export class ThumbnailGenerationJob extends FileJob<{ sizes: number[], indexedOn protected async processFile(mPath: string): Promise { for (const item of this.config.sizes) { - await PhotoProcessing.generateThumbnail(mPath, + await PhotoProcessing.generateThumbnail( + mPath, item, - MediaDTOUtils.isVideoPath(mPath) ? ThumbnailSourceType.Video : ThumbnailSourceType.Photo, - false); - + MediaDTOUtils.isVideoPath(mPath) + ? ThumbnailSourceType.Video + : ThumbnailSourceType.Photo, + false + ); } } - - } diff --git a/src/backend/model/jobs/jobs/VideoConvertingJob.ts b/src/backend/model/jobs/jobs/VideoConvertingJob.ts index a85cf331..592810d9 100644 --- a/src/backend/model/jobs/jobs/VideoConvertingJob.ts +++ b/src/backend/model/jobs/jobs/VideoConvertingJob.ts @@ -1,16 +1,15 @@ -import {Config} from '../../../../common/config/private/Config'; -import {DefaultsJobs} from '../../../../common/entities/job/JobDTO'; -import {FileJob} from './FileJob'; -import {VideoProcessing} from '../../fileprocessing/VideoProcessing'; +import { Config } from '../../../../common/config/private/Config'; +import { DefaultsJobs } from '../../../../common/entities/job/JobDTO'; +import { FileJob } from './FileJob'; +import { VideoProcessing } from '../../fileprocessing/VideoProcessing'; declare const global: any; - export class VideoConvertingJob extends FileJob { public readonly Name = DefaultsJobs[DefaultsJobs['Video Converting']]; constructor() { - super({noPhoto: true, noMetaFile: true}); + super({ noPhoto: true, noMetaFile: true }); } public get Supported(): boolean { @@ -27,6 +26,4 @@ export class VideoConvertingJob extends FileJob { global.gc(); } } - - } diff --git a/src/backend/model/threading/DiskMangerWorker.ts b/src/backend/model/threading/DiskMangerWorker.ts index 888626a1..f40e17a4 100644 --- a/src/backend/model/threading/DiskMangerWorker.ts +++ b/src/backend/model/threading/DiskMangerWorker.ts @@ -1,22 +1,22 @@ -import {promises as fsp, Stats} from 'fs'; +import { promises as fsp, Stats } from 'fs'; import * as path from 'path'; -import {ParentDirectoryDTO, SubDirectoryDTO} from '../../../common/entities/DirectoryDTO'; -import {PhotoDTO} from '../../../common/entities/PhotoDTO'; -import {ProjectPath} from '../../ProjectPath'; -import {Config} from '../../../common/config/private/Config'; -import {VideoDTO} from '../../../common/entities/VideoDTO'; -import {FileDTO} from '../../../common/entities/FileDTO'; -import {MetadataLoader} from './MetadataLoader'; -import {Logger} from '../../Logger'; -import {SupportedFormats} from '../../../common/SupportedFormats'; -import {VideoProcessing} from '../fileprocessing/VideoProcessing'; -import {PhotoProcessing} from '../fileprocessing/PhotoProcessing'; -import {Utils} from '../../../common/Utils'; - +import { + ParentDirectoryDTO, + SubDirectoryDTO, +} from '../../../common/entities/DirectoryDTO'; +import { PhotoDTO } from '../../../common/entities/PhotoDTO'; +import { ProjectPath } from '../../ProjectPath'; +import { Config } from '../../../common/config/private/Config'; +import { VideoDTO } from '../../../common/entities/VideoDTO'; +import { FileDTO } from '../../../common/entities/FileDTO'; +import { MetadataLoader } from './MetadataLoader'; +import { Logger } from '../../Logger'; +import { SupportedFormats } from '../../../common/SupportedFormats'; +import { VideoProcessing } from '../fileprocessing/VideoProcessing'; +import { PhotoProcessing } from '../fileprocessing/PhotoProcessing'; +import { Utils } from '../../../common/Utils'; export class DiskMangerWorker { - - public static calcLastModified(stat: Stats): number { return Math.max(stat.ctime.getTime(), stat.mtime.getTime()); } @@ -26,12 +26,17 @@ export class DiskMangerWorker { } public static pathFromRelativeDirName(relativeDirectoryName: string): string { - return path.join(path.dirname(this.normalizeDirPath(relativeDirectoryName)), path.sep); + return path.join( + path.dirname(this.normalizeDirPath(relativeDirectoryName)), + path.sep + ); } - - public static pathFromParent(parent: { path: string, name: string }): string { - return path.join(this.normalizeDirPath(path.join(parent.path, parent.name)), path.sep); + public static pathFromParent(parent: { path: string; name: string }): string { + return path.join( + this.normalizeDirPath(path.join(parent.path, parent.name)), + path.sep + ); } public static dirName(dirPath: string): string { @@ -41,9 +46,15 @@ export class DiskMangerWorker { return path.basename(dirPath); } - public static async excludeDir(name: string, relativeDirectoryName: string, absoluteDirectoryName: string): Promise { - if (Config.Server.Indexing.excludeFolderList.length === 0 && - Config.Server.Indexing.excludeFileList.length === 0) { + public static async excludeDir( + name: string, + relativeDirectoryName: string, + absoluteDirectoryName: string + ): Promise { + if ( + Config.Server.Indexing.excludeFolderList.length === 0 && + Config.Server.Indexing.excludeFileList.length === 0 + ) { return false; } const absoluteName = path.normalize(path.join(absoluteDirectoryName, name)); @@ -69,28 +80,38 @@ export class DiskMangerWorker { try { await fsp.access(path.join(absoluteName, exclude)); return true; - } catch (e) { - } + } catch (e) {} } return false; } - public static async scanDirectoryNoMetadata(relativeDirectoryName: string, - settings: DirectoryScanSettings = {}): Promise> { + public static async scanDirectoryNoMetadata( + relativeDirectoryName: string, + settings: DirectoryScanSettings = {} + ): Promise> { settings.noMetadata = true; - return (await this.scanDirectory(relativeDirectoryName, settings)) as ParentDirectoryDTO; + return (await this.scanDirectory( + relativeDirectoryName, + settings + )) as ParentDirectoryDTO; } - public static async scanDirectory(relativeDirectoryName: string, - settings: DirectoryScanSettings = {}): Promise { - + public static async scanDirectory( + relativeDirectoryName: string, + settings: DirectoryScanSettings = {} + ): Promise { relativeDirectoryName = this.normalizeDirPath(relativeDirectoryName); const directoryName = DiskMangerWorker.dirName(relativeDirectoryName); const directoryParent = this.pathFromRelativeDirName(relativeDirectoryName); - const absoluteDirectoryName = path.join(ProjectPath.ImageFolder, relativeDirectoryName); + const absoluteDirectoryName = path.join( + ProjectPath.ImageFolder, + relativeDirectoryName + ); - const stat = await fsp.stat(path.join(ProjectPath.ImageFolder, relativeDirectoryName)); + const stat = await fsp.stat( + path.join(ProjectPath.ImageFolder, relativeDirectoryName) + ); const directory: ParentDirectoryDTO = { id: null, parent: null, @@ -104,34 +125,47 @@ export class DiskMangerWorker { preview: null, validPreview: false, media: [], - metaFile: [] + metaFile: [], }; // nothing to scan, we are here for the empty dir - if (settings.noPhoto === true && settings.noMetadata === true && settings.noVideo === true) { + if ( + settings.noPhoto === true && + settings.noMetadata === true && + settings.noVideo === true + ) { return directory; } const list = await fsp.readdir(absoluteDirectoryName); for (const file of list) { - const fullFilePath = path.normalize(path.join(absoluteDirectoryName, file)); + const fullFilePath = path.normalize( + path.join(absoluteDirectoryName, file) + ); if ((await fsp.stat(fullFilePath)).isDirectory()) { - if (settings.noDirectory === true || settings.previewOnly === true || - await DiskMangerWorker.excludeDir(file, relativeDirectoryName, absoluteDirectoryName)) { + if ( + settings.noDirectory === true || + settings.previewOnly === true || + (await DiskMangerWorker.excludeDir( + file, + relativeDirectoryName, + absoluteDirectoryName + )) + ) { continue; } // create preview directory - const d = await DiskMangerWorker.scanDirectory(path.join(relativeDirectoryName, file), + const d = (await DiskMangerWorker.scanDirectory( + path.join(relativeDirectoryName, file), { - previewOnly: true + previewOnly: true, } - ) as SubDirectoryDTO; + )) as SubDirectoryDTO; d.lastScanned = 0; // it was not a fully scan d.isPartial = true; directory.directories.push(d); - } else if (PhotoProcessing.isPhoto(fullFilePath)) { if (settings.noPhoto === true) { continue; @@ -140,7 +174,10 @@ export class DiskMangerWorker { const photo = { name: file, directory: null, - metadata: settings.noMetadata === true ? null : await MetadataLoader.loadPhotoMetadata(fullFilePath) + metadata: + settings.noMetadata === true + ? null + : await MetadataLoader.loadPhotoMetadata(fullFilePath), } as PhotoDTO; if (!directory.preview) { @@ -148,7 +185,7 @@ export class DiskMangerWorker { directory.preview.directory = { path: directory.path, - name: directory.name + name: directory.name, }; } // add the preview photo to the list of media, so it will be saved to the DB @@ -159,32 +196,43 @@ export class DiskMangerWorker { if (settings.previewOnly === true) { break; } - } else if (VideoProcessing.isVideo(fullFilePath)) { - if (Config.Client.Media.Video.enabled === false || settings.noVideo === true || settings.previewOnly === true) { + if ( + Config.Client.Media.Video.enabled === false || + settings.noVideo === true || + settings.previewOnly === true + ) { continue; } try { directory.media.push({ name: file, directory: null, - metadata: settings.noMetadata === true ? null : await MetadataLoader.loadVideoMetadata(fullFilePath) + metadata: + settings.noMetadata === true + ? null + : await MetadataLoader.loadVideoMetadata(fullFilePath), } as VideoDTO); } catch (e) { - Logger.warn('Media loading error, skipping: ' + file + ', reason: ' + e.toString()); + Logger.warn( + 'Media loading error, skipping: ' + + file + + ', reason: ' + + e.toString() + ); } - } else if (DiskMangerWorker.isMetaFile(fullFilePath)) { - if (!DiskMangerWorker.isEnabledMetaFile(fullFilePath) || + if ( + !DiskMangerWorker.isEnabledMetaFile(fullFilePath) || settings.noMetaFile === true || - settings.previewOnly === true) { + settings.previewOnly === true + ) { continue; } directory.metaFile.push({ name: file, directory: null, } as FileDTO); - } } @@ -193,7 +241,6 @@ export class DiskMangerWorker { return directory; } - private static isMetaFile(fullPath: string): boolean { const extension = path.extname(fullPath).toLowerCase(); return SupportedFormats.WithDots.MetaFiles.indexOf(extension) !== -1; @@ -213,8 +260,6 @@ export class DiskMangerWorker { return false; } - - } export interface DirectoryScanSettings { diff --git a/src/backend/model/threading/MetadataLoader.ts b/src/backend/model/threading/MetadataLoader.ts index 8d0420ac..6f76d796 100644 --- a/src/backend/model/threading/MetadataLoader.ts +++ b/src/backend/model/threading/MetadataLoader.ts @@ -1,65 +1,68 @@ -import {VideoMetadata} from '../../../common/entities/VideoDTO'; -import {FaceRegion, PhotoMetadata} from '../../../common/entities/PhotoDTO'; -import {Config} from '../../../common/config/private/Config'; -import {Logger} from '../../Logger'; +import { VideoMetadata } from '../../../common/entities/VideoDTO'; +import { FaceRegion, PhotoMetadata } from '../../../common/entities/PhotoDTO'; +import { Config } from '../../../common/config/private/Config'; +import { Logger } from '../../Logger'; import * as fs from 'fs'; -import {imageSize} from 'image-size'; +import { imageSize } from 'image-size'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import * as ExifReader from 'exifreader'; -import {ExifParserFactory, OrientationTypes} from 'ts-exif-parser'; -import {IptcParser} from 'ts-node-iptc'; -import {FFmpegFactory} from '../FFmpegFactory'; -import {FfprobeData} from 'fluent-ffmpeg'; -import {Utils} from '../../../common/Utils'; - +import { ExifParserFactory, OrientationTypes } from 'ts-exif-parser'; +import { IptcParser } from 'ts-node-iptc'; +import { FFmpegFactory } from '../FFmpegFactory'; +import { FfprobeData } from 'fluent-ffmpeg'; +import { Utils } from '../../../common/Utils'; const LOG_TAG = '[MetadataLoader]'; const ffmpeg = FFmpegFactory.get(); export class MetadataLoader { - public static loadVideoMetadata(fullPath: string): Promise { return new Promise((resolve) => { const metadata: VideoMetadata = { size: { width: 1, - height: 1 + height: 1, }, bitRate: 0, duration: 0, creationDate: 0, fileSize: 0, - fps: 0 + fps: 0, }; try { const stat = fs.statSync(fullPath); metadata.fileSize = stat.size; metadata.creationDate = stat.mtime.getTime(); - } catch (err) { - } + } catch (err) {} try { ffmpeg(fullPath).ffprobe((err: any, data: FfprobeData) => { if (!!err || data === null || !data.streams[0]) { return resolve(metadata); } - try { for (const stream of data.streams) { if (stream.width) { metadata.size.width = stream.width; metadata.size.height = stream.height; - if (Utils.isInt32(parseInt('' + stream.rotation, 10)) && - (Math.abs(parseInt('' + stream.rotation, 10)) / 90) % 2 === 1) { + if ( + Utils.isInt32(parseInt('' + stream.rotation, 10)) && + (Math.abs(parseInt('' + stream.rotation, 10)) / 90) % 2 === 1 + ) { // noinspection JSSuspiciousNameCombination metadata.size.width = stream.height; // noinspection JSSuspiciousNameCombination metadata.size.height = stream.width; } - if (Utils.isInt32(Math.floor(parseFloat(stream.duration) * 1000))) { - metadata.duration = Math.floor(parseFloat(stream.duration) * 1000); + if ( + Utils.isInt32(Math.floor(parseFloat(stream.duration) * 1000)) + ) { + metadata.duration = Math.floor( + parseFloat(stream.duration) * 1000 + ); } if (Utils.isInt32(parseInt(stream.bit_rate, 10))) { @@ -68,13 +71,14 @@ export class MetadataLoader { if (Utils.isInt32(parseInt(stream.avg_frame_rate, 10))) { metadata.fps = parseInt(stream.avg_frame_rate, 10) || null; } - metadata.creationDate = Date.parse(stream.tags.creation_time) || metadata.creationDate; + metadata.creationDate = + Date.parse(stream.tags.creation_time) || + metadata.creationDate; break; } } - - } catch (err) { - } + // eslint-disable-next-line no-empty + } catch (err) {} metadata.creationDate = metadata.creationDate || 0; return resolve(metadata); @@ -87,237 +91,296 @@ export class MetadataLoader { public static loadPhotoMetadata(fullPath: string): Promise { return new Promise((resolve, reject) => { - const fd = fs.openSync(fullPath, 'r'); + const fd = fs.openSync(fullPath, 'r'); - const data = Buffer.allocUnsafe(Config.Server.photoMetadataSize); - fs.read(fd, data, 0, Config.Server.photoMetadataSize, 0, (err) => { - fs.closeSync(fd); - if (err) { - return reject({file: fullPath, error: err}); - } - const metadata: PhotoMetadata = { - size: {width: 1, height: 1}, - creationDate: 0, - fileSize: 0 - }; + const data = Buffer.allocUnsafe(Config.Server.photoMetadataSize); + fs.read(fd, data, 0, Config.Server.photoMetadataSize, 0, (err) => { + fs.closeSync(fd); + if (err) { + return reject({ file: fullPath, error: err }); + } + const metadata: PhotoMetadata = { + size: { width: 1, height: 1 }, + creationDate: 0, + fileSize: 0, + }; + try { try { + const stat = fs.statSync(fullPath); + metadata.fileSize = stat.size; + metadata.creationDate = stat.mtime.getTime(); + } catch (err) {} - try { - const stat = fs.statSync(fullPath); - metadata.fileSize = stat.size; - metadata.creationDate = stat.mtime.getTime(); - } catch (err) { - } - - try { - const exif = ExifParserFactory.create(data).parse(); - if (exif.tags.ISO || exif.tags.Model || - exif.tags.Make || exif.tags.FNumber || - exif.tags.ExposureTime || exif.tags.FocalLength || - exif.tags.LensModel) { - if (exif.tags.Model && exif.tags.Model !== '') { - metadata.cameraData = metadata.cameraData || {}; - metadata.cameraData.model = '' + exif.tags.Model; - } - if (exif.tags.Make && exif.tags.Make !== '') { - metadata.cameraData = metadata.cameraData || {}; - metadata.cameraData.make = '' + exif.tags.Make; - } - if (exif.tags.LensModel && exif.tags.LensModel !== '') { - metadata.cameraData = metadata.cameraData || {}; - metadata.cameraData.lens = '' + exif.tags.LensModel; - } - if (Utils.isUInt32(exif.tags.ISO)) { - metadata.cameraData = metadata.cameraData || {}; - metadata.cameraData.ISO = parseInt('' + exif.tags.ISO, 10); - } - if (Utils.isFloat32(exif.tags.FocalLength)) { - metadata.cameraData = metadata.cameraData || {}; - metadata.cameraData.focalLength = parseFloat('' + exif.tags.FocalLength); - } - if (Utils.isFloat32(exif.tags.ExposureTime)) { - metadata.cameraData = metadata.cameraData || {}; - metadata.cameraData.exposure = parseFloat(parseFloat('' + exif.tags.ExposureTime).toFixed(4)); - } - if (Utils.isFloat32(exif.tags.FNumber)) { - metadata.cameraData = metadata.cameraData || {}; - metadata.cameraData.fStop = parseFloat(parseFloat('' + exif.tags.FNumber).toFixed(2)); - } + try { + const exif = ExifParserFactory.create(data).parse(); + if ( + exif.tags.ISO || + exif.tags.Model || + exif.tags.Make || + exif.tags.FNumber || + exif.tags.ExposureTime || + exif.tags.FocalLength || + exif.tags.LensModel + ) { + if (exif.tags.Model && exif.tags.Model !== '') { + metadata.cameraData = metadata.cameraData || {}; + metadata.cameraData.model = '' + exif.tags.Model; } - if (!isNaN(exif.tags.GPSLatitude) || exif.tags.GPSLongitude || exif.tags.GPSAltitude) { - metadata.positionData = metadata.positionData || {}; - metadata.positionData.GPSData = {}; - - if (Utils.isFloat32(exif.tags.GPSLongitude)) { - metadata.positionData.GPSData.longitude = parseFloat(exif.tags.GPSLongitude.toFixed(6)); - } - if (Utils.isFloat32(exif.tags.GPSLatitude)) { - metadata.positionData.GPSData.latitude = parseFloat(exif.tags.GPSLatitude.toFixed(6)); - } + if (exif.tags.Make && exif.tags.Make !== '') { + metadata.cameraData = metadata.cameraData || {}; + metadata.cameraData.make = '' + exif.tags.Make; } - if (exif.tags.CreateDate || exif.tags.DateTimeOriginal || exif.tags.ModifyDate) { - metadata.creationDate = (exif.tags.DateTimeOriginal || exif.tags.CreateDate || exif.tags.ModifyDate) * 1000; + if (exif.tags.LensModel && exif.tags.LensModel !== '') { + metadata.cameraData = metadata.cameraData || {}; + metadata.cameraData.lens = '' + exif.tags.LensModel; } - - - if (exif.imageSize) { - metadata.size = {width: exif.imageSize.width, height: exif.imageSize.height}; - } else if (exif.tags.RelatedImageWidth && exif.tags.RelatedImageHeight) { - metadata.size = {width: exif.tags.RelatedImageWidth, height: exif.tags.RelatedImageHeight}; - } else { - const info = imageSize(fullPath); - metadata.size = {width: info.width, height: info.height}; + if (Utils.isUInt32(exif.tags.ISO)) { + metadata.cameraData = metadata.cameraData || {}; + metadata.cameraData.ISO = parseInt('' + exif.tags.ISO, 10); } - } catch (err) { - Logger.debug(LOG_TAG, 'Error parsing exif', fullPath, err); - try { - const info = imageSize(fullPath); - metadata.size = {width: info.width, height: info.height}; - } catch (e) { - metadata.size = {width: 1, height: 1}; + if (Utils.isFloat32(exif.tags.FocalLength)) { + metadata.cameraData = metadata.cameraData || {}; + metadata.cameraData.focalLength = parseFloat( + '' + exif.tags.FocalLength + ); + } + if (Utils.isFloat32(exif.tags.ExposureTime)) { + metadata.cameraData = metadata.cameraData || {}; + metadata.cameraData.exposure = parseFloat( + parseFloat('' + exif.tags.ExposureTime).toFixed(4) + ); + } + if (Utils.isFloat32(exif.tags.FNumber)) { + metadata.cameraData = metadata.cameraData || {}; + metadata.cameraData.fStop = parseFloat( + parseFloat('' + exif.tags.FNumber).toFixed(2) + ); } } + if ( + !isNaN(exif.tags.GPSLatitude) || + exif.tags.GPSLongitude || + exif.tags.GPSAltitude + ) { + metadata.positionData = metadata.positionData || {}; + metadata.positionData.GPSData = {}; - try { - const iptcData = IptcParser.parse(data); - if (iptcData.country_or_primary_location_name) { - metadata.positionData = metadata.positionData || {}; - metadata.positionData.country = iptcData.country_or_primary_location_name.replace(/\0/g, '').trim(); + if (Utils.isFloat32(exif.tags.GPSLongitude)) { + metadata.positionData.GPSData.longitude = parseFloat( + exif.tags.GPSLongitude.toFixed(6) + ); } - if (iptcData.province_or_state) { - metadata.positionData = metadata.positionData || {}; - metadata.positionData.state = iptcData.province_or_state.replace(/\0/g, '').trim(); + if (Utils.isFloat32(exif.tags.GPSLatitude)) { + metadata.positionData.GPSData.latitude = parseFloat( + exif.tags.GPSLatitude.toFixed(6) + ); } - if (iptcData.city) { - metadata.positionData = metadata.positionData || {}; - metadata.positionData.city = iptcData.city.replace(/\0/g, '').trim(); - } - if (iptcData.caption) { - metadata.caption = iptcData.caption.replace(/\0/g, '').trim(); - } - if (Array.isArray(iptcData.keywords)) { - metadata.keywords = iptcData.keywords; - } - - if (iptcData.date_time) { - metadata.creationDate = iptcData.date_time.getTime(); - } - - } catch (err) { - // Logger.debug(LOG_TAG, 'Error parsing iptc data', fullPath, err); + } + if ( + exif.tags.CreateDate || + exif.tags.DateTimeOriginal || + exif.tags.ModifyDate + ) { + metadata.creationDate = + (exif.tags.DateTimeOriginal || + exif.tags.CreateDate || + exif.tags.ModifyDate) * 1000; } - if (!metadata.creationDate) { // creationDate can be negative, when it was created before epoch (1970) - metadata.creationDate = 0; + if (exif.imageSize) { + metadata.size = { + width: exif.imageSize.width, + height: exif.imageSize.height, + }; + } else if ( + exif.tags.RelatedImageWidth && + exif.tags.RelatedImageHeight + ) { + metadata.size = { + width: exif.tags.RelatedImageWidth, + height: exif.tags.RelatedImageHeight, + }; + } else { + const info = imageSize(fullPath); + metadata.size = { width: info.width, height: info.height }; } - - try { - // TODO: clean up the three different exif readers, - // and keep the minimum amount only - const exif = ExifReader.load(data); - if (exif.Rating) { - metadata.rating = (parseInt(exif.Rating.value, 10) as any); - if (metadata.rating < 0) { - metadata.rating = 0; - } - } - if (exif.subject && exif.subject.value && exif.subject.value.length > 0) { - if (metadata.keywords === undefined) { - metadata.keywords = []; - } - for (const kw of exif.subject.value) { - if (metadata.keywords.indexOf(kw.description) === -1) { - metadata.keywords.push(kw.description); - } - } - } - if (exif.Orientation) { - const orientation = (parseInt(exif.Orientation.value as any, 10) as any); - if (OrientationTypes.BOTTOM_LEFT < orientation) { - // noinspection JSSuspiciousNameCombination - const height = metadata.size.width; - // noinspection JSSuspiciousNameCombination - metadata.size.width = metadata.size.height; - metadata.size.height = height; - } - } - if (Config.Client.Faces.enabled) { - const faces: FaceRegion[] = []; - if (exif.Regions && exif.Regions.value.RegionList && exif.Regions.value.RegionList.value) { - for (const regionRoot of exif.Regions.value.RegionList.value as any[]) { - - let type; - let name; - let box; - const createFaceBox = (w: string, h: string, x: string, y: string) => { - return { - width: Math.round(parseFloat(w) * metadata.size.width), - height: Math.round(parseFloat(h) * metadata.size.height), - left: Math.round(parseFloat(x) * metadata.size.width), - top: Math.round(parseFloat(y) * metadata.size.height) - }; - }; - - /* Adobe Lightroom based face region structure */ - if (regionRoot.value && - regionRoot.value['rdf:Description'] && - regionRoot.value['rdf:Description'].value && - regionRoot.value['rdf:Description'].value['mwg-rs:Area']) { - - const region = regionRoot.value['rdf:Description']; - const regionBox = region.value['mwg-rs:Area'].attributes; - - name = region.attributes['mwg-rs:Name']; - type = region.attributes['mwg-rs:Type']; - box = createFaceBox(regionBox['stArea:w'], - regionBox['stArea:h'], - regionBox['stArea:x'], - regionBox['stArea:y']); - /* Load exiftool edited face region structure, see github issue #191 */ - } else if (regionRoot.Area && regionRoot.Name && regionRoot.Type) { - - const regionBox = regionRoot.Area.value; - name = regionRoot.Name.value; - type = regionRoot.Type.value; - box = createFaceBox(regionBox.w.value, - regionBox.h.value, - regionBox.x.value, - regionBox.y.value); - } - - if (type !== 'Face' || !name) { - continue; - } - // convert center base box to corner based box - box.left = Math.round(Math.max(0, box.left - box.width / 2)); - box.top = Math.round(Math.max(0, box.top - box.height / 2)); - faces.push({name, box}); - } - } - if (faces.length > 0) { - metadata.faces = faces; // save faces - if (Config.Client.Faces.keywordsToPersons) { - // remove faces from keywords - metadata.faces.forEach(f => { - const index = metadata.keywords.indexOf(f.name); - if (index !== -1) { - metadata.keywords.splice(index, 1); - } - }); - } - } - } - } catch (err) { - } - - - return resolve(metadata); } catch (err) { - return reject({file: fullPath, error: err}); + Logger.debug(LOG_TAG, 'Error parsing exif', fullPath, err); + try { + const info = imageSize(fullPath); + metadata.size = { width: info.width, height: info.height }; + } catch (e) { + metadata.size = { width: 1, height: 1 }; + } } - }); - } - ); - } + try { + const iptcData = IptcParser.parse(data); + if (iptcData.country_or_primary_location_name) { + metadata.positionData = metadata.positionData || {}; + metadata.positionData.country = + iptcData.country_or_primary_location_name + .replace(/\0/g, '') + .trim(); + } + if (iptcData.province_or_state) { + metadata.positionData = metadata.positionData || {}; + metadata.positionData.state = iptcData.province_or_state + .replace(/\0/g, '') + .trim(); + } + if (iptcData.city) { + metadata.positionData = metadata.positionData || {}; + metadata.positionData.city = iptcData.city + .replace(/\0/g, '') + .trim(); + } + if (iptcData.caption) { + metadata.caption = iptcData.caption.replace(/\0/g, '').trim(); + } + if (Array.isArray(iptcData.keywords)) { + metadata.keywords = iptcData.keywords; + } + + if (iptcData.date_time) { + metadata.creationDate = iptcData.date_time.getTime(); + } + } catch (err) { + // Logger.debug(LOG_TAG, 'Error parsing iptc data', fullPath, err); + } + + if (!metadata.creationDate) { + // creationDate can be negative, when it was created before epoch (1970) + metadata.creationDate = 0; + } + + try { + // TODO: clean up the three different exif readers, + // and keep the minimum amount only + const exif = ExifReader.load(data); + if (exif.Rating) { + metadata.rating = parseInt(exif.Rating.value, 10) as any; + if (metadata.rating < 0) { + metadata.rating = 0; + } + } + if ( + exif.subject && + exif.subject.value && + exif.subject.value.length > 0 + ) { + if (metadata.keywords === undefined) { + metadata.keywords = []; + } + for (const kw of exif.subject.value) { + if (metadata.keywords.indexOf(kw.description) === -1) { + metadata.keywords.push(kw.description); + } + } + } + if (exif.Orientation) { + const orientation = parseInt( + exif.Orientation.value as any, + 10 + ) as any; + if (OrientationTypes.BOTTOM_LEFT < orientation) { + // noinspection JSSuspiciousNameCombination + const height = metadata.size.width; + // noinspection JSSuspiciousNameCombination + metadata.size.width = metadata.size.height; + metadata.size.height = height; + } + } + if (Config.Client.Faces.enabled) { + const faces: FaceRegion[] = []; + if ( + exif.Regions && + exif.Regions.value.RegionList && + exif.Regions.value.RegionList.value + ) { + for (const regionRoot of exif.Regions.value.RegionList + .value as any[]) { + let type; + let name; + let box; + const createFaceBox = ( + w: string, + h: string, + x: string, + y: string + ) => { + return { + width: Math.round(parseFloat(w) * metadata.size.width), + height: Math.round(parseFloat(h) * metadata.size.height), + left: Math.round(parseFloat(x) * metadata.size.width), + top: Math.round(parseFloat(y) * metadata.size.height), + }; + }; + + /* Adobe Lightroom based face region structure */ + if ( + regionRoot.value && + regionRoot.value['rdf:Description'] && + regionRoot.value['rdf:Description'].value && + regionRoot.value['rdf:Description'].value['mwg-rs:Area'] + ) { + const region = regionRoot.value['rdf:Description']; + const regionBox = region.value['mwg-rs:Area'].attributes; + + name = region.attributes['mwg-rs:Name']; + type = region.attributes['mwg-rs:Type']; + box = createFaceBox( + regionBox['stArea:w'], + regionBox['stArea:h'], + regionBox['stArea:x'], + regionBox['stArea:y'] + ); + /* Load exiftool edited face region structure, see github issue #191 */ + } else if ( + regionRoot.Area && + regionRoot.Name && + regionRoot.Type + ) { + const regionBox = regionRoot.Area.value; + name = regionRoot.Name.value; + type = regionRoot.Type.value; + box = createFaceBox( + regionBox.w.value, + regionBox.h.value, + regionBox.x.value, + regionBox.y.value + ); + } + + if (type !== 'Face' || !name) { + continue; + } + // convert center base box to corner based box + box.left = Math.round(Math.max(0, box.left - box.width / 2)); + box.top = Math.round(Math.max(0, box.top - box.height / 2)); + faces.push({ name, box }); + } + } + if (faces.length > 0) { + metadata.faces = faces; // save faces + if (Config.Client.Faces.keywordsToPersons) { + // remove faces from keywords + metadata.faces.forEach((f) => { + const index = metadata.keywords.indexOf(f.name); + if (index !== -1) { + metadata.keywords.splice(index, 1); + } + }); + } + } + } + } catch (err) {} + + return resolve(metadata); + } catch (err) { + return reject({ file: fullPath, error: err }); + } + }); + }); + } } diff --git a/src/backend/model/threading/PhotoWorker.ts b/src/backend/model/threading/PhotoWorker.ts index 7d56fdf7..b5c940ac 100644 --- a/src/backend/model/threading/PhotoWorker.ts +++ b/src/backend/model/threading/PhotoWorker.ts @@ -1,10 +1,9 @@ -import {Metadata, Sharp} from 'sharp'; -import {Logger} from '../../Logger'; -import {FfmpegCommand, FfprobeData} from 'fluent-ffmpeg'; -import {FFmpegFactory} from '../FFmpegFactory'; +import { Metadata, Sharp } from 'sharp'; +import { Logger } from '../../Logger'; +import { FfmpegCommand, FfprobeData } from 'fluent-ffmpeg'; +import { FFmpegFactory } from '../FFmpegFactory'; export class PhotoWorker { - private static imageRenderer: (input: RendererInput) => Promise = null; private static videoRenderer: (input: RendererInput) => Promise = null; @@ -25,18 +24,17 @@ export class PhotoWorker { return PhotoWorker.imageRenderer(input); } - public static renderFromVideo(input: RendererInput): Promise { if (PhotoWorker.videoRenderer === null) { PhotoWorker.videoRenderer = VideoRendererFactory.build(); } return PhotoWorker.videoRenderer(input); } - } export enum ThumbnailSourceType { - Photo = 1, Video = 2 + Photo = 1, + Video = 2, } export interface RendererInput { @@ -47,10 +45,10 @@ export interface RendererInput { outPath: string; qualityPriority: boolean; cut?: { - left: number, - top: number, - width: number, - height: number + left: number; + top: number; + width: number; + height: number; }; } @@ -60,7 +58,6 @@ export class VideoRendererFactory { const path = require('path'); return (input: RendererInput): Promise => { return new Promise((resolve, reject): void => { - Logger.silly('[FFmpeg] rendering thumbnail: ' + input.mediaPath); ffmpeg(input.mediaPath).ffprobe((err: any, data: FfprobeData): void => { @@ -96,15 +93,22 @@ export class VideoRendererFactory { }) .outputOptions(['-qscale:v 4']); if (input.makeSquare === false) { - const newSize = width < height ? Math.min(input.size, width) + 'x?' : '?x' + Math.min(input.size, height); + const newSize = + width < height + ? Math.min(input.size, width) + 'x?' + : '?x' + Math.min(input.size, height); command.takeScreenshots({ - timemarks: ['10%'], size: newSize, filename: fileName, folder + timemarks: ['10%'], + size: newSize, + filename: fileName, + folder, }); - - } else { command.takeScreenshots({ - timemarks: ['10%'], size: input.size + 'x' + input.size, filename: fileName, folder + timemarks: ['10%'], + size: input.size + 'x' + input.size, + filename: fileName, + folder, }); } }); @@ -114,7 +118,6 @@ export class VideoRendererFactory { } export class ImageRendererFactory { - public static build(): (input: RendererInput) => Promise { return ImageRendererFactory.Sharp(); } @@ -123,12 +126,19 @@ export class ImageRendererFactory { const sharp = require('sharp'); sharp.cache(false); return async (input: RendererInput): Promise => { - Logger.silly('[SharpRenderer] rendering photo:' + input.mediaPath + ', size:' + input.size); - const image: Sharp = sharp(input.mediaPath, {failOnError: false}); + Logger.silly( + '[SharpRenderer] rendering photo:' + + input.mediaPath + + ', size:' + + input.size + ); + const image: Sharp = sharp(input.mediaPath, { failOnError: false }); const metadata: Metadata = await image.metadata(); - - const kernel = input.qualityPriority === true ? sharp.kernel.lanczos3 : sharp.kernel.nearest; + const kernel = + input.qualityPriority === true + ? sharp.kernel.lanczos3 + : sharp.kernel.nearest; if (input.cut) { image.extract(input.cut); @@ -136,26 +146,21 @@ export class ImageRendererFactory { if (input.makeSquare === false) { if (metadata.height > metadata.width) { image.resize(Math.min(input.size, metadata.width), null, { - kernel + kernel, }); } else { image.resize(null, Math.min(input.size, metadata.height), { - kernel + kernel, }); } - - } else { - image - .resize(input.size, input.size, { - kernel, - position: sharp.gravity.centre, - fit: 'cover' - }); + image.resize(input.size, input.size, { + kernel, + position: sharp.gravity.centre, + fit: 'cover', + }); } await image.withMetadata().jpeg().toFile(input.outPath); }; } - - } diff --git a/src/backend/model/threading/TaskExecuter.ts b/src/backend/model/threading/TaskExecuter.ts index f3ab6f9e..5c30f954 100644 --- a/src/backend/model/threading/TaskExecuter.ts +++ b/src/backend/model/threading/TaskExecuter.ts @@ -1,12 +1,10 @@ -import {TaskQue} from './TaskQue'; - +import { TaskQue } from './TaskQue'; export interface ITaskExecuter { execute(input: I): Promise; } export class TaskExecuter implements ITaskExecuter { - private taskQue = new TaskQue(); private taskInProgress = 0; private run = async () => { @@ -25,9 +23,7 @@ export class TaskExecuter implements ITaskExecuter { process.nextTick(this.run); }; - constructor(private size: number, private worker: (input: I) => Promise) { - } - + constructor(private size: number, private worker: (input: I) => Promise) {} execute(input: I): Promise { const promise = this.taskQue.add(input).promise.obj; diff --git a/src/backend/model/threading/TaskQue.ts b/src/backend/model/threading/TaskQue.ts index da37bc2f..ef222e27 100644 --- a/src/backend/model/threading/TaskQue.ts +++ b/src/backend/model/threading/TaskQue.ts @@ -1,30 +1,28 @@ -import {Utils} from '../../../common/Utils'; - +import { Utils } from '../../../common/Utils'; export interface TaskQueEntry { data: I; - promise: { obj: Promise, resolve: (ret: O) => void, reject: (err: any) => void }; + promise: { + obj: Promise | null; + resolve: ((ret: O) => void) | null; + reject: ((err: any) => void) | null; + }; } - export class TaskQue { - private tasks: TaskQueEntry[] = []; private processing: TaskQueEntry[] = []; - constructor() { - } - public isEmpty(): boolean { return this.tasks.length === 0; } public add(input: I): TaskQueEntry { - return (this.getSameTask(input) || this.putNewTask(input)); + return this.getSameTask(input) || this.putNewTask(input); } public get(): TaskQueEntry { - const task = this.tasks.shift(); + const task = this.tasks.shift() as TaskQueEntry; this.processing.push(task); return task; } @@ -38,8 +36,10 @@ export class TaskQue { } private getSameTask(input: I): TaskQueEntry { - return this.tasks.find(t => Utils.equalsFilter(t.data, input)) || - this.processing.find(t => Utils.equalsFilter(t.data, input)); + return (this.tasks.find((t) => Utils.equalsFilter(t.data, input)) || + this.processing.find((t) => + Utils.equalsFilter(t.data, input) + )) as TaskQueEntry; } private putNewTask(input: I): TaskQueEntry { @@ -48,14 +48,16 @@ export class TaskQue { promise: { obj: null, resolve: null, - reject: null - } + reject: null, + }, }; this.tasks.push(taskEntry); - taskEntry.promise.obj = new Promise((resolve: (ret: O) => void, reject: (err: any) => void) => { - taskEntry.promise.reject = reject; - taskEntry.promise.resolve = resolve; - }); + taskEntry.promise.obj = new Promise( + (resolve: (ret: O) => void, reject: (err: any) => void) => { + taskEntry.promise.reject = reject; + taskEntry.promise.resolve = resolve; + } + ); return taskEntry; } } diff --git a/src/backend/model/threading/ThreadPool.ts b/src/backend/model/threading/ThreadPool.ts index 3288762b..5a976922 100644 --- a/src/backend/model/threading/ThreadPool.ts +++ b/src/backend/model/threading/ThreadPool.ts @@ -1,13 +1,18 @@ import * as cluster from 'cluster'; -import {Worker} from 'cluster'; -import {Logger} from '../../Logger'; -import {DiskManagerTask, ThumbnailTask, WorkerMessage, WorkerTask, WorkerTaskTypes} from './Worker'; -import {ParentDirectoryDTO} from '../../../common/entities/DirectoryDTO'; -import {RendererInput} from './PhotoWorker'; -import {TaskQue, TaskQueEntry} from './TaskQue'; -import {ITaskExecuter} from './TaskExecuter'; -import {DirectoryScanSettings} from './DiskMangerWorker'; - +import { Worker } from 'cluster'; +import { Logger } from '../../Logger'; +import { + DiskManagerTask, + ThumbnailTask, + WorkerMessage, + WorkerTask, + WorkerTaskTypes, +} from './Worker'; +import { ParentDirectoryDTO } from '../../../common/entities/DirectoryDTO'; +import { RendererInput } from './PhotoWorker'; +import { TaskQue, TaskQueEntry } from './TaskQue'; +import { ITaskExecuter } from './TaskExecuter'; +import { DirectoryScanSettings } from './DiskMangerWorker'; interface WorkerWrapper { worker: Worker; @@ -17,7 +22,6 @@ interface WorkerWrapper { const LOG_TAG = '[ThreadPool]'; export class ThreadPool { - public static WorkerCount = 0; private workers: WorkerWrapper[] = []; private taskQue = new TaskQue(); @@ -59,16 +63,32 @@ export class ThreadPool { } private startWorker(): void { - const worker = {poolTask: null, worker: (cluster as any).fork()} as WorkerWrapper; + const worker = { + poolTask: null, + worker: (cluster as any).fork(), + } as WorkerWrapper; this.workers.push(worker); worker.worker.on('online', (): void => { ThreadPool.WorkerCount++; - Logger.debug(LOG_TAG, 'Worker ' + worker.worker.process.pid + ' is online, worker count:', ThreadPool.WorkerCount); + Logger.debug( + LOG_TAG, + 'Worker ' + worker.worker.process.pid + ' is online, worker count:', + ThreadPool.WorkerCount + ); }); worker.worker.on('exit', (code, signal): void => { ThreadPool.WorkerCount--; - Logger.warn(LOG_TAG, 'Worker ' + worker.worker.process.pid + ' died with code: ' + code + - ', and signal: ' + signal + ', worker count:', ThreadPool.WorkerCount); + Logger.warn( + LOG_TAG, + 'Worker ' + + worker.worker.process.pid + + ' died with code: ' + + code + + ', and signal: ' + + signal + + ', worker count:', + ThreadPool.WorkerCount + ); Logger.debug(LOG_TAG, 'Starting a new worker'); this.startWorker(); }); @@ -87,20 +107,28 @@ export class ThreadPool { this.run(); }); } - } -export class DiskManagerTH extends ThreadPool implements ITaskExecuter { - execute(relativeDirectoryName: string, settings: DirectoryScanSettings = {}): Promise { +export class DiskManagerTH + extends ThreadPool + implements ITaskExecuter +{ + execute( + relativeDirectoryName: string, + settings: DirectoryScanSettings = {} + ): Promise { return super.executeTask({ type: WorkerTaskTypes.diskManager, relativeDirectoryName, - settings + settings, } as DiskManagerTask); } } -export class ThumbnailTH extends ThreadPool implements ITaskExecuter { +export class ThumbnailTH + extends ThreadPool + implements ITaskExecuter +{ execute(input: RendererInput): Promise { return super.executeTask({ type: WorkerTaskTypes.thumbnail, diff --git a/src/backend/model/threading/VideoConverterWorker.ts b/src/backend/model/threading/VideoConverterWorker.ts index dcc2bf23..8c73ad27 100644 --- a/src/backend/model/threading/VideoConverterWorker.ts +++ b/src/backend/model/threading/VideoConverterWorker.ts @@ -1,27 +1,30 @@ -import {Logger} from '../../Logger'; -import {promises as fsp} from 'fs'; -import {FfmpegCommand} from 'fluent-ffmpeg'; -import {FFmpegFactory} from '../FFmpegFactory'; -import {FFmpegPresets, videoCodecType, videoFormatType, videoResolutionType} from '../../../common/config/private/PrivateConfig'; - +import { Logger } from '../../Logger'; +import { promises as fsp } from 'fs'; +import { FfmpegCommand } from 'fluent-ffmpeg'; +import { FFmpegFactory } from '../FFmpegFactory'; +import { + FFmpegPresets, + videoCodecType, + videoFormatType, + videoResolutionType, +} from '../../../common/config/private/PrivateConfig'; export interface VideoConverterInput { videoPath: string; output: { - path: string, - bitRate?: number, - resolution?: videoResolutionType, - fps?: number, - crf?: number, - preset?: FFmpegPresets, - customOptions?: string[], - codec: videoCodecType, - format: videoFormatType + path: string; + bitRate?: number; + resolution?: videoResolutionType; + fps?: number; + crf?: number; + preset?: FFmpegPresets; + customOptions?: string[]; + codec: videoCodecType; + format: videoFormatType; }; } export class VideoConverterWorker { - private static ffmpeg = FFmpegFactory.get(); public static async convert(input: VideoConverterInput): Promise { @@ -29,20 +32,16 @@ export class VideoConverterWorker { input.output.path = origPath + '.part'; await this._convert(input); await fsp.rename(input.output.path, origPath); - } private static _convert(input: VideoConverterInput): Promise { - if (this.ffmpeg == null) { this.ffmpeg = FFmpegFactory.get(); } return new Promise((resolve, reject) => { - Logger.silly('[FFmpeg] transcoding video: ' + input.videoPath); - const command: FfmpegCommand = this.ffmpeg(input.videoPath); let executedCmd = ''; command @@ -58,7 +57,7 @@ export class VideoConverterWorker { }); // set video bitrate if (input.output.bitRate) { - command.videoBitrate((input.output.bitRate / 1024) + 'k'); + command.videoBitrate(input.output.bitRate / 1024 + 'k'); } // set target codec command.videoCodec(input.output.codec); @@ -86,12 +85,11 @@ export class VideoConverterWorker { } // set output format to force - command.format(input.output.format) + command + .format(input.output.format) // save to file .save(input.output.path); - }); } - } diff --git a/src/backend/model/threading/Worker.ts b/src/backend/model/threading/Worker.ts index c858b66c..0ca40907 100644 --- a/src/backend/model/threading/Worker.ts +++ b/src/backend/model/threading/Worker.ts @@ -1,23 +1,25 @@ -import {DirectoryScanSettings, DiskMangerWorker} from './DiskMangerWorker'; -import {Logger} from '../../Logger'; -import {PhotoWorker, RendererInput} from './PhotoWorker'; -import {Utils} from '../../../common/Utils'; -import {MediaDTO} from '../../../common/entities/MediaDTO'; -import {ParentDirectoryDTO} from '../../../common/entities/DirectoryDTO'; +import { DirectoryScanSettings, DiskMangerWorker } from './DiskMangerWorker'; +import { Logger } from '../../Logger'; +import { PhotoWorker, RendererInput } from './PhotoWorker'; +import { Utils } from '../../../common/Utils'; +import { MediaDTO } from '../../../common/entities/MediaDTO'; +import { ParentDirectoryDTO } from '../../../common/entities/DirectoryDTO'; declare var process: NodeJS.Process; const LOG_TAG = '[Worker]'; export class Worker { - public static process)>(): void { + public static process>(): void { Logger.debug(LOG_TAG, 'Worker is waiting for tasks'); process.on('message', async (task: WorkerTask) => { try { let result = null; switch (task.type) { case WorkerTaskTypes.diskManager: - result = await DiskMangerWorker.scanDirectory((task as DiskManagerTask).relativeDirectoryName, - (task as DiskManagerTask).settings); + result = await DiskMangerWorker.scanDirectory( + (task as DiskManagerTask).relativeDirectoryName, + (task as DiskManagerTask).settings + ); if (global.gc) { global.gc(); } @@ -30,18 +32,18 @@ export class Worker { } process.send({ error: null, - result + result, } as WorkerMessage); } catch (err) { - process.send({error: err, result: null}); + process.send({ error: err, result: null }); } }); } } - export enum WorkerTaskTypes { - thumbnail = 1, diskManager = 2 + thumbnail = 1, + diskManager = 2, } export interface WorkerTask { @@ -60,7 +62,7 @@ export interface ThumbnailTask extends WorkerTask { export const WorkerTask = { equals: (t1: WorkerTask, t2: WorkerTask): boolean => { return Utils.equalsFilter(t1, t2); - } + }, }; export interface WorkerMessage { diff --git a/src/backend/routes/AlbumRouter.ts b/src/backend/routes/AlbumRouter.ts index ca0b49da..f34bd76f 100644 --- a/src/backend/routes/AlbumRouter.ts +++ b/src/backend/routes/AlbumRouter.ts @@ -1,22 +1,21 @@ -import {AuthenticationMWs} from '../middlewares/user/AuthenticationMWs'; -import {Express} from 'express'; -import {RenderingMWs} from '../middlewares/RenderingMWs'; -import {UserRoles} from '../../common/entities/UserDTO'; -import {VersionMWs} from '../middlewares/VersionMWs'; -import {AlbumMWs} from '../middlewares/AlbumMWs'; -import {ServerTimingMWs} from '../middlewares/ServerTimingMWs'; +import { AuthenticationMWs } from '../middlewares/user/AuthenticationMWs'; +import { Express } from 'express'; +import { RenderingMWs } from '../middlewares/RenderingMWs'; +import { UserRoles } from '../../common/entities/UserDTO'; +import { VersionMWs } from '../middlewares/VersionMWs'; +import { AlbumMWs } from '../middlewares/AlbumMWs'; +import { ServerTimingMWs } from '../middlewares/ServerTimingMWs'; export class AlbumRouter { public static route(app: Express): void { - this.addListAlbums(app); this.addAddSavedSearch(app); this.addDeleteAlbum(app); } - private static addListAlbums(app: Express): void { - app.get(['/api/albums'], + app.get( + ['/api/albums'], // common part AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.User), @@ -30,7 +29,8 @@ export class AlbumRouter { } private static addDeleteAlbum(app: Express): void { - app.delete(['/api/albums/:id'], + app.delete( + ['/api/albums/:id'], // common part AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), @@ -44,7 +44,8 @@ export class AlbumRouter { } private static addAddSavedSearch(app: Express): void { - app.put(['/api/albums/saved-searches'], + app.put( + ['/api/albums/saved-searches'], // common part AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), @@ -56,5 +57,4 @@ export class AlbumRouter { RenderingMWs.renderResult ); } - } diff --git a/src/backend/routes/ErrorRouter.ts b/src/backend/routes/ErrorRouter.ts index a0a2509e..4a8c2394 100644 --- a/src/backend/routes/ErrorRouter.ts +++ b/src/backend/routes/ErrorRouter.ts @@ -1,33 +1,39 @@ -import {RenderingMWs} from '../middlewares/RenderingMWs'; -import {ErrorCodes, ErrorDTO} from '../../common/entities/Error'; -import {Logger} from '../Logger'; -import {Express, NextFunction, Request, Response} from 'express'; +import { RenderingMWs } from '../middlewares/RenderingMWs'; +import { ErrorCodes, ErrorDTO } from '../../common/entities/Error'; +import { Logger } from '../Logger'; +import { Express, NextFunction, Request, Response } from 'express'; export class ErrorRouter { public static route(app: Express): void { - this.addApiErrorHandler(app); this.addGenericHandler(app); } private static addApiErrorHandler(app: Express): void { - app.use('/api/*', - RenderingMWs.renderError - ); + app.use('/api/*', RenderingMWs.renderError); } private static addGenericHandler(app: Express): void { - app.use((err: any, req: Request, res: Response, next: NextFunction): any => { - + app.use( + (err: any, req: Request, res: Response, next: NextFunction): any => { if (err.name === 'UnauthorizedError') { // jwt authentication error res.status(401); - return next(new ErrorDTO(ErrorCodes.NOT_AUTHENTICATED, 'Invalid token')); + return next( + new ErrorDTO(ErrorCodes.NOT_AUTHENTICATED, 'Invalid token') + ); } if (err.name === 'ForbiddenError' && err.code === 'EBADCSRFTOKEN') { // jwt authentication error res.status(401); - return next(new ErrorDTO(ErrorCodes.NOT_AUTHENTICATED, 'Invalid CSRF token', err, req)); + return next( + new ErrorDTO( + ErrorCodes.NOT_AUTHENTICATED, + 'Invalid CSRF token', + err, + req + ) + ); } console.log(err); @@ -35,10 +41,16 @@ export class ErrorRouter { // Flush out the stack to the console Logger.error('Unexpected error:'); console.error(err); - return next(new ErrorDTO(ErrorCodes.SERVER_ERROR, 'Unknown server side error', err, req)); + return next( + new ErrorDTO( + ErrorCodes.SERVER_ERROR, + 'Unknown server side error', + err, + req + ) + ); }, RenderingMWs.renderError ); } - } diff --git a/src/backend/routes/GalleryRouter.ts b/src/backend/routes/GalleryRouter.ts index ffbc61e8..664edf28 100644 --- a/src/backend/routes/GalleryRouter.ts +++ b/src/backend/routes/GalleryRouter.ts @@ -1,18 +1,17 @@ -import {AuthenticationMWs} from '../middlewares/user/AuthenticationMWs'; -import {Express} from 'express'; -import {GalleryMWs} from '../middlewares/GalleryMWs'; -import {RenderingMWs} from '../middlewares/RenderingMWs'; -import {ThumbnailGeneratorMWs} from '../middlewares/thumbnail/ThumbnailGeneratorMWs'; -import {UserRoles} from '../../common/entities/UserDTO'; -import {ThumbnailSourceType} from '../model/threading/PhotoWorker'; -import {VersionMWs} from '../middlewares/VersionMWs'; -import {SupportedFormats} from '../../common/SupportedFormats'; -import {PhotoConverterMWs} from '../middlewares/thumbnail/PhotoConverterMWs'; -import {ServerTimingMWs} from '../middlewares/ServerTimingMWs'; +import { AuthenticationMWs } from '../middlewares/user/AuthenticationMWs'; +import { Express } from 'express'; +import { GalleryMWs } from '../middlewares/GalleryMWs'; +import { RenderingMWs } from '../middlewares/RenderingMWs'; +import { ThumbnailGeneratorMWs } from '../middlewares/thumbnail/ThumbnailGeneratorMWs'; +import { UserRoles } from '../../common/entities/UserDTO'; +import { ThumbnailSourceType } from '../model/threading/PhotoWorker'; +import { VersionMWs } from '../middlewares/VersionMWs'; +import { SupportedFormats } from '../../common/SupportedFormats'; +import { PhotoConverterMWs } from '../middlewares/thumbnail/PhotoConverterMWs'; +import { ServerTimingMWs } from '../middlewares/ServerTimingMWs'; export class GalleryRouter { public static route(app: Express): void { - this.addGetImageIcon(app); this.addGetVideoIcon(app); this.addGetPhotoThumbnail(app); @@ -31,7 +30,8 @@ export class GalleryRouter { } protected static addDirectoryList(app: Express): void { - app.get(['/api/gallery/content/:directory(*)', '/api/gallery/', '/api/gallery//'], + app.get( + ['/api/gallery/content/:directory(*)', '/api/gallery/', '/api/gallery//'], // common part AuthenticationMWs.authenticate, AuthenticationMWs.normalizePathParam('directory'), @@ -48,7 +48,8 @@ export class GalleryRouter { } protected static addDirectoryZip(app: Express): void { - app.get(['/api/gallery/zip/:directory(*)'], + app.get( + ['/api/gallery/zip/:directory(*)'], // common part AuthenticationMWs.authenticate, AuthenticationMWs.normalizePathParam('directory'), @@ -61,7 +62,12 @@ export class GalleryRouter { } protected static addGetImage(app: Express): void { - app.get(['/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Photos.join('|') + '))'], + app.get( + [ + '/api/gallery/content/:mediaPath(*.(' + + SupportedFormats.Photos.join('|') + + '))', + ], // common part AuthenticationMWs.authenticate, AuthenticationMWs.normalizePathParam('mediaPath'), @@ -75,7 +81,12 @@ export class GalleryRouter { } protected static addGetBestFitImage(app: Express): void { - app.get(['/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Photos.join('|') + '))/bestFit'], + app.get( + [ + '/api/gallery/content/:mediaPath(*.(' + + SupportedFormats.Photos.join('|') + + '))/bestFit', + ], // common part AuthenticationMWs.authenticate, AuthenticationMWs.normalizePathParam('mediaPath'), @@ -90,7 +101,12 @@ export class GalleryRouter { } protected static addGetVideo(app: Express): void { - app.get(['/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Videos.join('|') + '))'], + app.get( + [ + '/api/gallery/content/:mediaPath(*.(' + + SupportedFormats.Videos.join('|') + + '))', + ], // common part AuthenticationMWs.authenticate, AuthenticationMWs.normalizePathParam('mediaPath'), @@ -104,7 +120,12 @@ export class GalleryRouter { } protected static addGetBestFitVideo(app: Express): void { - app.get(['/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Videos.join('|') + '))/bestFit'], + app.get( + [ + '/api/gallery/content/:mediaPath(*.(' + + SupportedFormats.Videos.join('|') + + '))/bestFit', + ], // common part AuthenticationMWs.authenticate, AuthenticationMWs.normalizePathParam('mediaPath'), @@ -119,7 +140,12 @@ export class GalleryRouter { } protected static addGetMetaFile(app: Express): void { - app.get(['/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.MetaFiles.join('|') + '))'], + app.get( + [ + '/api/gallery/content/:mediaPath(*.(' + + SupportedFormats.MetaFiles.join('|') + + '))', + ], // common part AuthenticationMWs.authenticate, AuthenticationMWs.normalizePathParam('mediaPath'), @@ -133,7 +159,8 @@ export class GalleryRouter { } protected static addRandom(app: Express): void { - app.get(['/api/gallery/random/:searchQueryDTO'], + app.get( + ['/api/gallery/random/:searchQueryDTO'], // common part AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Guest), @@ -148,7 +175,10 @@ export class GalleryRouter { } protected static addGetPhotoThumbnail(app: Express): void { - app.get('/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Photos.join('|') + '))/thumbnail/:size?', + app.get( + '/api/gallery/content/:mediaPath(*.(' + + SupportedFormats.Photos.join('|') + + '))/thumbnail/:size?', // common part AuthenticationMWs.authenticate, AuthenticationMWs.normalizePathParam('mediaPath'), @@ -163,7 +193,10 @@ export class GalleryRouter { } protected static addGetVideoThumbnail(app: Express): void { - app.get('/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Videos.join('|') + '))/thumbnail/:size?', + app.get( + '/api/gallery/content/:mediaPath(*.(' + + SupportedFormats.Videos.join('|') + + '))/thumbnail/:size?', // common part AuthenticationMWs.authenticate, AuthenticationMWs.normalizePathParam('mediaPath'), @@ -177,9 +210,11 @@ export class GalleryRouter { ); } - protected static addGetVideoIcon(app: Express): void { - app.get('/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Videos.join('|') + '))/icon', + app.get( + '/api/gallery/content/:mediaPath(*.(' + + SupportedFormats.Videos.join('|') + + '))/icon', // common part AuthenticationMWs.authenticate, AuthenticationMWs.normalizePathParam('mediaPath'), @@ -194,7 +229,10 @@ export class GalleryRouter { } protected static addGetImageIcon(app: Express): void { - app.get('/api/gallery/content/:mediaPath(*\.(' + SupportedFormats.Photos.join('|') + '))/icon', + app.get( + '/api/gallery/content/:mediaPath(*.(' + + SupportedFormats.Photos.join('|') + + '))/icon', // common part AuthenticationMWs.authenticate, AuthenticationMWs.normalizePathParam('mediaPath'), @@ -209,7 +247,8 @@ export class GalleryRouter { } protected static addSearch(app: Express): void { - app.get('/api/search/:searchQueryDTO(*)', + app.get( + '/api/search/:searchQueryDTO(*)', // common part AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Guest), @@ -224,9 +263,9 @@ export class GalleryRouter { ); } - protected static addAutoComplete(app: Express): void { - app.get('/api/autocomplete/:text(*)', + app.get( + '/api/autocomplete/:text(*)', // common part AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Guest), @@ -238,5 +277,4 @@ export class GalleryRouter { RenderingMWs.renderResult ); } - } diff --git a/src/backend/routes/LoggerRouter.ts b/src/backend/routes/LoggerRouter.ts index 1cf98cc3..9440eeef 100644 --- a/src/backend/routes/LoggerRouter.ts +++ b/src/backend/routes/LoggerRouter.ts @@ -1,5 +1,5 @@ -import {Express, NextFunction, Request, Response} from 'express'; -import {logFN, Logger} from '../Logger'; +import { Express, NextFunction, Request, Response } from 'express'; +import { logFN, Logger } from '../Logger'; declare global { namespace Express { @@ -23,7 +23,12 @@ export class LoggerRouter { res.end = (a?: any, b?: any, c?: any) => { res.end = end; res.end(a, b, c); - loggerFn(req.method, req.url, res.statusCode, (Date.now() - req._startTime) + 'ms'); + loggerFn( + req.method, + req.url, + res.statusCode, + Date.now() - req._startTime + 'ms' + ); return res; }; } @@ -40,15 +45,17 @@ export class LoggerRouter { return next(); }); - app.get('/node_modules*', (req: Request, res: Response, next: NextFunction): any => { - LoggerRouter.log(Logger.silly, req, res); - return next(); - }); + app.get( + '/node_modules*', + (req: Request, res: Response, next: NextFunction): any => { + LoggerRouter.log(Logger.silly, req, res); + return next(); + } + ); app.use((req: Request, res: Response, next: NextFunction): any => { LoggerRouter.log(Logger.debug, req, res); return next(); }); - } } diff --git a/src/backend/routes/NotificationRouter.ts b/src/backend/routes/NotificationRouter.ts index 71ce89c4..bcf14f90 100644 --- a/src/backend/routes/NotificationRouter.ts +++ b/src/backend/routes/NotificationRouter.ts @@ -1,19 +1,19 @@ -import {UserRoles} from '../../common/entities/UserDTO'; -import {AuthenticationMWs} from '../middlewares/user/AuthenticationMWs'; -import {RenderingMWs} from '../middlewares/RenderingMWs'; -import {NotificationMWs} from '../middlewares/NotificationMWs'; -import {Express} from 'express'; -import {VersionMWs} from '../middlewares/VersionMWs'; -import {ServerTimingMWs} from '../middlewares/ServerTimingMWs'; +import { UserRoles } from '../../common/entities/UserDTO'; +import { AuthenticationMWs } from '../middlewares/user/AuthenticationMWs'; +import { RenderingMWs } from '../middlewares/RenderingMWs'; +import { NotificationMWs } from '../middlewares/NotificationMWs'; +import { Express } from 'express'; +import { VersionMWs } from '../middlewares/VersionMWs'; +import { ServerTimingMWs } from '../middlewares/ServerTimingMWs'; export class NotificationRouter { public static route(app: Express): void { - this.addGetNotifications(app); } private static addGetNotifications(app: Express): void { - app.get('/api/notifications', + app.get( + '/api/notifications', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Guest), VersionMWs.injectGalleryVersion, @@ -22,5 +22,4 @@ export class NotificationRouter { RenderingMWs.renderResult ); } - } diff --git a/src/backend/routes/PersonRouter.ts b/src/backend/routes/PersonRouter.ts index ceb2a427..85a80887 100644 --- a/src/backend/routes/PersonRouter.ts +++ b/src/backend/routes/PersonRouter.ts @@ -1,24 +1,23 @@ -import {AuthenticationMWs} from '../middlewares/user/AuthenticationMWs'; -import {Express} from 'express'; -import {RenderingMWs} from '../middlewares/RenderingMWs'; -import {UserRoles} from '../../common/entities/UserDTO'; -import {PersonMWs} from '../middlewares/PersonMWs'; -import {ThumbnailGeneratorMWs} from '../middlewares/thumbnail/ThumbnailGeneratorMWs'; -import {VersionMWs} from '../middlewares/VersionMWs'; -import {Config} from '../../common/config/private/Config'; -import {ServerTimingMWs} from '../middlewares/ServerTimingMWs'; +import { AuthenticationMWs } from '../middlewares/user/AuthenticationMWs'; +import { Express } from 'express'; +import { RenderingMWs } from '../middlewares/RenderingMWs'; +import { UserRoles } from '../../common/entities/UserDTO'; +import { PersonMWs } from '../middlewares/PersonMWs'; +import { ThumbnailGeneratorMWs } from '../middlewares/thumbnail/ThumbnailGeneratorMWs'; +import { VersionMWs } from '../middlewares/VersionMWs'; +import { Config } from '../../common/config/private/Config'; +import { ServerTimingMWs } from '../middlewares/ServerTimingMWs'; export class PersonRouter { public static route(app: Express): void { - this.updatePerson(app); this.addGetPersons(app); this.getPersonThumbnail(app); } - protected static updatePerson(app: Express): void { - app.post(['/api/person/:name'], + app.post( + ['/api/person/:name'], // common part AuthenticationMWs.authenticate, AuthenticationMWs.authorise(Config.Client.Faces.writeAccessMinRole), @@ -32,7 +31,8 @@ export class PersonRouter { } protected static addGetPersons(app: Express): void { - app.get(['/api/person'], + app.get( + ['/api/person'], // common part AuthenticationMWs.authenticate, AuthenticationMWs.authorise(Config.Client.Faces.readAccessMinRole), @@ -49,7 +49,8 @@ export class PersonRouter { } protected static getPersonThumbnail(app: Express): void { - app.get(['/api/person/:name/thumbnail'], + app.get( + ['/api/person/:name/thumbnail'], // common part AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.User), @@ -62,5 +63,4 @@ export class PersonRouter { RenderingMWs.renderFile ); } - } diff --git a/src/backend/routes/PublicRouter.ts b/src/backend/routes/PublicRouter.ts index aa424b77..f2b669a1 100644 --- a/src/backend/routes/PublicRouter.ts +++ b/src/backend/routes/PublicRouter.ts @@ -1,14 +1,14 @@ -import {Express, NextFunction, Request, Response} from 'express'; +import { Express, NextFunction, Request, Response } from 'express'; import * as path from 'path'; import * as fs from 'fs'; import * as ejs from 'ejs'; -import {Config} from '../../common/config/private/Config'; -import {ProjectPath} from '../ProjectPath'; -import {AuthenticationMWs} from '../middlewares/user/AuthenticationMWs'; -import {CookieNames} from '../../common/CookieNames'; -import {ErrorCodes, ErrorDTO} from '../../common/entities/Error'; -import {UserDTO} from '../../common/entities/UserDTO'; -import {ServerTimeEntry} from '../middlewares/ServerTimingMWs'; +import { Config } from '../../common/config/private/Config'; +import { ProjectPath } from '../ProjectPath'; +import { AuthenticationMWs } from '../middlewares/user/AuthenticationMWs'; +import { CookieNames } from '../../common/CookieNames'; +import { ErrorCodes, ErrorDTO } from '../../common/entities/Error'; +import { UserDTO } from '../../common/entities/UserDTO'; +import { ServerTimeEntry } from '../middlewares/ServerTimingMWs'; declare global { namespace Express { @@ -16,7 +16,7 @@ declare global { locale?: string; localePath?: string; tpl?: any; - timing?: { [key: string]: ServerTimeEntry } ; + timing?: { [key: string]: ServerTimeEntry }; } interface Response { @@ -26,13 +26,13 @@ declare global { } export class PublicRouter { - - public static route(app: Express): void { const setLocale = (req: Request, res: Response, next: NextFunction) => { let selectedLocale = req.locale; if (req.cookies && req.cookies[CookieNames.lang]) { - if (Config.Client.languages.indexOf(req.cookies[CookieNames.lang]) !== -1) { + if ( + Config.Client.languages.indexOf(req.cookies[CookieNames.lang]) !== -1 + ) { selectedLocale = req.cookies[CookieNames.lang]; } } @@ -42,16 +42,18 @@ export class PublicRouter { }; const renderIndex = (req: Request, res: Response, next: NextFunction) => { - ejs.renderFile(path.join(ProjectPath.FrontendFolder, req.localePath, 'index.html'), - res.tpl, (err, str) => { + ejs.renderFile( + path.join(ProjectPath.FrontendFolder, req.localePath, 'index.html'), + res.tpl, + (err, str) => { if (err) { return next(new ErrorDTO(ErrorCodes.GENERAL_ERROR, err.message)); } res.send(str); - }); + } + ); }; - const redirectToBase = (locale: string) => { return (req: Request, res: Response) => { if (Config.Client.languages.indexOf(locale) !== -1) { @@ -61,77 +63,101 @@ export class PublicRouter { }; }; - app.use( - (req: Request, res: Response, next: NextFunction) => { - res.tpl = {}; + app.use((req: Request, res: Response, next: NextFunction) => { + res.tpl = {}; - res.tpl.user = null; - if (req.session.user) { - res.tpl.user = { - id: req.session.user.id, - name: req.session.user.name, - csrfToken: req.session.user.csrfToken, - role: req.session.user.role, - usedSharingKey: req.session.user.usedSharingKey, - permissions: req.session.user.permissions - } as UserDTO; + res.tpl.user = null; + if (req.session['user']) { + res.tpl.user = { + id: req.session['user'].id, + name: req.session['user'].name, + csrfToken: req.session['user'].csrfToken, + role: req.session['user'].role, + usedSharingKey: req.session['user'].usedSharingKey, + permissions: req.session['user'].permissions, + } as UserDTO; - if (!res.tpl.user.csrfToken && req.csrfToken) { - res.tpl.user.csrfToken = req.csrfToken(); - } + if (!res.tpl.user.csrfToken && req.csrfToken) { + res.tpl.user.csrfToken = req.csrfToken(); } - const confCopy = {Client: Config.Client.toJSON({attachVolatile: true})}; - // Escaping html tags, like - confCopy.Client.Other.customHTMLHead = confCopy.Client.Other.customHTMLHead.replace(/&/g, '&') + } + const confCopy = { + Client: Config.Client.toJSON({ attachVolatile: true }), + }; + // Escaping html tags, like + confCopy.Client.Other.customHTMLHead = + confCopy.Client.Other.customHTMLHead + .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); - res.tpl.Config = confCopy; - res.tpl.customHTMLHead = Config.Client.Other.customHTMLHead; + res.tpl.Config = confCopy; + res.tpl.customHTMLHead = Config.Client.Other.customHTMLHead; - return next(); + return next(); + }); + + app.get('/heartbeat', (req: Request, res: Response) => { + res.sendStatus(200); + }); + + app.get('/manifest.json', (req: Request, res: Response) => { + res.send({ + name: Config.Client.applicationTitle, + icons: [ + { + src: 'assets/icon_inv.png', + sizes: '48x48 72x72 96x96 128x128 256x256', + }, + ], + display: 'standalone', + orientation: 'any', + start_url: + Config.Client.publicUrl === '' ? '.' : Config.Client.publicUrl, + background_color: '#000000', + theme_color: '#000000', }); + }); - app.get('/heartbeat', - (req: Request, res: Response) => { - res.sendStatus(200); - } - ); - - app.get('/manifest.json', - (req: Request, res: Response) => { - res.send({ - name: Config.Client.applicationTitle, - icons: [ - { - src: 'assets/icon_inv.png', - sizes: '48x48 72x72 96x96 128x128 256x256' - } - ], - display: 'standalone', - orientation: 'any', - start_url: Config.Client.publicUrl === '' ? '.' : Config.Client.publicUrl, - background_color: '#000000', - theme_color: '#000000' - }); - } - ); - - app.get(['/', '/login', '/gallery*', '/share*', '/admin', '/duplicates', '/faces', '/albums', '/search*'], + app.get( + [ + '/', + '/login', + '/gallery*', + '/share*', + '/admin', + '/duplicates', + '/faces', + '/albums', + '/search*', + ], AuthenticationMWs.tryAuthenticate, setLocale, renderIndex ); - Config.Client.languages.forEach(l => { - app.get(['/' + l + '/', '/' + l + '/login', '/' + l + '/gallery*', '/' + l + '/share*', '/' + l + '/admin', '/' + l + '/search*'], + Config.Client.languages.forEach((l) => { + app.get( + [ + '/' + l + '/', + '/' + l + '/login', + '/' + l + '/gallery*', + '/' + l + '/share*', + '/' + l + '/admin', + '/' + l + '/search*', + ], redirectToBase(l) ); }); const renderFile = (subDir: string = '') => { return (req: Request, res: Response) => { - const file = path.join(ProjectPath.FrontendFolder, req.localePath, subDir, req.params.file); + const file = path.join( + ProjectPath.FrontendFolder, + req.localePath, + subDir, + req.params.file + ); if (!fs.existsSync(file)) { return res.sendStatus(404); } @@ -139,15 +165,17 @@ export class PublicRouter { }; }; - app.get('/assets/:file(*)', + app.get( + '/assets/:file(*)', setLocale, AuthenticationMWs.normalizePathParam('file'), - renderFile('assets')); - app.get('/:file', + renderFile('assets') + ); + app.get( + '/:file', setLocale, AuthenticationMWs.normalizePathParam('file'), - renderFile()); - + renderFile() + ); } - } diff --git a/src/backend/routes/Router.ts b/src/backend/routes/Router.ts index 859721d4..261e0363 100644 --- a/src/backend/routes/Router.ts +++ b/src/backend/routes/Router.ts @@ -1,19 +1,17 @@ -import {Express} from 'express'; -import {PublicRouter} from './PublicRouter'; -import {UserRouter} from './UserRouter'; -import {GalleryRouter} from './GalleryRouter'; -import {PersonRouter} from './PersonRouter'; -import {SharingRouter} from './SharingRouter'; -import {AdminRouter} from './admin/AdminRouter'; -import {SettingsRouter} from './admin/SettingsRouter'; -import {NotificationRouter} from './NotificationRouter'; -import {ErrorRouter} from './ErrorRouter'; -import {AlbumRouter} from './AlbumRouter'; +import { Express } from 'express'; +import { PublicRouter } from './PublicRouter'; +import { UserRouter } from './UserRouter'; +import { GalleryRouter } from './GalleryRouter'; +import { PersonRouter } from './PersonRouter'; +import { SharingRouter } from './SharingRouter'; +import { AdminRouter } from './admin/AdminRouter'; +import { SettingsRouter } from './admin/SettingsRouter'; +import { NotificationRouter } from './NotificationRouter'; +import { ErrorRouter } from './ErrorRouter'; +import { AlbumRouter } from './AlbumRouter'; export class Router { - public static route(app: Express): void { - PublicRouter.route(app); AdminRouter.route(app); diff --git a/src/backend/routes/SharingRouter.ts b/src/backend/routes/SharingRouter.ts index e8227db3..cd7460da 100644 --- a/src/backend/routes/SharingRouter.ts +++ b/src/backend/routes/SharingRouter.ts @@ -1,14 +1,13 @@ -import {AuthenticationMWs} from '../middlewares/user/AuthenticationMWs'; -import {UserRoles} from '../../common/entities/UserDTO'; -import {RenderingMWs} from '../middlewares/RenderingMWs'; -import {SharingMWs} from '../middlewares/SharingMWs'; +import { AuthenticationMWs } from '../middlewares/user/AuthenticationMWs'; +import { UserRoles } from '../../common/entities/UserDTO'; +import { RenderingMWs } from '../middlewares/RenderingMWs'; +import { SharingMWs } from '../middlewares/SharingMWs'; import * as express from 'express'; -import {QueryParams} from '../../common/QueryParams'; -import {ServerTimingMWs} from '../middlewares/ServerTimingMWs'; +import { QueryParams } from '../../common/QueryParams'; +import { ServerTimingMWs } from '../middlewares/ServerTimingMWs'; export class SharingRouter { public static route(app: express.Express): void { - this.addShareLogin(app); this.addGetSharing(app); this.addCreateSharing(app); @@ -18,7 +17,8 @@ export class SharingRouter { } private static addShareLogin(app: express.Express): void { - app.post('/api/share/login', + app.post( + '/api/share/login', AuthenticationMWs.inverseAuthenticate, AuthenticationMWs.shareLogin, ServerTimingMWs.addServerTiming, @@ -27,7 +27,8 @@ export class SharingRouter { } private static addGetSharing(app: express.Express): void { - app.get('/api/share/:' + QueryParams.gallery.sharingKey_params, + app.get( + '/api/share/:' + QueryParams.gallery.sharingKey_params, AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.LimitedGuest), SharingMWs.getSharing, @@ -37,7 +38,8 @@ export class SharingRouter { } private static addCreateSharing(app: express.Express): void { - app.post(['/api/share/:directory(*)', '/api/share/', '/api/share//'], + app.post( + ['/api/share/:directory(*)', '/api/share/', '/api/share//'], AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.User), SharingMWs.createSharing, @@ -47,7 +49,8 @@ export class SharingRouter { } private static addUpdateSharing(app: express.Express): void { - app.put(['/api/share/:directory(*)', '/api/share/', '/api/share//'], + app.put( + ['/api/share/:directory(*)', '/api/share/', '/api/share//'], AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.User), SharingMWs.updateSharing, @@ -56,9 +59,9 @@ export class SharingRouter { ); } - private static addDeleteSharing(app: express.Express): void { - app.delete(['/api/share/:sharingKey'], + app.delete( + ['/api/share/:sharingKey'], AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), SharingMWs.deleteSharing, @@ -68,7 +71,8 @@ export class SharingRouter { } private static addListSharing(app: express.Express): void { - app.get(['/api/share/list'], + app.get( + ['/api/share/list'], AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.User), SharingMWs.listSharing, @@ -76,5 +80,4 @@ export class SharingRouter { RenderingMWs.renderSharingList ); } - } diff --git a/src/backend/routes/UserRouter.ts b/src/backend/routes/UserRouter.ts index 02bc7f93..c8afdeb9 100644 --- a/src/backend/routes/UserRouter.ts +++ b/src/backend/routes/UserRouter.ts @@ -1,10 +1,10 @@ -import {UserMWs} from '../middlewares/user/UserMWs'; -import {Express} from 'express'; -import {UserRoles} from '../../common/entities/UserDTO'; -import {AuthenticationMWs} from '../middlewares/user/AuthenticationMWs'; -import {UserRequestConstrainsMWs} from '../middlewares/user/UserRequestConstrainsMWs'; -import {RenderingMWs} from '../middlewares/RenderingMWs'; -import {ServerTimingMWs} from '../middlewares/ServerTimingMWs'; +import { UserMWs } from '../middlewares/user/UserMWs'; +import { Express } from 'express'; +import { UserRoles } from '../../common/entities/UserDTO'; +import { AuthenticationMWs } from '../middlewares/user/AuthenticationMWs'; +import { UserRequestConstrainsMWs } from '../middlewares/user/UserRequestConstrainsMWs'; +import { RenderingMWs } from '../middlewares/RenderingMWs'; +import { ServerTimingMWs } from '../middlewares/ServerTimingMWs'; export class UserRouter { public static route(app: Express): void { @@ -13,7 +13,6 @@ export class UserRouter { this.addGetSessionUser(app); this.addChangePassword(app); - this.addCreateUser(app); this.addDeleteUser(app); this.addListUsers(app); @@ -21,7 +20,8 @@ export class UserRouter { } private static addLogin(app: Express): void { - app.post('/api/user/login', + app.post( + '/api/user/login', AuthenticationMWs.inverseAuthenticate, AuthenticationMWs.login, ServerTimingMWs.addServerTiming, @@ -30,25 +30,26 @@ export class UserRouter { } private static addLogout(app: Express): void { - app.post('/api/user/logout', + app.post( + '/api/user/logout', AuthenticationMWs.logout, ServerTimingMWs.addServerTiming, RenderingMWs.renderOK ); } - private static addGetSessionUser(app: Express): void { - app.get('/api/user/me', + app.get( + '/api/user/me', AuthenticationMWs.authenticate, ServerTimingMWs.addServerTiming, RenderingMWs.renderSessionUser ); } - private static addChangePassword(app: Express): void { - app.post('/api/user/:id/password', + app.post( + '/api/user/:id/password', AuthenticationMWs.authenticate, UserRequestConstrainsMWs.forceSelfRequest, UserMWs.changePassword, @@ -57,9 +58,9 @@ export class UserRouter { ); } - private static addCreateUser(app: Express): void { - app.put('/api/user', + app.put( + '/api/user', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), UserMWs.createUser, @@ -69,7 +70,8 @@ export class UserRouter { } private static addDeleteUser(app: Express): void { - app.delete('/api/user/:id', + app.delete( + '/api/user/:id', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), UserRequestConstrainsMWs.notSelfRequest, @@ -79,9 +81,9 @@ export class UserRouter { ); } - private static addListUsers(app: Express): void { - app.get('/api/user/list', + app.get( + '/api/user/list', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), UserMWs.listUsers, @@ -91,7 +93,8 @@ export class UserRouter { } private static addChangeRole(app: Express): void { - app.post('/api/user/:id/role', + app.post( + '/api/user/:id/role', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), UserRequestConstrainsMWs.notSelfRequestOr2Admins, @@ -100,6 +103,4 @@ export class UserRouter { RenderingMWs.renderOK ); } - - } diff --git a/src/backend/routes/admin/AdminRouter.ts b/src/backend/routes/admin/AdminRouter.ts index 73b4ae3d..e6aba414 100644 --- a/src/backend/routes/admin/AdminRouter.ts +++ b/src/backend/routes/admin/AdminRouter.ts @@ -1,19 +1,19 @@ -import {AuthenticationMWs} from '../../middlewares/user/AuthenticationMWs'; -import {UserRoles} from '../../../common/entities/UserDTO'; -import {RenderingMWs} from '../../middlewares/RenderingMWs'; -import {AdminMWs} from '../../middlewares/admin/AdminMWs'; -import {Express} from 'express'; +import { AuthenticationMWs } from '../../middlewares/user/AuthenticationMWs'; +import { UserRoles } from '../../../common/entities/UserDTO'; +import { RenderingMWs } from '../../middlewares/RenderingMWs'; +import { AdminMWs } from '../../middlewares/admin/AdminMWs'; +import { Express } from 'express'; export class AdminRouter { public static route(app: Express): void { - this.addGetStatistic(app); this.addGetDuplicates(app); this.addJobs(app); } private static addGetStatistic(app: Express): void { - app.get('/api/admin/statistic', + app.get( + '/api/admin/statistic', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), AdminMWs.loadStatistic, @@ -22,7 +22,8 @@ export class AdminRouter { } private static addGetDuplicates(app: Express): void { - app.get('/api/admin/duplicates', + app.get( + '/api/admin/duplicates', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), AdminMWs.getDuplicates, @@ -31,31 +32,33 @@ export class AdminRouter { } private static addJobs(app: Express): void { - app.get('/api/admin/jobs/available', + app.get( + '/api/admin/jobs/available', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), AdminMWs.getAvailableJobs, RenderingMWs.renderResult ); - app.get('/api/admin/jobs/scheduled/progress', + app.get( + '/api/admin/jobs/scheduled/progress', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), AdminMWs.getJobProgresses, RenderingMWs.renderResult ); - app.post('/api/admin/jobs/scheduled/:id/start', + app.post( + '/api/admin/jobs/scheduled/:id/start', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), AdminMWs.startJob, RenderingMWs.renderResult ); - app.post('/api/admin/jobs/scheduled/:id/stop', + app.post( + '/api/admin/jobs/scheduled/:id/stop', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), AdminMWs.stopJob, RenderingMWs.renderResult ); } - - } diff --git a/src/backend/routes/admin/SettingsRouter.ts b/src/backend/routes/admin/SettingsRouter.ts index 65623b04..b5138083 100644 --- a/src/backend/routes/admin/SettingsRouter.ts +++ b/src/backend/routes/admin/SettingsRouter.ts @@ -1,128 +1,142 @@ -import {AuthenticationMWs} from '../../middlewares/user/AuthenticationMWs'; -import {UserRoles} from '../../../common/entities/UserDTO'; -import {RenderingMWs} from '../../middlewares/RenderingMWs'; -import {Express} from 'express'; -import {SettingsMWs} from '../../middlewares/admin/SettingsMWs'; +import { AuthenticationMWs } from '../../middlewares/user/AuthenticationMWs'; +import { UserRoles } from '../../../common/entities/UserDTO'; +import { RenderingMWs } from '../../middlewares/RenderingMWs'; +import { Express } from 'express'; +import { SettingsMWs } from '../../middlewares/admin/SettingsMWs'; export class SettingsRouter { public static route(app: Express): void { - this.addSettings(app); } private static addSettings(app: Express): void { - app.get('/api/settings', + app.get( + '/api/settings', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), RenderingMWs.renderConfig ); - - app.put('/api/settings/database', + app.put( + '/api/settings/database', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), SettingsMWs.updateDatabaseSettings, RenderingMWs.renderOK ); - app.put('/api/settings/map', + app.put( + '/api/settings/map', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), SettingsMWs.updateMapSettings, RenderingMWs.renderOK ); - app.put('/api/settings/video', + app.put( + '/api/settings/video', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), SettingsMWs.updateVideoSettings, RenderingMWs.renderOK ); - app.put('/api/settings/photo', + app.put( + '/api/settings/photo', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), SettingsMWs.updatePhotoSettings, RenderingMWs.renderOK ); - app.put('/api/settings/metafile', + app.put( + '/api/settings/metafile', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), SettingsMWs.updateMetaFileSettings, RenderingMWs.renderOK ); - app.put('/api/settings/authentication', + app.put( + '/api/settings/authentication', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), SettingsMWs.updateAuthenticationSettings, RenderingMWs.renderOK ); - app.put('/api/settings/thumbnail', + app.put( + '/api/settings/thumbnail', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), SettingsMWs.updateThumbnailSettings, RenderingMWs.renderOK ); - app.put('/api/settings/search', + app.put( + '/api/settings/search', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), SettingsMWs.updateSearchSettings, RenderingMWs.renderOK ); - app.put('/api/settings/preview', + app.put( + '/api/settings/preview', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), SettingsMWs.updatePreviewSettings, RenderingMWs.renderOK ); - app.put('/api/settings/faces', + app.put( + '/api/settings/faces', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), SettingsMWs.updateFacesSettings, RenderingMWs.renderOK ); - app.put('/api/settings/albums', + app.put( + '/api/settings/albums', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), SettingsMWs.updateAlbumsSettings, RenderingMWs.renderOK ); - app.put('/api/settings/share', + app.put( + '/api/settings/share', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), SettingsMWs.updateShareSettings, RenderingMWs.renderOK ); - app.put('/api/settings/randomPhoto', + app.put( + '/api/settings/randomPhoto', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), SettingsMWs.updateRandomPhotoSettings, RenderingMWs.renderOK ); - app.put('/api/settings/basic', + app.put( + '/api/settings/basic', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), SettingsMWs.updateBasicSettings, RenderingMWs.renderOK ); - app.put('/api/settings/other', + app.put( + '/api/settings/other', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), SettingsMWs.updateOtherSettings, RenderingMWs.renderOK ); - app.put('/api/settings/indexing', + app.put( + '/api/settings/indexing', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), SettingsMWs.updateIndexingSettings, RenderingMWs.renderOK ); - app.put('/api/settings/jobs', + app.put( + '/api/settings/jobs', AuthenticationMWs.authenticate, AuthenticationMWs.authorise(UserRoles.Admin), SettingsMWs.updateJobSettings, RenderingMWs.renderOK ); } - - } diff --git a/src/backend/server.ts b/src/backend/server.ts index 3227fe50..70c46ad9 100644 --- a/src/backend/server.ts +++ b/src/backend/server.ts @@ -1,27 +1,27 @@ -import {Config} from '../common/config/private/Config'; +import { Config } from '../common/config/private/Config'; import * as express from 'express'; -import {Request} from 'express'; +import { Request } from 'express'; import * as cookieParser from 'cookie-parser'; import * as _http from 'http'; -import {Server as HttpServer} from 'http'; +import { Server as HttpServer } from 'http'; // @ts-ignore import * as locale from 'locale'; -import {ObjectManagers} from './model/ObjectManagers'; -import {Logger} from './Logger'; -import {LoggerRouter} from './routes/LoggerRouter'; -import {DiskManager} from './model/DiskManger'; -import {ConfigDiagnostics} from './model/diagnostics/ConfigDiagnostics'; -import {Localizations} from './model/Localizations'; -import {CookieNames} from '../common/CookieNames'; -import {Router} from './routes/Router'; -import {PhotoProcessing} from './model/fileprocessing/PhotoProcessing'; +import { ObjectManagers } from './model/ObjectManagers'; +import { Logger } from './Logger'; +import { LoggerRouter } from './routes/LoggerRouter'; +import { DiskManager } from './model/DiskManger'; +import { ConfigDiagnostics } from './model/diagnostics/ConfigDiagnostics'; +import { Localizations } from './model/Localizations'; +import { CookieNames } from '../common/CookieNames'; +import { Router } from './routes/Router'; +import { PhotoProcessing } from './model/fileprocessing/PhotoProcessing'; import * as _csrf from 'csurf'; import * as unless from 'express-unless'; -import {Event} from '../common/event/Event'; -import {QueryParams} from '../common/QueryParams'; -import {ConfigClassBuilder} from 'typeconfig/node'; -import {ConfigClassOptions} from 'typeconfig/src/decorators/class/IConfigClass'; -import {DatabaseType} from '../common/config/private/PrivateConfig'; +import { Event } from '../common/event/Event'; +import { QueryParams } from '../common/QueryParams'; +import { ConfigClassBuilder } from 'typeconfig/node'; +import { ConfigClassOptions } from 'typeconfig/src/decorators/class/IConfigClass'; +import { DatabaseType } from '../common/config/private/PrivateConfig'; const session = require('cookie-session'); @@ -30,14 +30,16 @@ declare var process: NodeJS.Process; const LOG_TAG = '[server]'; export class Server { - public onStarted = new Event(); private app: express.Express; private server: HttpServer; constructor() { if (!(process.env.NODE_ENV === 'production')) { - Logger.info(LOG_TAG, 'Running in DEBUG mode, set env variable NODE_ENV=production to disable '); + Logger.info( + LOG_TAG, + 'Running in DEBUG mode, set env variable NODE_ENV=production to disable ' + ); } this.init().catch(console.error); } @@ -49,8 +51,15 @@ export class Server { async init(): Promise { Logger.info(LOG_TAG, 'running diagnostics...'); await ConfigDiagnostics.runDiagnostics(); - Logger.verbose(LOG_TAG, 'using config from ' + - (ConfigClassBuilder.attachPrivateInterface(Config).__options as ConfigClassOptions).configPath + ':'); + Logger.verbose( + LOG_TAG, + 'using config from ' + + ( + ConfigClassBuilder.attachPrivateInterface(Config) + .__options as ConfigClassOptions + ).configPath + + ':' + ); Logger.verbose(LOG_TAG, JSON.stringify(Config, null, '\t')); this.app = express(); @@ -59,16 +68,16 @@ export class Server { this.app.set('view engine', 'ejs'); - /** * Session above all */ - this.app.use(session({ - name: CookieNames.session, - keys: Config.Server.sessionSecret - })); - + this.app.use( + session({ + name: CookieNames.session, + keys: Config.Server.sessionSecret, + }) + ); /** * Parse parameters in POST @@ -78,16 +87,28 @@ export class Server { this.app.use(cookieParser()); const csuf: any = _csrf(); csuf.unless = unless; - this.app.use(csuf.unless((req: Request) => { - return Config.Client.authenticationRequired === false || - ['/api/user/login', '/api/user/logout', '/api/share/login'].indexOf(req.originalUrl) !== -1 || - (Config.Client.Sharing.enabled === true && !!req.query[QueryParams.gallery.sharingKey_query]); - })); + this.app.use( + csuf.unless((req: Request) => { + return ( + Config.Client.authenticationRequired === false || + ['/api/user/login', '/api/user/logout', '/api/share/login'].indexOf( + req.originalUrl + ) !== -1 || + (Config.Client.Sharing.enabled === true && + !!req.query[QueryParams.gallery.sharingKey_query]) + ); + }) + ); // enable token generation but do not check it - this.app.post(['/api/user/login', '/api/share/login'], _csrf({ignoreMethods: ['POST']})); - this.app.get(['/api/user/me', '/api/share/:' + QueryParams.gallery.sharingKey_params], _csrf({ignoreMethods: ['GET']})); - + this.app.post( + ['/api/user/login', '/api/share/login'], + _csrf({ ignoreMethods: ['POST'] }) + ); + this.app.get( + ['/api/user/me', '/api/share/:' + QueryParams.gallery.sharingKey_params], + _csrf({ ignoreMethods: ['GET'] }) + ); DiskManager.init(); PhotoProcessing.init(); @@ -102,7 +123,6 @@ export class Server { Router.route(this.app); - // Get PORT from environment and store in Express. this.app.set('port', Config.Server.port); @@ -148,12 +168,10 @@ export class Server { */ private onListening = () => { const addr = this.server.address(); - const bind = typeof addr === 'string' - ? 'pipe ' + addr - : 'port ' + addr.port; + const bind = + typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; Logger.info(LOG_TAG, 'Listening on ' + bind); }; - } diff --git a/src/common/BackendTexts.ts b/src/common/BackendTexts.ts index d767207d..940d4b22 100644 --- a/src/common/BackendTexts.ts +++ b/src/common/BackendTexts.ts @@ -1,6 +1,6 @@ export type backendText = number; export const backendTexts = { - indexedFilesOnly: {name: 10, description: 12}, - sizeToGenerate: {name: 20, description: 22}, - indexChangesOnly: {name: 30, description: 32} + indexedFilesOnly: { name: 10, description: 12 }, + sizeToGenerate: { name: 20, description: 22 }, + indexChangesOnly: { name: 30, description: 32 }, }; diff --git a/src/common/PG2ConfMap.ts b/src/common/PG2ConfMap.ts index 4c05432a..8a503516 100644 --- a/src/common/PG2ConfMap.ts +++ b/src/common/PG2ConfMap.ts @@ -1,6 +1,5 @@ import {SortingMethods} from './entities/SortingMethods'; - /** * This contains the action of the supported list of *.pg2conf files. * These files are passed down to the client as metaFiles (like photos and directories) @@ -13,8 +12,8 @@ export const PG2ConfMap = { '.order_ascending_date.pg2conf': SortingMethods.ascDate, '.order_descending_rating.pg2conf': SortingMethods.descRating, '.order_ascending_rating.pg2conf': SortingMethods.ascRating, - '.order_random.pg2conf': SortingMethods.random - } + '.order_random.pg2conf': SortingMethods.random, + }, }; /** @@ -22,13 +21,12 @@ export const PG2ConfMap = { * do not get passed down to the client or saved to the DB */ - export enum ServerSidePG2ConfAction { // Enum always starts from 1 as !!0 === false - SAVED_SEARCH = 1 + SAVED_SEARCH = 1, } export const ServerPG2ConfMap: { [key: string]: ServerSidePG2ConfAction } = { - '.saved_searches.pg2conf': ServerSidePG2ConfAction.SAVED_SEARCH + '.saved_searches.pg2conf': ServerSidePG2ConfAction.SAVED_SEARCH, }; diff --git a/src/common/QueryParams.ts b/src/common/QueryParams.ts index d1764e21..80a9413c 100644 --- a/src/common/QueryParams.ts +++ b/src/common/QueryParams.ts @@ -7,17 +7,17 @@ export const QueryParams = { fromDate: 'fromDate', toDate: 'toDate', minResolution: 'fromRes', - maxResolution: 'toRes' + maxResolution: 'toRes', }, search: { type: 'type', - query: 'qs' + query: 'qs', }, photo: 'p', sharingKey_query: 'sk', sharingKey_params: 'sharingKey', directory: 'directory', knownLastModified: 'klm', - knownLastScanned: 'kls' - } + knownLastScanned: 'kls', + }, }; diff --git a/src/common/SearchQueryParser.ts b/src/common/SearchQueryParser.ts index 190377e9..883abf18 100644 --- a/src/common/SearchQueryParser.ts +++ b/src/common/SearchQueryParser.ts @@ -17,10 +17,9 @@ import { TextSearch, TextSearchQueryMatchTypes, TextSearchQueryTypes, - ToDateSearch + ToDateSearch, } from './entities/SearchQueryDTO'; -import {Utils} from './Utils'; - +import { Utils } from './Utils'; export interface QueryKeywords { portrait: string; @@ -46,7 +45,6 @@ export interface QueryKeywords { position: string; } - export const defaultQueryKeywords: QueryKeywords = { NSomeOf: 'of', and: 'and', @@ -70,14 +68,16 @@ export const defaultQueryKeywords: QueryKeywords = { portrait: 'portrait', position: 'position', someOf: 'some-of', - kmFrom: 'km-from' + kmFrom: 'km-from', }; export class SearchQueryParser { - constructor(private keywords: QueryKeywords = defaultQueryKeywords) { - } + constructor(private keywords: QueryKeywords = defaultQueryKeywords) {} - public static stringifyText(text: string, matchType = TextSearchQueryMatchTypes.like): string { + public static stringifyText( + text: string, + matchType = TextSearchQueryMatchTypes.like + ): string { if (matchType === TextSearchQueryMatchTypes.exact_match) { return '"' + text + '"'; } @@ -101,7 +101,10 @@ export class SearchQueryParser { if (text.charAt(0) === '"' || text.charAt(0) === '(') { text = text.substring(1); } - if (text.charAt(text.length - 1) === '"' || text.charAt(text.length - 1) === ')') { + if ( + text.charAt(text.length - 1) === '"' || + text.charAt(text.length - 1) === ')' + ) { text = text.substring(0, text.length - 1); } // it is the year only @@ -111,12 +114,11 @@ export class SearchQueryParser { let timestamp = null; // Parsing ISO string try { - const parts = text.split('-').map(t => parseInt(t, 10)); + const parts = text.split('-').map((t) => parseInt(t, 10)); if (parts && parts.length === 3) { - timestamp = (Date.UTC(parts[0], parts[1] - 1, parts[2])); // Note: months are 0-based + timestamp = Date.UTC(parts[0], parts[1] - 1, parts[2]); // Note: months are 0-based } - } catch (e) { - } + } catch (e) {} // If it could not parse as ISO string, try our luck with Date.parse // https://stackoverflow.com/questions/2587345/why-does-date-parse-give-incorrect-results if (timestamp === null) { @@ -130,8 +132,10 @@ export class SearchQueryParser { } public parse(str: string, implicitAND = true): SearchQueryDTO { - str = str.replace(/\s\s+/g, ' ') // remove double spaces - .replace(/:\s+/g, ':').trim(); + str = str + .replace(/\s\s+/g, ' ') // remove double spaces + .replace(/:\s+/g, ':') + .trim(); if (str.charAt(0) === '(' && str.charAt(str.length - 1) === ')') { str = str.slice(1, str.length - 1); @@ -153,9 +157,11 @@ export class SearchQueryParser { continue; } - if (quotationMark === false && + if ( + quotationMark === false && bracketIn.length === 0 && - str.charAt(i) === ' ') { + str.charAt(i) === ' ' + ) { return i; } } @@ -167,42 +173,67 @@ export class SearchQueryParser { if (tokenEnd !== str.length - 1) { if (str.startsWith(' ' + this.keywords.and, tokenEnd)) { - const rest = this.parse(str.slice(tokenEnd + (' ' + this.keywords.and).length), implicitAND); + const rest = this.parse( + str.slice(tokenEnd + (' ' + this.keywords.and).length), + implicitAND + ); return { type: SearchQueryTypes.AND, - list: [this.parse(str.slice(0, tokenEnd), implicitAND), // trim brackets - ...(rest.type === SearchQueryTypes.AND ? (rest as SearchListQuery).list : [rest])] + list: [ + this.parse(str.slice(0, tokenEnd), implicitAND), // trim brackets + ...(rest.type === SearchQueryTypes.AND + ? (rest as SearchListQuery).list + : [rest]), + ], } as ANDSearchQuery; } else if (str.startsWith(' ' + this.keywords.or, tokenEnd)) { - const rest = this.parse(str.slice(tokenEnd + (' ' + this.keywords.or).length), implicitAND); + const rest = this.parse( + str.slice(tokenEnd + (' ' + this.keywords.or).length), + implicitAND + ); return { type: SearchQueryTypes.OR, - list: [this.parse(str.slice(0, tokenEnd), implicitAND), // trim brackets - ...(rest.type === SearchQueryTypes.OR ? (rest as SearchListQuery).list : [rest])] + list: [ + this.parse(str.slice(0, tokenEnd), implicitAND), // trim brackets + ...(rest.type === SearchQueryTypes.OR + ? (rest as SearchListQuery).list + : [rest]), + ], } as ORSearchQuery; - } else { // Relation cannot be detected - const t = implicitAND === true ? SearchQueryTypes.AND : SearchQueryTypes.UNKNOWN_RELATION; + } else { + // Relation cannot be detected + const t = + implicitAND === true + ? SearchQueryTypes.AND + : SearchQueryTypes.UNKNOWN_RELATION; const rest = this.parse(str.slice(tokenEnd), implicitAND); return { type: t, - list: [this.parse(str.slice(0, tokenEnd), implicitAND), // trim brackets - ...(rest.type === t ? (rest as SearchListQuery).list : [rest])] + list: [ + this.parse(str.slice(0, tokenEnd), implicitAND), // trim brackets + ...(rest.type === t ? (rest as SearchListQuery).list : [rest]), + ], } as SearchListQuery; } } - if (str.startsWith(this.keywords.someOf + ':') || - new RegExp('^\\d*-' + this.keywords.NSomeOf + ':').test(str)) { - const prefix = str.startsWith(this.keywords.someOf + ':') ? - this.keywords.someOf + ':' : - new RegExp('^\\d*-' + this.keywords.NSomeOf + ':').exec(str)[0]; + if ( + str.startsWith(this.keywords.someOf + ':') || + new RegExp('^\\d*-' + this.keywords.NSomeOf + ':').test(str) + ) { + const prefix = str.startsWith(this.keywords.someOf + ':') + ? this.keywords.someOf + ':' + : new RegExp('^\\d*-' + this.keywords.NSomeOf + ':').exec(str)[0]; let tmpList: any = this.parse(str.slice(prefix.length + 1, -1), false); // trim brackets const unfoldList = (q: SearchListQuery): SearchQueryDTO[] => { if (q.list) { if (q.type === SearchQueryTypes.UNKNOWN_RELATION) { - return [].concat.apply([], q.list.map(e => unfoldList(e as any))); // flatten array + return [].concat.apply( + [], + q.list.map((e) => unfoldList(e as any)) + ); // flatten array } else { - q.list.forEach(e => unfoldList(e as any)); + q.list.forEach((e) => unfoldList(e as any)); } } return [q]; @@ -210,7 +241,7 @@ export class SearchQueryParser { tmpList = unfoldList(tmpList as SearchListQuery); const ret = { type: SearchQueryTypes.SOME_OF, - list: tmpList + list: tmpList, } as SomeOfSearchQuery; if (new RegExp('^\\d*-' + this.keywords.NSomeOf + ':').test(str)) { ret.min = parseInt(new RegExp(/^\d*/).exec(str)[0], 10); @@ -226,14 +257,14 @@ export class SearchQueryParser { return { type: SearchQueryTypes.from_date, value: SearchQueryParser.parseDate(str.substring(str.indexOf(':') + 1)), - ...(str.startsWith(this.keywords.from + '!:') && {negate: true}) // only add if the value is true + ...(str.startsWith(this.keywords.from + '!:') && { negate: true }), // only add if the value is true } as FromDateSearch; } if (kwStartsWith(str, this.keywords.to)) { return { type: SearchQueryTypes.to_date, value: SearchQueryParser.parseDate(str.substring(str.indexOf(':') + 1)), - ...(str.startsWith(this.keywords.to + '!:') && {negate: true})// only add if the value is true + ...(str.startsWith(this.keywords.to + '!:') && { negate: true }), // only add if the value is true } as ToDateSearch; } @@ -241,68 +272,88 @@ export class SearchQueryParser { return { type: SearchQueryTypes.min_rating, value: parseInt(str.substring(str.indexOf(':') + 1), 10), - ...(str.startsWith(this.keywords.minRating + '!:') && {negate: true}) // only add if the value is true + ...(str.startsWith(this.keywords.minRating + '!:') && { negate: true }), // only add if the value is true } as MinRatingSearch; } if (kwStartsWith(str, this.keywords.maxRating)) { return { type: SearchQueryTypes.max_rating, value: parseInt(str.substring(str.indexOf(':') + 1), 10), - ...(str.startsWith(this.keywords.maxRating + '!:') && {negate: true}) // only add if the value is true + ...(str.startsWith(this.keywords.maxRating + '!:') && { negate: true }), // only add if the value is true } as MaxRatingSearch; } if (kwStartsWith(str, this.keywords.minResolution)) { return { type: SearchQueryTypes.min_resolution, value: parseInt(str.substring(str.indexOf(':') + 1), 10), - ...(str.startsWith(this.keywords.minResolution + '!:') && {negate: true}) // only add if the value is true + ...(str.startsWith(this.keywords.minResolution + '!:') && { + negate: true, + }), // only add if the value is true } as MinResolutionSearch; } if (kwStartsWith(str, this.keywords.maxResolution)) { return { type: SearchQueryTypes.max_resolution, value: parseInt(str.substring(str.indexOf(':') + 1), 10), - ...(str.startsWith(this.keywords.maxResolution + '!:') && {negate: true}) // only add if the value is true + ...(str.startsWith(this.keywords.maxResolution + '!:') && { + negate: true, + }), // only add if the value is true } as MaxResolutionSearch; } if (new RegExp('^\\d*-' + this.keywords.kmFrom + '!?:').test(str)) { - let from = str.slice(new RegExp('^\\d*-' + this.keywords.kmFrom + '!?:').exec(str)[0].length); - if ((from.charAt(0) === '(' && from.charAt(from.length - 1) === ')') || - (from.charAt(0) === '"' && from.charAt(from.length - 1) === '"')) { + let from = str.slice( + new RegExp('^\\d*-' + this.keywords.kmFrom + '!?:').exec(str)[0].length + ); + if ( + (from.charAt(0) === '(' && from.charAt(from.length - 1) === ')') || + (from.charAt(0) === '"' && from.charAt(from.length - 1) === '"') + ) { from = from.slice(1, from.length - 1); } return { type: SearchQueryTypes.distance, distance: parseInt(new RegExp(/^\d*/).exec(str)[0], 10), - from: {text: from}, - ...(new RegExp('^\\d*-' + this.keywords.kmFrom + '!:').test(str) && {negate: true}) // only add if the value is true + from: { text: from }, + ...(new RegExp('^\\d*-' + this.keywords.kmFrom + '!:').test(str) && { + negate: true, + }), // only add if the value is true } as DistanceSearch; } if (str.startsWith(this.keywords.orientation + ':')) { return { type: SearchQueryTypes.orientation, - landscape: str.slice((this.keywords.orientation + ':').length) === this.keywords.landscape + landscape: + str.slice((this.keywords.orientation + ':').length) === + this.keywords.landscape, } as OrientationSearch; } // parse text search - const tmp = TextSearchQueryTypes.map(type => ({ + const tmp = TextSearchQueryTypes.map((type) => ({ key: (this.keywords as any)[SearchQueryTypes[type]] + ':', - queryTemplate: {type, text: ''} as TextSearch - })).concat(TextSearchQueryTypes.map(type => ({ - key: (this.keywords as any)[SearchQueryTypes[type]] + '!:', - queryTemplate: {type, text: '', negate: true} as TextSearch - }))); + queryTemplate: { type, text: '' } as TextSearch, + })).concat( + TextSearchQueryTypes.map((type) => ({ + key: (this.keywords as any)[SearchQueryTypes[type]] + '!:', + queryTemplate: { type, text: '', negate: true } as TextSearch, + })) + ); for (const typeTmp of tmp) { if (str.startsWith(typeTmp.key)) { const ret: TextSearch = Utils.clone(typeTmp.queryTemplate); // exact match - if (str.charAt(typeTmp.key.length) === '"' && str.charAt(str.length - 1) === '"') { + if ( + str.charAt(typeTmp.key.length) === '"' && + str.charAt(str.length - 1) === '"' + ) { ret.text = str.slice(typeTmp.key.length + 1, str.length - 1); ret.matchType = TextSearchQueryMatchTypes.exact_match; // like match - } else if (str.charAt(typeTmp.key.length) === '(' && str.charAt(str.length - 1) === ')') { + } else if ( + str.charAt(typeTmp.key.length) === '(' && + str.charAt(str.length - 1) === ')' + ) { ret.text = str.slice(typeTmp.key.length + 1, str.length - 1); } else { ret.text = str.slice(typeTmp.key.length); @@ -311,11 +362,9 @@ export class SearchQueryParser { } } - - return {type: SearchQueryTypes.any_text, text: str} as TextSearch; + return { type: SearchQueryTypes.any_text, text: str } as TextSearch; } - public stringify(query: SearchQueryDTO): string { const ret = this.stringifyOnEntry(query); if (ret.charAt(0) === '(' && ret.charAt(ret.length - 1) === ')') { @@ -331,56 +380,139 @@ export class SearchQueryParser { const colon = (query as NegatableSearchQuery).negate === true ? '!:' : ':'; switch (query.type) { case SearchQueryTypes.AND: - return '(' + (query as SearchListQuery).list.map(q => this.stringifyOnEntry(q)).join(' ' + this.keywords.and + ' ') + ')'; + return ( + '(' + + (query as SearchListQuery).list + .map((q) => this.stringifyOnEntry(q)) + .join(' ' + this.keywords.and + ' ') + + ')' + ); case SearchQueryTypes.OR: - return '(' + (query as SearchListQuery).list.map(q => this.stringifyOnEntry(q)).join(' ' + this.keywords.or + ' ') + ')'; + return ( + '(' + + (query as SearchListQuery).list + .map((q) => this.stringifyOnEntry(q)) + .join(' ' + this.keywords.or + ' ') + + ')' + ); case SearchQueryTypes.SOME_OF: if ((query as SomeOfSearchQuery).min) { - return (query as SomeOfSearchQuery).min + '-' + this.keywords.NSomeOf + ':(' + - (query as SearchListQuery).list.map(q => this.stringifyOnEntry(q)).join(' ') + ')'; + return ( + (query as SomeOfSearchQuery).min + + '-' + + this.keywords.NSomeOf + + ':(' + + (query as SearchListQuery).list + .map((q) => this.stringifyOnEntry(q)) + .join(' ') + + ')' + ); } - return this.keywords.someOf + ':(' + - (query as SearchListQuery).list.map(q => this.stringifyOnEntry(q)).join(' ') + ')'; - + return ( + this.keywords.someOf + + ':(' + + (query as SearchListQuery).list + .map((q) => this.stringifyOnEntry(q)) + .join(' ') + + ')' + ); case SearchQueryTypes.orientation: - return this.keywords.orientation + ':' + - ((query as OrientationSearch).landscape ? this.keywords.landscape : this.keywords.portrait); + return ( + this.keywords.orientation + + ':' + + ((query as OrientationSearch).landscape + ? this.keywords.landscape + : this.keywords.portrait) + ); case SearchQueryTypes.from_date: if (!(query as FromDateSearch).value) { return ''; } - return this.keywords.from + colon + - SearchQueryParser.stringifyDate((query as FromDateSearch).value); + return ( + this.keywords.from + + colon + + SearchQueryParser.stringifyDate((query as FromDateSearch).value) + ); case SearchQueryTypes.to_date: if (!(query as ToDateSearch).value) { return ''; } - return this.keywords.to + colon + - SearchQueryParser.stringifyDate((query as ToDateSearch).value); + return ( + this.keywords.to + + colon + + SearchQueryParser.stringifyDate((query as ToDateSearch).value) + ); case SearchQueryTypes.min_rating: - return this.keywords.minRating + colon + (isNaN((query as RangeSearch).value) ? '' : (query as RangeSearch).value); + return ( + this.keywords.minRating + + colon + + (isNaN((query as RangeSearch).value) + ? '' + : (query as RangeSearch).value) + ); case SearchQueryTypes.max_rating: - return this.keywords.maxRating + colon + (isNaN((query as RangeSearch).value) ? '' : (query as RangeSearch).value); + return ( + this.keywords.maxRating + + colon + + (isNaN((query as RangeSearch).value) + ? '' + : (query as RangeSearch).value) + ); case SearchQueryTypes.min_resolution: - return this.keywords.minResolution + colon + (isNaN((query as RangeSearch).value) ? '' : (query as RangeSearch).value); + return ( + this.keywords.minResolution + + colon + + (isNaN((query as RangeSearch).value) + ? '' + : (query as RangeSearch).value) + ); case SearchQueryTypes.max_resolution: - return this.keywords.maxResolution + colon + (isNaN((query as RangeSearch).value) ? '' : (query as RangeSearch).value); + return ( + this.keywords.maxResolution + + colon + + (isNaN((query as RangeSearch).value) + ? '' + : (query as RangeSearch).value) + ); case SearchQueryTypes.distance: if ((query as DistanceSearch).from.text.indexOf(' ') !== -1) { - return (query as DistanceSearch).distance + '-' + this.keywords.kmFrom + colon + '(' + (query as DistanceSearch).from.text + ')'; + return ( + (query as DistanceSearch).distance + + '-' + + this.keywords.kmFrom + + colon + + '(' + + (query as DistanceSearch).from.text + + ')' + ); } - return (query as DistanceSearch).distance + '-' + this.keywords.kmFrom + colon + (query as DistanceSearch).from.text; + return ( + (query as DistanceSearch).distance + + '-' + + this.keywords.kmFrom + + colon + + (query as DistanceSearch).from.text + ); case SearchQueryTypes.any_text: if (!(query as TextSearch).negate) { - return SearchQueryParser.stringifyText((query as TextSearch).text, (query as TextSearch).matchType); + return SearchQueryParser.stringifyText( + (query as TextSearch).text, + (query as TextSearch).matchType + ); } else { - return (this.keywords as any)[SearchQueryTypes[query.type]] + colon + - SearchQueryParser.stringifyText((query as TextSearch).text, (query as TextSearch).matchType); + return ( + (this.keywords as any)[SearchQueryTypes[query.type]] + + colon + + SearchQueryParser.stringifyText( + (query as TextSearch).text, + (query as TextSearch).matchType + ) + ); } case SearchQueryTypes.person: @@ -392,8 +524,14 @@ export class SearchQueryParser { if (!(query as TextSearch).text) { return ''; } - return (this.keywords as any)[SearchQueryTypes[query.type]] + colon + - SearchQueryParser.stringifyText((query as TextSearch).text, (query as TextSearch).matchType); + return ( + (this.keywords as any)[SearchQueryTypes[query.type]] + + colon + + SearchQueryParser.stringifyText( + (query as TextSearch).text, + (query as TextSearch).matchType + ) + ); default: throw new Error('Unknown type: ' + query.type); diff --git a/src/common/SupportedFormats.ts b/src/common/SupportedFormats.ts index 9642899f..e00db7ad 100644 --- a/src/common/SupportedFormats.ts +++ b/src/common/SupportedFormats.ts @@ -1,29 +1,29 @@ export const SupportedFormats = { - Photos: [ - 'gif', - 'jpeg', 'jpg', 'jpe', - 'png', - 'webp', - 'svg' - ], + Photos: ['gif', 'jpeg', 'jpg', 'jpe', 'png', 'webp', 'svg'], // Browser supported video formats // Read more: https://www.w3schools.com/html/html5_video.asp - Videos: [ - 'mp4', - 'webm', - 'ogv', - 'ogg' - ], - MetaFiles: [ - 'gpx', 'pg2conf', 'md' - ], + Videos: ['mp4', 'webm', 'ogv', 'ogg'], + MetaFiles: ['gpx', 'pg2conf', 'md'], // These formats need to be transcoded (with the build-in ffmpeg support) TranscodeNeed: { // based on libvips, all supported formats for sharp: https://github.com/libvips/libvips Photos: [] as string[], Videos: [ - 'avi', 'mkv', 'mov', 'wmv', 'flv', 'mts', 'm2ts', 'mpg', '3gp', 'm4v', 'mpeg', 'vob', - 'divx', 'xvid', 'ts' + 'avi', + 'mkv', + 'mov', + 'wmv', + 'flv', + 'mts', + 'm2ts', + 'mpg', + '3gp', + 'm4v', + 'mpeg', + 'vob', + 'divx', + 'xvid', + 'ts', ], }, // -------------------------------------------- @@ -35,15 +35,23 @@ export const SupportedFormats = { TranscodeNeed: { Photos: [] as string[], Videos: [] as string[], - } - } + }, + }, }; -SupportedFormats.Photos = SupportedFormats.Photos.concat(SupportedFormats.TranscodeNeed.Photos); -SupportedFormats.Videos = SupportedFormats.Videos.concat(SupportedFormats.TranscodeNeed.Videos); -SupportedFormats.WithDots.Photos = SupportedFormats.Photos.map(f => '.' + f); -SupportedFormats.WithDots.Videos = SupportedFormats.Videos.map(f => '.' + f); -SupportedFormats.WithDots.MetaFiles = SupportedFormats.MetaFiles.map(f => '.' + f); -SupportedFormats.WithDots.TranscodeNeed.Photos = SupportedFormats.TranscodeNeed.Photos.map(f => '.' + f); -SupportedFormats.WithDots.TranscodeNeed.Videos = SupportedFormats.TranscodeNeed.Videos.map(f => '.' + f); +SupportedFormats.Photos = SupportedFormats.Photos.concat( + SupportedFormats.TranscodeNeed.Photos +); +SupportedFormats.Videos = SupportedFormats.Videos.concat( + SupportedFormats.TranscodeNeed.Videos +); +SupportedFormats.WithDots.Photos = SupportedFormats.Photos.map((f) => '.' + f); +SupportedFormats.WithDots.Videos = SupportedFormats.Videos.map((f) => '.' + f); +SupportedFormats.WithDots.MetaFiles = SupportedFormats.MetaFiles.map( + (f) => '.' + f +); +SupportedFormats.WithDots.TranscodeNeed.Photos = + SupportedFormats.TranscodeNeed.Photos.map((f) => '.' + f); +SupportedFormats.WithDots.TranscodeNeed.Videos = + SupportedFormats.TranscodeNeed.Videos.map((f) => '.' + f); diff --git a/src/common/Utils.ts b/src/common/Utils.ts index 6426eb23..5454dde0 100644 --- a/src/common/Utils.ts +++ b/src/common/Utils.ts @@ -1,8 +1,9 @@ export class Utils { static GUID(): string { - const s4 = (): string => Math.floor((1 + Math.random()) * 0x10000) - .toString(16) - .substring(1); + const s4 = (): string => + Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); return s4() + s4() + '-' + s4() + s4(); } @@ -21,7 +22,6 @@ export class Utils { }); } - static removeNullOrEmptyObj(obj: T): T { if (typeof obj !== 'object' || obj == null) { return obj; @@ -81,7 +81,6 @@ export class Utils { } } else if (object[key] !== filter[key]) { return false; - } } @@ -98,7 +97,6 @@ export class Utils { return size.toFixed(2) + postFixes[index]; } - static createRange(from: number, to: number): Array { const arr = new Array(to - from + 1); let c = to - from + 1; @@ -174,46 +172,39 @@ export class Utils { } public static enumToArray(EnumType: any): { key: number; value: string }[] { - const arr: Array<{ key: number; value: string; }> = []; + const arr: Array<{ key: number; value: string }> = []; for (const enumMember in EnumType) { if (!EnumType.hasOwnProperty(enumMember)) { continue; } const key = parseInt(enumMember, 10); if (key >= 0) { - arr.push({key, value: EnumType[enumMember]}); + arr.push({ key, value: EnumType[enumMember] }); } } return arr; } - public static findClosest(num: number, arr: number[]): number { - let curr = arr[0]; let diff = Math.abs(num - curr); arr.forEach((value): void => { - const newDiff = Math.abs(num - value); if (newDiff < diff) { diff = newDiff; curr = value; } - }); return curr; } - public static findClosestinSorted(num: number, arr: number[]): number { - let curr = arr[0]; let diff = Math.abs(num - curr); for (const item of arr) { - const newDiff = Math.abs(num - item); if (newDiff > diff) { break; @@ -222,7 +213,6 @@ export class Utils { curr = item; } - return curr; } @@ -239,8 +229,11 @@ export class Utils { public static isFloat32(value: number): boolean { const E = Math.pow(10, 38); const nE = Math.pow(10, -38); - return !isNaN(value) && ((value >= -3.402823466 * E && value <= -1.175494351 * nE) || - (value <= 3.402823466 * E && value >= 1.175494351 * nE)); + return ( + !isNaN(value) && + ((value >= -3.402823466 * E && value <= -1.175494351 * nE) || + (value <= 3.402823466 * E && value >= 1.175494351 * nE)) + ); } public static getAnyX(num: number, arr: any[], start = 0): any[][] { @@ -262,18 +255,15 @@ export class Utils { } return ret; } - } export class LRU { - data: { [key: string]: { value: V, usage: number } } = {}; + data: { [key: string]: { value: V; usage: number } } = {}; - - constructor(public readonly size: number) { - } + constructor(public readonly size: number) {} set(key: string, value: V): void { - this.data[key] = {usage: Date.now(), value}; + this.data[key] = { usage: Date.now(), value }; if (Object.keys(this.data).length > this.size) { let oldestK = key; let oldest = this.data[oldestK].usage; @@ -293,5 +283,4 @@ export class LRU { } return this.data[key].value; } - } diff --git a/src/common/config/private/Config.ts b/src/common/config/private/Config.ts index 6a3f4aaa..71494b7e 100644 --- a/src/common/config/private/Config.ts +++ b/src/common/config/private/Config.ts @@ -1,14 +1,13 @@ -import {IPrivateConfig, ServerConfig} from './PrivateConfig'; -import {ClientConfig} from '../public/ClientConfig'; +import { IPrivateConfig, ServerConfig } from './PrivateConfig'; +import { ClientConfig } from '../public/ClientConfig'; import * as crypto from 'crypto'; import * as path from 'path'; -import {ConfigClass, ConfigClassBuilder} from 'typeconfig/node'; -import {ConfigProperty, IConfigClass} from 'typeconfig/common'; - +import { ConfigClass, ConfigClassBuilder } from 'typeconfig/node'; +import { ConfigProperty, IConfigClass } from 'typeconfig/common'; declare const process: any; -const upTime = (new Date()).toISOString(); +const upTime = new Date().toISOString(); @ConfigClass({ configPath: path.join(__dirname, './../../../../config.json'), @@ -25,41 +24,47 @@ const upTime = (new Date()).toISOString(); rewriteENVConfig: true, enumsAsString: true, saveIfNotExist: true, - exitOnConfig: true + exitOnConfig: true, }, defaults: { - enabled: true - } - } + enabled: true, + }, + }, }) export class PrivateConfigClass implements IPrivateConfig { - @ConfigProperty({type: ServerConfig}) + @ConfigProperty({ type: ServerConfig }) Server: ServerConfig = new ServerConfig(); - @ConfigProperty({type: ClientConfig}) - Client: IConfigClass & ClientConfig = new ClientConfig() as (IConfigClass & ClientConfig); + @ConfigProperty({ type: ClientConfig }) + Client: IConfigClass & ClientConfig = new ClientConfig() as IConfigClass & + ClientConfig; constructor() { if (!this.Server.sessionSecret || this.Server.sessionSecret.length === 0) { - this.Server.sessionSecret = [crypto.randomBytes(256).toString('hex'), + this.Server.sessionSecret = [ crypto.randomBytes(256).toString('hex'), - crypto.randomBytes(256).toString('hex')]; + crypto.randomBytes(256).toString('hex'), + crypto.randomBytes(256).toString('hex'), + ]; } - this.Server.Environment.appVersion = require('../../../../package.json').version; - this.Server.Environment.buildTime = require('../../../../package.json').buildTime; - this.Server.Environment.buildCommitHash = require('../../../../package.json').buildCommitHash; + this.Server.Environment.appVersion = + require('../../../../package.json').version; + this.Server.Environment.buildTime = + require('../../../../package.json').buildTime; + this.Server.Environment.buildCommitHash = + require('../../../../package.json').buildCommitHash; this.Server.Environment.upTime = upTime; this.Server.Environment.isDocker = !!process.env.PI_DOCKER; } - async original(): Promise { const pc = ConfigClassBuilder.attachInterface(new PrivateConfigClass()); await pc.load(); return pc; } - } -export const Config = ConfigClassBuilder.attachInterface(new PrivateConfigClass()); +export const Config = ConfigClassBuilder.attachInterface( + new PrivateConfigClass() +); Config.loadSync(); diff --git a/src/common/config/private/PrivateConfig.ts b/src/common/config/private/PrivateConfig.ts index 66a70ebd..3825efd2 100644 --- a/src/common/config/private/PrivateConfig.ts +++ b/src/common/config/private/PrivateConfig.ts @@ -1,56 +1,88 @@ -/* tslint:disable:no-inferrable-types */ +/* eslint-disable @typescript-eslint/no-inferrable-types */ import 'reflect-metadata'; -import {JobScheduleDTO, JobTrigger, JobTriggerType} from '../../entities/job/JobScheduleDTO'; -import {ClientConfig} from '../public/ClientConfig'; -import {SubConfigClass} from 'typeconfig/src/decorators/class/SubConfigClass'; -import {ConfigProperty} from 'typeconfig/src/decorators/property/ConfigPropoerty'; -import {DefaultsJobs} from '../../entities/job/JobDTO'; -import {SearchQueryDTO, SearchQueryTypes, TextSearch} from '../../entities/SearchQueryDTO'; -import {SortingMethods} from '../../entities/SortingMethods'; -import {UserRoles} from '../../entities/UserDTO'; +import { + JobScheduleDTO, + JobTrigger, + JobTriggerType, +} from '../../entities/job/JobScheduleDTO'; +import { ClientConfig } from '../public/ClientConfig'; +import { SubConfigClass } from 'typeconfig/src/decorators/class/SubConfigClass'; +import { ConfigProperty } from 'typeconfig/src/decorators/property/ConfigPropoerty'; +import { DefaultsJobs } from '../../entities/job/JobDTO'; +import { + SearchQueryDTO, + SearchQueryTypes, + TextSearch, +} from '../../entities/SearchQueryDTO'; +import { SortingMethods } from '../../entities/SortingMethods'; +import { UserRoles } from '../../entities/UserDTO'; export enum DatabaseType { - memory = 1, mysql = 2, sqlite = 3 + memory = 1, + mysql = 2, + sqlite = 3, } export enum LogLevel { - error = 1, warn = 2, info = 3, verbose = 4, debug = 5, silly = 6 + error = 1, + warn = 2, + info = 3, + verbose = 4, + debug = 5, + silly = 6, } export enum SQLLogLevel { - none = 1, error = 2, all = 3 + none = 1, + error = 2, + all = 3, } - export enum ReIndexingSensitivity { - low = 1, medium = 2, high = 3 + low = 1, + medium = 2, + high = 3, } export enum FFmpegPresets { - ultrafast = 1, superfast = 2, veryfast = 3, faster = 4, fast = 5, medium = 6, - slow = 7, slower = 8, veryslow = 9, placebo = 10 + ultrafast = 1, + superfast = 2, + veryfast = 3, + faster = 4, + fast = 5, + medium = 6, + slow = 7, + slower = 8, + veryslow = 9, + placebo = 10, } - export type videoCodecType = 'libvpx-vp9' | 'libx264' | 'libvpx' | 'libx265'; -export type videoResolutionType = 240 | 360 | 480 | 720 | 1080 | 1440 | 2160 | 4320; +export type videoResolutionType = + | 240 + | 360 + | 480 + | 720 + | 1080 + | 1440 + | 2160 + | 4320; export type videoFormatType = 'mp4' | 'webm'; @SubConfigClass() export class MySQLConfig { - @ConfigProperty({envAlias: 'MYSQL_HOST'}) + @ConfigProperty({ envAlias: 'MYSQL_HOST' }) host: string = 'localhost'; - @ConfigProperty({envAlias: 'MYSQL_PORT', min: 0, max: 65535}) + @ConfigProperty({ envAlias: 'MYSQL_PORT', min: 0, max: 65535 }) port: number = 3306; - @ConfigProperty({envAlias: 'MYSQL_DATABASE'}) + @ConfigProperty({ envAlias: 'MYSQL_DATABASE' }) database: string = 'pigallery2'; - @ConfigProperty({envAlias: 'MYSQL_USERNAME'}) + @ConfigProperty({ envAlias: 'MYSQL_USERNAME' }) username: string = ''; - @ConfigProperty({envAlias: 'MYSQL_PASSWORD', type: 'password'}) + @ConfigProperty({ envAlias: 'MYSQL_PASSWORD', type: 'password' }) password: string = ''; } - @SubConfigClass() export class SQLiteConfig { @ConfigProperty() @@ -59,19 +91,17 @@ export class SQLiteConfig { @SubConfigClass() export class UserConfig { - @ConfigProperty() name: string; - @ConfigProperty({type: UserRoles}) + @ConfigProperty({ type: UserRoles }) role: UserRoles; - @ConfigProperty({description: 'Unencrypted, temporary password'}) + @ConfigProperty({ description: 'Unencrypted, temporary password' }) password: string; - @ConfigProperty({description: 'Encrypted password'}) - encryptedPassword: string; - + @ConfigProperty({ description: 'Encrypted password' }) + encryptedPassword: string | undefined; constructor(name: string, password: string, role: UserRoles) { this.name = name; @@ -85,11 +115,11 @@ export class ServerDataBaseConfig { @ConfigProperty({ type: DatabaseType, onNewValue: (value, config) => { - if (value === DatabaseType.memory) { + if (config && value === DatabaseType.memory) { config.Client.Search.enabled = false; config.Client.Sharing.enabled = false; } - } + }, }) type: DatabaseType = DatabaseType.sqlite; @@ -104,16 +134,17 @@ export class ServerDataBaseConfig { @ConfigProperty({ arrayType: UserConfig, - description: 'Creates these users in the DB if they do not exist. If a user with this name exist, it wont be overwritten, even if the role is different.' + description: + 'Creates these users in the DB if they do not exist. If a user with this name exist, it wont be overwritten, even if the role is different.', }) enforcedUsers: UserConfig[] = []; } @SubConfigClass() export class ServerThumbnailConfig { - @ConfigProperty({description: 'if true, photos will have better quality.'}) + @ConfigProperty({ description: 'if true, photos will have better quality.' }) qualityPriority: boolean = true; - @ConfigProperty({type: 'ratio'}) + @ConfigProperty({ type: 'ratio' }) personFaceMargin: number = 0.6; // in ration [0-1] } @@ -123,29 +154,36 @@ export class ServerSharingConfig { updateTimeout: number = 1000 * 60 * 5; } - @SubConfigClass() export class ServerIndexingConfig { @ConfigProperty() cachedFolderTimeout: number = 1000 * 60 * 60; // Do not rescans the folder if seems ok - @ConfigProperty({type: ReIndexingSensitivity}) + @ConfigProperty({ type: ReIndexingSensitivity }) reIndexingSensitivity: ReIndexingSensitivity = ReIndexingSensitivity.low; @ConfigProperty({ arrayType: 'string', - description: 'If an entry starts with \'/\' it is treated as an absolute path.' + - ' If it doesn\'t start with \'/\' but contains a \'/\', the path is relative to the image directory.' + - ' If it doesn\'t contain a \'/\', any folder with this name will be excluded.' + description: + "If an entry starts with '/' it is treated as an absolute path." + + " If it doesn't start with '/' but contains a '/', the path is relative to the image directory." + + " If it doesn't contain a '/', any folder with this name will be excluded.", }) excludeFolderList: string[] = ['.Trash-1000', '.dtrash', '$RECYCLE.BIN']; - @ConfigProperty({arrayType: 'string', description: 'Any folder that contains a file with this name will be excluded from indexing.'}) + @ConfigProperty({ + arrayType: 'string', + description: + 'Any folder that contains a file with this name will be excluded from indexing.', + }) excludeFileList: string[] = []; } @SubConfigClass() export class ServerThreadingConfig { - @ConfigProperty({description: 'App can run on multiple thread'}) + @ConfigProperty({ description: 'App can run on multiple thread' }) enabled: boolean = true; - @ConfigProperty({description: 'Number of threads that are used to generate thumbnails. If 0, number of \'CPU cores -1\' threads will be used.'}) + @ConfigProperty({ + description: + "Number of threads that are used to generate thumbnails. If 0, number of 'CPU cores -1' threads will be used.", + }) thumbnailThreads: number = 0; // if zero-> CPU count -1 } @@ -157,57 +195,53 @@ export class ServerDuplicatesConfig { @SubConfigClass() export class ServerLogConfig { - @ConfigProperty({type: LogLevel}) + @ConfigProperty({ type: LogLevel }) level: LogLevel = LogLevel.info; - @ConfigProperty({type: SQLLogLevel}) + @ConfigProperty({ type: SQLLogLevel }) sqlLevel: SQLLogLevel = SQLLogLevel.error; @ConfigProperty() logServerTiming: boolean = false; } - @SubConfigClass() export class NeverJobTrigger implements JobTrigger { - @ConfigProperty({type: JobTriggerType}) + @ConfigProperty({ type: JobTriggerType }) readonly type = JobTriggerType.never; } @SubConfigClass() export class ScheduledJobTrigger implements JobTrigger { - @ConfigProperty({type: JobTriggerType}) + @ConfigProperty({ type: JobTriggerType }) readonly type = JobTriggerType.scheduled; - @ConfigProperty({type: 'unsignedInt'}) - time: number; // data time + @ConfigProperty({ type: 'unsignedInt' }) + time: number; // data time } @SubConfigClass() export class PeriodicJobTrigger implements JobTrigger { - @ConfigProperty({type: JobTriggerType}) + @ConfigProperty({ type: JobTriggerType }) readonly type = JobTriggerType.periodic; - @ConfigProperty({type: 'unsignedInt', max: 7}) - periodicity: number; // 0-6: week days 7 every day - @ConfigProperty({type: 'unsignedInt', max: 23 * 60 + 59}) - atTime: number; // day time + @ConfigProperty({ type: 'unsignedInt', max: 7 }) + periodicity: number | undefined; // 0-6: week days 7 every day + @ConfigProperty({ type: 'unsignedInt', max: 23 * 60 + 59 }) + atTime: number | undefined; // day time } @SubConfigClass() export class AfterJobTrigger implements JobTrigger { - - @ConfigProperty({type: JobTriggerType}) + @ConfigProperty({ type: JobTriggerType }) readonly type = JobTriggerType.after; @ConfigProperty() - afterScheduleName: string; // runs after schedule + afterScheduleName: string | undefined; // runs after schedule constructor(afterScheduleName?: string) { this.afterScheduleName = afterScheduleName; } } - @SubConfigClass() export class JobScheduleConfig implements JobScheduleDTO { - @ConfigProperty() name: string; @ConfigProperty() @@ -231,12 +265,25 @@ export class JobScheduleConfig implements JobScheduleDTO { return PeriodicJobTrigger; } return null; - } + }, }) - trigger: AfterJobTrigger | NeverJobTrigger | PeriodicJobTrigger | ScheduledJobTrigger; + trigger: + | AfterJobTrigger + | NeverJobTrigger + | PeriodicJobTrigger + | ScheduledJobTrigger; - constructor(name: string, jobName: string, allowParallelRun: boolean, - trigger: AfterJobTrigger | NeverJobTrigger | PeriodicJobTrigger | ScheduledJobTrigger, config: any) { + constructor( + name: string, + jobName: string, + allowParallelRun: boolean, + trigger: + | AfterJobTrigger + | NeverJobTrigger + | PeriodicJobTrigger + | ScheduledJobTrigger, + config: any + ) { this.name = name; this.jobName = jobName; this.config = config; @@ -247,51 +294,62 @@ export class JobScheduleConfig implements JobScheduleDTO { @SubConfigClass() export class ServerJobConfig { - @ConfigProperty({type: 'integer', description: 'Job history size'}) + @ConfigProperty({ type: 'integer', description: 'Job history size' }) maxSavedProgress: number = 10; - @ConfigProperty({arrayType: JobScheduleConfig}) + @ConfigProperty({ arrayType: JobScheduleConfig }) scheduled: JobScheduleConfig[] = [ - new JobScheduleConfig(DefaultsJobs[DefaultsJobs.Indexing], + new JobScheduleConfig( + DefaultsJobs[DefaultsJobs.Indexing], DefaultsJobs[DefaultsJobs.Indexing], false, - new NeverJobTrigger(), {indexChangesOnly: true} + new NeverJobTrigger(), + { indexChangesOnly: true } ), - new JobScheduleConfig(DefaultsJobs[DefaultsJobs['Preview Filling']], + new JobScheduleConfig( + DefaultsJobs[DefaultsJobs['Preview Filling']], DefaultsJobs[DefaultsJobs['Preview Filling']], false, - new NeverJobTrigger(), {} + new NeverJobTrigger(), + {} ), - new JobScheduleConfig(DefaultsJobs[DefaultsJobs['Thumbnail Generation']], + new JobScheduleConfig( + DefaultsJobs[DefaultsJobs['Thumbnail Generation']], DefaultsJobs[DefaultsJobs['Thumbnail Generation']], false, - new AfterJobTrigger(DefaultsJobs[DefaultsJobs['Preview Filling']]), {sizes: [240], indexedOnly: true} + new AfterJobTrigger(DefaultsJobs[DefaultsJobs['Preview Filling']]), + { sizes: [240], indexedOnly: true } ), - new JobScheduleConfig(DefaultsJobs[DefaultsJobs['Photo Converting']], + new JobScheduleConfig( + DefaultsJobs[DefaultsJobs['Photo Converting']], DefaultsJobs[DefaultsJobs['Photo Converting']], false, - new AfterJobTrigger(DefaultsJobs[DefaultsJobs['Thumbnail Generation']]), {indexedOnly: true} + new AfterJobTrigger(DefaultsJobs[DefaultsJobs['Thumbnail Generation']]), + { indexedOnly: true } ), - new JobScheduleConfig(DefaultsJobs[DefaultsJobs['Video Converting']], + new JobScheduleConfig( + DefaultsJobs[DefaultsJobs['Video Converting']], DefaultsJobs[DefaultsJobs['Video Converting']], false, - new AfterJobTrigger(DefaultsJobs[DefaultsJobs['Photo Converting']]), {indexedOnly: true} + new AfterJobTrigger(DefaultsJobs[DefaultsJobs['Photo Converting']]), + { indexedOnly: true } ), - new JobScheduleConfig(DefaultsJobs[DefaultsJobs['Temp Folder Cleaning']], + new JobScheduleConfig( + DefaultsJobs[DefaultsJobs['Temp Folder Cleaning']], DefaultsJobs[DefaultsJobs['Temp Folder Cleaning']], false, - new AfterJobTrigger(DefaultsJobs[DefaultsJobs['Video Converting']]), {indexedOnly: true} + new AfterJobTrigger(DefaultsJobs[DefaultsJobs['Video Converting']]), + { indexedOnly: true } ), ]; } - @SubConfigClass() export class VideoTranscodingConfig { - @ConfigProperty({type: 'unsignedInt'}) + @ConfigProperty({ type: 'unsignedInt' }) bitRate: number = 5 * 1024 * 1024; - @ConfigProperty({type: 'unsignedInt'}) + @ConfigProperty({ type: 'unsignedInt' }) resolution: videoResolutionType = 720; - @ConfigProperty({type: 'positiveFloat'}) + @ConfigProperty({ type: 'positiveFloat' }) fps: number = 25; @ConfigProperty() codec: videoCodecType = 'libx264'; @@ -299,17 +357,22 @@ export class VideoTranscodingConfig { format: videoFormatType = 'mp4'; @ConfigProperty({ type: 'unsignedInt', - description: 'Constant Rate Factor. The range of the CRF scale is 0–51, where 0 is lossless, 23 is the default, and 51 is worst quality possible.', - max: 51 + description: + 'Constant Rate Factor. The range of the CRF scale is 0–51, where 0 is lossless, 23 is the default, and 51 is worst quality possible.', + max: 51, }) crf: number = 23; @ConfigProperty({ type: FFmpegPresets, - description: 'A preset is a collection of options that will provide a certain encoding speed to compression ratio' + description: + 'A preset is a collection of options that will provide a certain encoding speed to compression ratio', }) preset: FFmpegPresets = FFmpegPresets.medium; - @ConfigProperty({arrayType: 'string', description: 'It will be sent to ffmpeg as it is, as custom options.'}) + @ConfigProperty({ + arrayType: 'string', + description: 'It will be sent to ffmpeg as it is, as custom options.', + }) customOptions: string[] = []; } @@ -321,9 +384,11 @@ export class ServerVideoConfig { @SubConfigClass() export class PhotoConvertingConfig { - @ConfigProperty({description: 'Converts photos on the fly, when they are requested.'}) + @ConfigProperty({ + description: 'Converts photos on the fly, when they are requested.', + }) onTheFly: boolean = true; - @ConfigProperty({type: 'unsignedInt'}) + @ConfigProperty({ type: 'unsignedInt' }) resolution: videoResolutionType = 1080; } @@ -335,20 +400,29 @@ export class ServerPhotoConfig { @SubConfigClass() export class ServerPreviewConfig { - @ConfigProperty({type: 'object'}) - SearchQuery: SearchQueryDTO = {type: SearchQueryTypes.any_text, text: ''} as TextSearch; - @ConfigProperty({arrayType: SortingMethods}) + @ConfigProperty({ type: 'object' }) + SearchQuery: SearchQueryDTO = { + type: SearchQueryTypes.any_text, + text: '', + } as TextSearch; + @ConfigProperty({ arrayType: SortingMethods }) Sorting: SortingMethods[] = [ SortingMethods.descRating, - SortingMethods.descDate + SortingMethods.descDate, ]; } @SubConfigClass() export class ServerMediaConfig { - @ConfigProperty({description: 'Images are loaded from this folder (read permission required)'}) + @ConfigProperty({ + description: + 'Images are loaded from this folder (read permission required)', + }) folder: string = 'demo/images'; - @ConfigProperty({description: 'Thumbnails, converted photos, videos will be stored here (write permission required)'}) + @ConfigProperty({ + description: + 'Thumbnails, converted photos, videos will be stored here (write permission required)', + }) tempFolder: string = 'demo/tmp'; @ConfigProperty() Video: ServerVideoConfig = new ServerVideoConfig(); @@ -358,28 +432,27 @@ export class ServerMediaConfig { Thumbnail: ServerThumbnailConfig = new ServerThumbnailConfig(); } - @SubConfigClass() export class ServerEnvironmentConfig { - @ConfigProperty({volatile: true}) - upTime: string; - @ConfigProperty({volatile: true}) - appVersion: string; - @ConfigProperty({volatile: true}) - buildTime: string; - @ConfigProperty({volatile: true}) - buildCommitHash: string; - @ConfigProperty({volatile: true}) - isDocker: boolean; + @ConfigProperty({ volatile: true }) + upTime: string | undefined; + @ConfigProperty({ volatile: true }) + appVersion: string | undefined; + @ConfigProperty({ volatile: true }) + buildTime: string | undefined; + @ConfigProperty({ volatile: true }) + buildCommitHash: string | undefined; + @ConfigProperty({ volatile: true }) + isDocker: boolean | undefined; } @SubConfigClass() export class ServerConfig { - @ConfigProperty({volatile: true}) + @ConfigProperty({ volatile: true }) Environment: ServerEnvironmentConfig = new ServerEnvironmentConfig(); - @ConfigProperty({arrayType: 'string'}) + @ConfigProperty({ arrayType: 'string' }) sessionSecret: string[] = []; - @ConfigProperty({type: 'unsignedInt', envAlias: 'PORT', min: 0, max: 65535}) + @ConfigProperty({ type: 'unsignedInt', envAlias: 'PORT', min: 0, max: 65535 }) port: number = 80; @ConfigProperty() host: string = '0.0.0.0'; @@ -393,11 +466,15 @@ export class ServerConfig { Database: ServerDataBaseConfig = new ServerDataBaseConfig(); @ConfigProperty() Sharing: ServerSharingConfig = new ServerSharingConfig(); - @ConfigProperty({type: 'unsignedInt', description: 'unit: ms'}) + @ConfigProperty({ type: 'unsignedInt', description: 'unit: ms' }) sessionTimeout: number = 1000 * 60 * 60 * 24 * 7; // in ms @ConfigProperty() Indexing: ServerIndexingConfig = new ServerIndexingConfig(); - @ConfigProperty({type: 'unsignedInt', description: 'only this many bites will be loaded when scanning photo for metadata'}) + @ConfigProperty({ + type: 'unsignedInt', + description: + 'only this many bites will be loaded when scanning photo for metadata', + }) photoMetadataSize: number = 512 * 1024; // only this many bites will be loaded when scanning photo for metadata @ConfigProperty() Duplicates: ServerDuplicatesConfig = new ServerDuplicatesConfig(); @@ -407,9 +484,7 @@ export class ServerConfig { Jobs: ServerJobConfig = new ServerJobConfig(); } - export interface IPrivateConfig { Server: ServerConfig; Client: ClientConfig; - } diff --git a/src/common/config/private/WebConfig.ts b/src/common/config/private/WebConfig.ts index d7bf0b02..fe8ff3ef 100644 --- a/src/common/config/private/WebConfig.ts +++ b/src/common/config/private/WebConfig.ts @@ -1,4 +1,4 @@ -/* tslint:disable:no-inferrable-types */ +/* eslint-disable @typescript-eslint/no-inferrable-types */ import 'reflect-metadata'; import {ClientConfig} from '../public/ClientConfig'; import {ServerConfig} from './PrivateConfig'; diff --git a/src/common/config/public/ClientConfig.ts b/src/common/config/public/ClientConfig.ts index dfbb3a17..34c9334e 100644 --- a/src/common/config/public/ClientConfig.ts +++ b/src/common/config/public/ClientConfig.ts @@ -1,24 +1,25 @@ -/* tslint:disable:no-inferrable-types */ +/* eslint-disable @typescript-eslint/no-inferrable-types */ import 'reflect-metadata'; -import {SortingMethods} from '../../entities/SortingMethods'; -import {UserRoles} from '../../entities/UserDTO'; -import {ConfigProperty, SubConfigClass} from 'typeconfig/common'; -import {IPrivateConfig} from '../private/PrivateConfig'; - +import { SortingMethods } from '../../entities/SortingMethods'; +import { UserRoles } from '../../entities/UserDTO'; +import { ConfigProperty, SubConfigClass } from 'typeconfig/common'; +import { IPrivateConfig } from '../private/PrivateConfig'; export enum MapProviders { - OpenStreetMap = 1, Mapbox = 2, Custom = 3 + OpenStreetMap = 1, + Mapbox = 2, + Custom = 3, } @SubConfigClass() export class AutoCompleteConfig { @ConfigProperty() enabled: boolean = true; - @ConfigProperty({type: 'unsignedInt'}) + @ConfigProperty({ type: 'unsignedInt' }) targetItemsPerCategory: number = 5; - @ConfigProperty({type: 'unsignedInt'}) + @ConfigProperty({ type: 'unsignedInt' }) maxItems: number = 30; - @ConfigProperty({type: 'unsignedInt'}) + @ConfigProperty({ type: 'unsignedInt' }) cacheTimeout: number = 1000 * 60 * 60; } @@ -26,23 +27,25 @@ export class AutoCompleteConfig { export class ClientSearchConfig { @ConfigProperty() enabled: boolean = true; - @ConfigProperty({type: 'unsignedInt'}) + @ConfigProperty({ type: 'unsignedInt' }) searchCacheTimeout: number = 1000 * 60 * 60; @ConfigProperty() AutoComplete: AutoCompleteConfig = new AutoCompleteConfig(); - @ConfigProperty({type: 'unsignedInt'}) + @ConfigProperty({ type: 'unsignedInt' }) maxMediaResult: number = 10000; - @ConfigProperty({description: 'Search returns also with directories, not just media'}) + @ConfigProperty({ + description: 'Search returns also with directories, not just media', + }) listDirectories: boolean = false; @ConfigProperty({ - description: 'Search also returns with metafiles from directories that contain a media file of the matched search result' + description: + 'Search also returns with metafiles from directories that contain a media file of the matched search result', }) listMetafiles: boolean = true; - @ConfigProperty({type: 'unsignedInt'}) + @ConfigProperty({ type: 'unsignedInt' }) maxDirectoryResult: number = 200; } - @SubConfigClass() export class ClientAlbumConfig { @ConfigProperty() @@ -59,7 +62,7 @@ export class ClientSharingConfig { @SubConfigClass() export class ClientRandomPhotoConfig { - @ConfigProperty({description: 'Enables random link generation.'}) + @ConfigProperty({ description: 'Enables random link generation.' }) enabled: boolean = true; } @@ -78,30 +81,34 @@ export class ClientMapConfig { if (value === false) { config.Client.MetaFile.gpx = false; } - } + }, }) enabled: boolean = true; - @ConfigProperty({type: 'unsignedInt', description: 'Maximum number of markers to be shown on the map preview on the gallery page.'}) + @ConfigProperty({ + type: 'unsignedInt', + description: + 'Maximum number of markers to be shown on the map preview on the gallery page.', + }) maxPreviewMarkers: number = 50; @ConfigProperty() useImageMarkers: boolean = true; - @ConfigProperty({type: MapProviders}) + @ConfigProperty({ type: MapProviders }) mapProvider: MapProviders = MapProviders.OpenStreetMap; @ConfigProperty() mapboxAccessToken: string = ''; - @ConfigProperty({arrayType: MapLayers}) + @ConfigProperty({ arrayType: MapLayers }) customLayers: MapLayers[] = [new MapLayers()]; } @SubConfigClass() export class ClientThumbnailConfig { - @ConfigProperty({type: 'unsignedInt', max: 100}) + @ConfigProperty({ type: 'unsignedInt', max: 100 }) iconSize: number = 45; - @ConfigProperty({type: 'unsignedInt'}) + @ConfigProperty({ type: 'unsignedInt' }) personThumbnailSize: number = 200; - @ConfigProperty({arrayType: 'unsignedInt'}) + @ConfigProperty({ arrayType: 'unsignedInt' }) thumbnailSizes: number[] = [240, 480]; - @ConfigProperty({volatile: true}) + @ConfigProperty({ volatile: true }) concurrentThumbnailGenerations: number = 1; /** @@ -130,9 +137,12 @@ export class ClientOtherConfig { enableCache: boolean = true; @ConfigProperty() enableOnScrollRendering: boolean = true; - @ConfigProperty({type: SortingMethods}) + @ConfigProperty({ type: SortingMethods }) defaultPhotoSortingMethod: SortingMethods = SortingMethods.ascDate; - @ConfigProperty({description: 'If enabled directories will be sorted by date, like photos, otherwise by name. Directory date is the last modification time of that directory not the creation date of the oldest photo'}) + @ConfigProperty({ + description: + 'If enabled directories will be sorted by date, like photos, otherwise by name. Directory date is the last modification time of that directory not the creation date of the oldest photo', + }) enableDirectorySortingByDate: boolean = false; @ConfigProperty() enableOnScrollThumbnailPrioritising: boolean = true; @@ -142,7 +152,10 @@ export class ClientOtherConfig { captionFirstNaming: boolean = false; // shows the caption instead of the filename in the photo grid @ConfigProperty() enableDownloadZip: boolean = false; - @ConfigProperty({description: 'Adds a button to flattens the file structure, by listing the content of all subdirectories.'}) + @ConfigProperty({ + description: + 'Adds a button to flattens the file structure, by listing the content of all subdirectories.', + }) enableDirectoryFlattening: boolean = false; } @@ -162,7 +175,10 @@ export class PhotoConvertingConfig { export class ClientPhotoConfig { @ConfigProperty() Converting: PhotoConvertingConfig = new PhotoConvertingConfig(); - @ConfigProperty({description: 'Enables loading the full resolution image on zoom in the ligthbox (preview).'}) + @ConfigProperty({ + description: + 'Enables loading the full resolution image on zoom in the ligthbox (preview).', + }) loadFullImageOnZoom: boolean = true; } @@ -178,11 +194,19 @@ export class ClientMediaConfig { @SubConfigClass() export class ClientMetaFileConfig { - @ConfigProperty({description: 'Reads *.gpx files and renders them on the map.'}) + @ConfigProperty({ + description: 'Reads *.gpx files and renders them on the map.', + }) gpx: boolean = true; - @ConfigProperty({description: 'Reads *.md files in a directory and shows the next to the map.'}) + @ConfigProperty({ + description: + 'Reads *.md files in a directory and shows the next to the map.', + }) markdown: boolean = true; - @ConfigProperty({description: 'Reads *.pg2conf files (You can use it for custom sorting and save search (albums)).'}) + @ConfigProperty({ + description: + 'Reads *.pg2conf files (You can use it for custom sorting and save search (albums)).', + }) pg2conf: boolean = true; } @@ -192,9 +216,9 @@ export class ClientFacesConfig { enabled: boolean = true; @ConfigProperty() keywordsToPersons: boolean = true; - @ConfigProperty({type: UserRoles}) + @ConfigProperty({ type: UserRoles }) writeAccessMinRole: UserRoles = UserRoles.Admin; - @ConfigProperty({type: UserRoles}) + @ConfigProperty({ type: UserRoles }) readAccessMinRole: UserRoles = UserRoles.User; } @@ -220,10 +244,10 @@ export class ClientConfig { Other: ClientOtherConfig = new ClientOtherConfig(); @ConfigProperty() authenticationRequired: boolean = true; - @ConfigProperty({type: UserRoles}) + @ConfigProperty({ type: UserRoles }) unAuthenticatedUserRole: UserRoles = UserRoles.Admin; - @ConfigProperty({arrayType: 'string', volatile: true}) - languages: string[]; + @ConfigProperty({ arrayType: 'string', volatile: true }) + languages: string[] | undefined; @ConfigProperty() Media: ClientMediaConfig = new ClientMediaConfig(); @ConfigProperty() @@ -231,6 +255,3 @@ export class ClientConfig { @ConfigProperty() Faces: ClientFacesConfig = new ClientFacesConfig(); } - - - diff --git a/src/common/config/public/Config.ts b/src/common/config/public/Config.ts index 1422d6ab..6822fffd 100644 --- a/src/common/config/public/Config.ts +++ b/src/common/config/public/Config.ts @@ -4,31 +4,31 @@ import {WebConfigClassBuilder} from 'typeconfig/src/decorators/builders/WebConfi import {ConfigProperty} from 'typeconfig/src/decorators/property/ConfigPropoerty'; import {IWebConfigClass} from 'typeconfig/common'; - /** * These configuration will be available at frontend and backend too */ @WebConfigClass() export class ClientClass { - @ConfigProperty() public Client: ClientConfig = new ClientConfig(); } // ConfigInject is getting injected form the server side to the global scope -// tslint:disable-next-line:no-namespace +// eslint-disable-next-line @typescript-eslint/no-namespace declare namespace ServerInject { export const ConfigInject: ClientClass; } -export let Config: IWebConfigClass & ClientClass = WebConfigClassBuilder.attachInterface(new ClientClass()); +export let Config: IWebConfigClass & ClientClass = + WebConfigClassBuilder.attachInterface(new ClientClass()); - -if (typeof ServerInject !== 'undefined' && typeof ServerInject.ConfigInject !== 'undefined') { +if ( + typeof ServerInject !== 'undefined' && + typeof ServerInject.ConfigInject !== 'undefined' +) { Config.load(ServerInject.ConfigInject); } - if (Config.Client.publicUrl === '') { Config.Client.publicUrl = location.origin; } diff --git a/src/common/entities/AutoCompleteItem.ts b/src/common/entities/AutoCompleteItem.ts index 07eef1b4..f76e4264 100644 --- a/src/common/entities/AutoCompleteItem.ts +++ b/src/common/entities/AutoCompleteItem.ts @@ -1,4 +1,4 @@ -import {SearchQueryTypes} from './SearchQueryDTO'; +import { SearchQueryTypes } from './SearchQueryDTO'; export interface IAutoCompleteItem { text: string; @@ -6,8 +6,7 @@ export interface IAutoCompleteItem { } export class AutoCompleteItem implements IAutoCompleteItem { - constructor(public text: string, public type: SearchQueryTypes = null) { - } + constructor(public text: string, public type: SearchQueryTypes = null) {} equals(other: AutoCompleteItem): boolean { return this.text === other.text && this.type === other.type; diff --git a/src/common/entities/ConentWrapper.ts b/src/common/entities/ConentWrapper.ts index ede12724..984e8480 100644 --- a/src/common/entities/ConentWrapper.ts +++ b/src/common/entities/ConentWrapper.ts @@ -1,9 +1,10 @@ -import {ParentDirectoryDTO} from './DirectoryDTO'; -import {SearchResultDTO} from './SearchResultDTO'; +import { ParentDirectoryDTO } from './DirectoryDTO'; +import { SearchResultDTO } from './SearchResultDTO'; export class ContentWrapper { - constructor(public directory: ParentDirectoryDTO = null, - public searchResult: SearchResultDTO = null, - public notModified?: boolean) { - } + constructor( + public directory: ParentDirectoryDTO = null, + public searchResult: SearchResultDTO = null, + public notModified?: boolean + ) {} } diff --git a/src/common/entities/DirectoryDTO.ts b/src/common/entities/DirectoryDTO.ts index bd6ed353..4444973b 100644 --- a/src/common/entities/DirectoryDTO.ts +++ b/src/common/entities/DirectoryDTO.ts @@ -1,12 +1,13 @@ -import {MediaDTO, MediaDTOUtils} from './MediaDTO'; -import {FileDTO} from './FileDTO'; -import {PhotoDTO, PreviewPhotoDTO} from './PhotoDTO'; -import {Utils} from '../Utils'; +import { MediaDTO, MediaDTOUtils } from './MediaDTO'; +import { FileDTO } from './FileDTO'; +import { PhotoDTO, PreviewPhotoDTO } from './PhotoDTO'; +import { Utils } from '../Utils'; export interface DirectoryPathDTO { name: string; path: string; } + // // export interface DirectoryDTO extends DirectoryPathDTO { // id: number; @@ -23,8 +24,8 @@ export interface DirectoryPathDTO { // metaFile: FileDTO[]; // } - -export interface DirectoryBaseDTO extends DirectoryPathDTO { +export interface DirectoryBaseDTO + extends DirectoryPathDTO { id: number; name: string; path: string; @@ -41,7 +42,8 @@ export interface DirectoryBaseDTO extends Director validPreview?: boolean; // does not go to the client side } -export interface ParentDirectoryDTO extends DirectoryBaseDTO { +export interface ParentDirectoryDTO + extends DirectoryBaseDTO { id: number; name: string; path: string; @@ -55,7 +57,8 @@ export interface ParentDirectoryDTO extends Direct metaFile: FileDTO[]; } -export interface SubDirectoryDTO extends DirectoryBaseDTO { +export interface SubDirectoryDTO + extends DirectoryBaseDTO { id: number; name: string; path: string; @@ -121,12 +124,11 @@ export const DirectoryDTOUtils = { delete dir.validPreview; // should not go to the client side; return dir; - }, filterPhotos: (dir: DirectoryBaseDTO): PhotoDTO[] => { - return dir.media.filter(m => MediaDTOUtils.isPhoto(m)) as PhotoDTO[]; + return dir.media.filter((m) => MediaDTOUtils.isPhoto(m)) as PhotoDTO[]; }, filterVideos: (dir: DirectoryBaseDTO): PhotoDTO[] => { - return dir.media.filter(m => MediaDTOUtils.isPhoto(m)) as PhotoDTO[]; - } + return dir.media.filter((m) => MediaDTOUtils.isPhoto(m)) as PhotoDTO[]; + }, }; diff --git a/src/common/entities/DuplicatesDTO.ts b/src/common/entities/DuplicatesDTO.ts index 66dd7695..25a381f3 100644 --- a/src/common/entities/DuplicatesDTO.ts +++ b/src/common/entities/DuplicatesDTO.ts @@ -1,4 +1,4 @@ -import {MediaDTO} from './MediaDTO'; +import { MediaDTO } from './MediaDTO'; export interface DuplicatesDTO { media: MediaDTO[]; diff --git a/src/common/entities/Error.ts b/src/common/entities/Error.ts index 978f6d38..9eeb5108 100644 --- a/src/common/entities/Error.ts +++ b/src/common/entities/Error.ts @@ -1,4 +1,4 @@ -import {Request} from 'express'; +import { Request } from 'express'; export enum ErrorCodes { NOT_AUTHENTICATED = 1, @@ -7,10 +7,8 @@ export enum ErrorCodes { PERMISSION_DENIED = 4, CREDENTIAL_NOT_FOUND = 5, - USER_CREATION_ERROR = 6, - GENERAL_ERROR = 7, THUMBNAIL_GENERATION_ERROR = 8, PERSON_ERROR = 9, @@ -31,15 +29,22 @@ export enum ErrorCodes { export class ErrorDTO { public detailsStr: string; public request: { - method: string, url: string - } = {method: '', url: ''}; + method: string; + url: string; + } = { method: '', url: '' }; - constructor(public code: ErrorCodes, public message?: string, public details?: any, req?: Request) { - this.detailsStr = (this.details ? this.details.toString() : '') || ErrorCodes[code]; + constructor( + public code: ErrorCodes, + public message?: string, + public details?: any, + req?: Request + ) { + this.detailsStr = + (this.details ? this.details.toString() : '') || ErrorCodes[code]; if (req) { this.request = { method: req.method, - url: req.url + url: req.url, }; } } diff --git a/src/common/entities/FileDTO.ts b/src/common/entities/FileDTO.ts index 83412827..1dc7af68 100644 --- a/src/common/entities/FileDTO.ts +++ b/src/common/entities/FileDTO.ts @@ -1,5 +1,4 @@ -import {DirectoryPathDTO} from './DirectoryDTO'; - +import { DirectoryPathDTO } from './DirectoryDTO'; export interface FileDTO { id: number; diff --git a/src/common/entities/LoginCredential.ts b/src/common/entities/LoginCredential.ts index 1b71dd6e..8088142d 100644 --- a/src/common/entities/LoginCredential.ts +++ b/src/common/entities/LoginCredential.ts @@ -1,7 +1,7 @@ export class LoginCredential { - constructor(public username: string = '', - public password: string = '', - public rememberMe: boolean = false) { - - } + constructor( + public username: string = '', + public password: string = '', + public rememberMe: boolean = false + ) {} } diff --git a/src/common/entities/MediaDTO.ts b/src/common/entities/MediaDTO.ts index 101f19d5..01cba7f3 100644 --- a/src/common/entities/MediaDTO.ts +++ b/src/common/entities/MediaDTO.ts @@ -1,8 +1,7 @@ -import {DirectoryPathDTO} from './DirectoryDTO'; -import {PhotoDTO} from './PhotoDTO'; -import {FileDTO} from './FileDTO'; -import {SupportedFormats} from '../SupportedFormats'; - +import { DirectoryPathDTO } from './DirectoryDTO'; +import { PhotoDTO } from './PhotoDTO'; +import { FileDTO } from './FileDTO'; +import { SupportedFormats } from '../SupportedFormats'; export interface MediaDTO extends FileDTO { id: number; @@ -12,14 +11,12 @@ export interface MediaDTO extends FileDTO { missingThumbnails?: number; } - export interface MediaMetadata { size: MediaDimension; creationDate: number; fileSize: number; } - export interface MediaDimension { width: number; height: number; @@ -27,13 +24,17 @@ export interface MediaDimension { export const MediaDTOUtils = { hasPositionData: (media: MediaDTO): boolean => { - return !!(media as PhotoDTO).metadata.positionData && - !!((media as PhotoDTO).metadata.positionData.city || + return ( + !!(media as PhotoDTO).metadata.positionData && + !!( + (media as PhotoDTO).metadata.positionData.city || (media as PhotoDTO).metadata.positionData.state || (media as PhotoDTO).metadata.positionData.country || ((media as PhotoDTO).metadata.positionData.GPSData && (media as PhotoDTO).metadata.positionData.GPSData.latitude && - (media as PhotoDTO).metadata.positionData.GPSData.longitude)); + (media as PhotoDTO).metadata.positionData.GPSData.longitude) + ) + ); }, isPhoto: (media: FileDTO): boolean => { return !MediaDTOUtils.isVideo(media); @@ -69,8 +70,7 @@ export const MediaDTOUtils = { return false; }, - calcAspectRatio: (photo: MediaDTO): number => { return photo.metadata.size.width / photo.metadata.size.height; - } + }, }; diff --git a/src/common/entities/Message.ts b/src/common/entities/Message.ts index 2418b345..c9478d42 100644 --- a/src/common/entities/Message.ts +++ b/src/common/entities/Message.ts @@ -1,4 +1,4 @@ -import {ErrorDTO} from './Error'; +import { ErrorDTO } from './Error'; export class Message { public error: ErrorDTO = null; diff --git a/src/common/entities/NotificationDTO.ts b/src/common/entities/NotificationDTO.ts index f8b30f98..3e526867 100644 --- a/src/common/entities/NotificationDTO.ts +++ b/src/common/entities/NotificationDTO.ts @@ -1,5 +1,7 @@ export enum NotificationType { - error = 1, warning = 2, info = 3 + error = 1, + warning = 2, + info = 3, } export interface NotificationDTO { @@ -7,8 +9,8 @@ export interface NotificationDTO { message: string; details?: any; request?: { - method: string, - url: string, - statusCode: number + method: string; + url: string; + statusCode: number; }; } diff --git a/src/common/entities/PasswordChangeRequest.ts b/src/common/entities/PasswordChangeRequest.ts index 4ad8de71..1e769ea1 100644 --- a/src/common/entities/PasswordChangeRequest.ts +++ b/src/common/entities/PasswordChangeRequest.ts @@ -1,8 +1,11 @@ -import {UserModificationRequest} from './UserModificationRequest'; +import { UserModificationRequest } from './UserModificationRequest'; export class PasswordChangeRequest extends UserModificationRequest { - - constructor(id: number, public oldPassword: string, public newPassword: string) { + constructor( + id: number, + public oldPassword: string, + public newPassword: string + ) { super(id); } } diff --git a/src/common/entities/PersonDTO.ts b/src/common/entities/PersonDTO.ts index 8cc3f599..5a7ea14c 100644 --- a/src/common/entities/PersonDTO.ts +++ b/src/common/entities/PersonDTO.ts @@ -1,4 +1,4 @@ -import {FaceRegionEntry} from '../../backend/model/database/sql/enitites/FaceRegionEntry'; +import { FaceRegionEntry } from '../../backend/model/database/sql/enitites/FaceRegionEntry'; export interface PersonWithSampleRegion extends PersonDTO { sampleRegion: FaceRegionEntry; diff --git a/src/common/entities/PhotoDTO.ts b/src/common/entities/PhotoDTO.ts index 3b801cd0..0c827dd9 100644 --- a/src/common/entities/PhotoDTO.ts +++ b/src/common/entities/PhotoDTO.ts @@ -1,6 +1,5 @@ -import {DirectoryPathDTO} from './DirectoryDTO'; -import {OrientationTypes} from 'ts-exif-parser'; -import {MediaDimension, MediaDTO, MediaMetadata} from './MediaDTO'; +import { DirectoryPathDTO } from './DirectoryDTO'; +import { MediaDimension, MediaDTO, MediaMetadata } from './MediaDTO'; export interface PreviewPhotoDTO extends MediaDTO { name: string; @@ -39,7 +38,6 @@ export interface PhotoMetadata extends MediaMetadata { faces?: FaceRegion[]; } - export interface PositionMetaData { GPSData?: GPSMetadata; country?: string; @@ -52,7 +50,6 @@ export interface GPSMetadata { longitude?: number; // float with precision: 6 } - export interface CameraMetadata { ISO?: number; model?: string; diff --git a/src/common/entities/SearchQueryDTO.ts b/src/common/entities/SearchQueryDTO.ts index a8c595c6..c243ec1b 100644 --- a/src/common/entities/SearchQueryDTO.ts +++ b/src/common/entities/SearchQueryDTO.ts @@ -1,7 +1,10 @@ -import {GPSMetadata} from './PhotoDTO'; +import { GPSMetadata } from './PhotoDTO'; export enum SearchQueryTypes { - AND = 1, OR, SOME_OF, UNKNOWN_RELATION = 99999, + AND = 1, + OR, + SOME_OF, + UNKNOWN_RELATION = 99999, // non-text metadata // |- range types @@ -47,31 +50,35 @@ export const MinRangeSearchQueryTypes = [ export const MaxRangeSearchQueryTypes = [ SearchQueryTypes.to_date, SearchQueryTypes.max_rating, - SearchQueryTypes.max_resolution + SearchQueryTypes.max_resolution, ]; -export const RangeSearchQueryTypes = MinRangeSearchQueryTypes.concat(MaxRangeSearchQueryTypes); +export const RangeSearchQueryTypes = MinRangeSearchQueryTypes.concat( + MaxRangeSearchQueryTypes +); export const MetadataSearchQueryTypes = [ SearchQueryTypes.distance, - SearchQueryTypes.orientation -].concat(RangeSearchQueryTypes) + SearchQueryTypes.orientation, +] + .concat(RangeSearchQueryTypes) .concat(TextSearchQueryTypes); export const rangedTypePairs: any = {}; rangedTypePairs[SearchQueryTypes.from_date] = SearchQueryTypes.to_date; rangedTypePairs[SearchQueryTypes.min_rating] = SearchQueryTypes.max_rating; -rangedTypePairs[SearchQueryTypes.min_resolution] = SearchQueryTypes.max_resolution; +rangedTypePairs[SearchQueryTypes.min_resolution] = + SearchQueryTypes.max_resolution; // add the other direction too for (const key of Object.keys(rangedTypePairs)) { rangedTypePairs[rangedTypePairs[key]] = key; } export enum TextSearchQueryMatchTypes { - exact_match = 1, like = 2 + exact_match = 1, + like = 2, } - export const SearchQueryDTOUtils = { getRangedQueryPair: (type: SearchQueryTypes): SearchQueryTypes => { if (rangedTypePairs[type]) { @@ -83,15 +90,20 @@ export const SearchQueryDTOUtils = { switch (query.type) { case SearchQueryTypes.AND: query.type = SearchQueryTypes.OR; - (query as SearchListQuery).list = (query as SearchListQuery).list.map(q => SearchQueryDTOUtils.negate(q)); + (query as SearchListQuery).list = (query as SearchListQuery).list.map( + (q) => SearchQueryDTOUtils.negate(q) + ); return query; case SearchQueryTypes.OR: query.type = SearchQueryTypes.AND; - (query as SearchListQuery).list = (query as SearchListQuery).list.map(q => SearchQueryDTOUtils.negate(q)); + (query as SearchListQuery).list = (query as SearchListQuery).list.map( + (q) => SearchQueryDTOUtils.negate(q) + ); return query; case SearchQueryTypes.orientation: - (query as OrientationSearch).landscape = !(query as OrientationSearch).landscape; + (query as OrientationSearch).landscape = !(query as OrientationSearch) + .landscape; return query; case SearchQueryTypes.from_date: @@ -108,7 +120,9 @@ export const SearchQueryDTOUtils = { case SearchQueryTypes.caption: case SearchQueryTypes.file_name: case SearchQueryTypes.directory: - (query as NegatableSearchQuery).negate = !(query as NegatableSearchQuery).negate; + (query as NegatableSearchQuery).negate = !( + query as NegatableSearchQuery + ).negate; return query; case SearchQueryTypes.SOME_OF: @@ -117,14 +131,13 @@ export const SearchQueryDTOUtils = { default: throw new Error('Unknown type' + query.type); } - } + }, }; export interface SearchQueryDTO { type: SearchQueryTypes; } - export interface NegatableSearchQuery extends SearchQueryDTO { negate?: boolean; // if true negates the expression } @@ -133,7 +146,6 @@ export interface SearchListQuery extends SearchQueryDTO { list: SearchQueryDTO[]; } - export interface ANDSearchQuery extends SearchQueryDTO, SearchListQuery { type: SearchQueryTypes.AND; list: SearchQueryDTO[]; @@ -151,13 +163,14 @@ export interface SomeOfSearchQuery extends SearchQueryDTO, SearchListQuery { } export interface TextSearch extends NegatableSearchQuery { - type: SearchQueryTypes.any_text | - SearchQueryTypes.person | - SearchQueryTypes.keyword | - SearchQueryTypes.position | - SearchQueryTypes.caption | - SearchQueryTypes.file_name | - SearchQueryTypes.directory; + type: + | SearchQueryTypes.any_text + | SearchQueryTypes.person + | SearchQueryTypes.keyword + | SearchQueryTypes.position + | SearchQueryTypes.caption + | SearchQueryTypes.file_name + | SearchQueryTypes.directory; matchType?: TextSearchQueryMatchTypes; text: string; } @@ -171,7 +184,6 @@ export interface DistanceSearch extends NegatableSearchQuery { distance: number; // in kms } - export interface RangeSearch extends NegatableSearchQuery { value: number; } diff --git a/src/common/entities/SearchResultDTO.ts b/src/common/entities/SearchResultDTO.ts index 28fba8cc..d0eede9e 100644 --- a/src/common/entities/SearchResultDTO.ts +++ b/src/common/entities/SearchResultDTO.ts @@ -1,7 +1,7 @@ -import {SubDirectoryDTO} from './DirectoryDTO'; -import {FileDTO} from './FileDTO'; -import {MediaDTO} from './MediaDTO'; -import {SearchQueryDTO} from './SearchQueryDTO'; +import { SubDirectoryDTO } from './DirectoryDTO'; +import { FileDTO } from './FileDTO'; +import { MediaDTO } from './MediaDTO'; +import { SearchQueryDTO } from './SearchQueryDTO'; export interface SearchResultDTO { searchQuery: SearchQueryDTO; diff --git a/src/common/entities/SharingDTO.ts b/src/common/entities/SharingDTO.ts index 154cc09a..80f9750c 100644 --- a/src/common/entities/SharingDTO.ts +++ b/src/common/entities/SharingDTO.ts @@ -1,4 +1,4 @@ -import {UserDTO} from './UserDTO'; +import { UserDTO } from './UserDTO'; export interface SharingDTO { id: number; diff --git a/src/common/entities/SortingMethods.ts b/src/common/entities/SortingMethods.ts index aba385e1..f5533b3d 100644 --- a/src/common/entities/SortingMethods.ts +++ b/src/common/entities/SortingMethods.ts @@ -1,3 +1,9 @@ export enum SortingMethods { - ascName = 1, descName, ascDate, descDate, ascRating, descRating, random + ascName = 1, + descName, + ascDate, + descDate, + ascRating, + descRating, + random, } diff --git a/src/common/entities/UserDTO.ts b/src/common/entities/UserDTO.ts index 433c0600..7c0cd7b5 100644 --- a/src/common/entities/UserDTO.ts +++ b/src/common/entities/UserDTO.ts @@ -1,12 +1,12 @@ -import {DirectoryPathDTO} from './DirectoryDTO'; -import {Utils} from '../Utils'; +import { DirectoryPathDTO } from './DirectoryDTO'; +import { Utils } from '../Utils'; export enum UserRoles { LimitedGuest = 1, Guest = 2, User = 3, Admin = 4, - Developer = 5 + Developer = 5, } export interface UserDTO { @@ -20,12 +20,11 @@ export interface UserDTO { } export const UserDTOUtils = { - isDirectoryPathAvailable: (path: string, permissions: string[]): boolean => { if (permissions == null) { return true; } - permissions = permissions.map(p => Utils.canonizePath(p)); + permissions = permissions.map((p) => Utils.canonizePath(p)); path = Utils.canonizePath(path); if (permissions.length === 0 || permissions[0] === '/*') { return true; @@ -36,7 +35,10 @@ export const UserDTOUtils = { } if (permission[permission.length - 1] === '*') { permission = permission.slice(0, -1); - if (path.startsWith(permission) && (!path[permission.length] || path[permission.length] === '/')) { + if ( + path.startsWith(permission) && + (!path[permission.length] || path[permission.length] === '/') + ) { return true; } } else if (path === permission) { @@ -44,14 +46,17 @@ export const UserDTOUtils = { } else if (path === '.' && permission === '/') { return true; } - } return false; }, - - isDirectoryAvailable: (directory: DirectoryPathDTO, permissions: string[]): boolean => { + isDirectoryAvailable: ( + directory: DirectoryPathDTO, + permissions: string[] + ): boolean => { return UserDTOUtils.isDirectoryPathAvailable( - Utils.concatUrls(directory.path, directory.name), permissions); - } + Utils.concatUrls(directory.path, directory.name), + permissions + ); + }, }; diff --git a/src/common/entities/UserModificationRequest.ts b/src/common/entities/UserModificationRequest.ts index 3148c2f4..8f25d123 100644 --- a/src/common/entities/UserModificationRequest.ts +++ b/src/common/entities/UserModificationRequest.ts @@ -1,4 +1,3 @@ export class UserModificationRequest { - constructor(public id: number) { - } + constructor(public id: number) {} } diff --git a/src/common/entities/VideoDTO.ts b/src/common/entities/VideoDTO.ts index 2a9d844d..a74ff937 100644 --- a/src/common/entities/VideoDTO.ts +++ b/src/common/entities/VideoDTO.ts @@ -1,5 +1,5 @@ -import {DirectoryPathDTO} from './DirectoryDTO'; -import {MediaDimension, MediaDTO, MediaMetadata} from './MediaDTO'; +import { DirectoryPathDTO } from './DirectoryDTO'; +import { MediaDimension, MediaDTO, MediaMetadata } from './MediaDTO'; export interface VideoDTO extends MediaDTO { id: number; @@ -8,7 +8,6 @@ export interface VideoDTO extends MediaDTO { metadata: VideoMetadata; } - export interface VideoMetadata extends MediaMetadata { size: MediaDimension; creationDate: number; diff --git a/src/common/entities/album/AlbumBaseDTO.ts b/src/common/entities/album/AlbumBaseDTO.ts index 94c331c0..65f82f54 100644 --- a/src/common/entities/album/AlbumBaseDTO.ts +++ b/src/common/entities/album/AlbumBaseDTO.ts @@ -1,4 +1,4 @@ -import {PreviewPhotoDTO} from '../PhotoDTO'; +import { PreviewPhotoDTO } from '../PhotoDTO'; export interface AlbumBaseDTO { id: number; diff --git a/src/common/entities/album/SavedSearchDTO.ts b/src/common/entities/album/SavedSearchDTO.ts index 78d12418..c5818682 100644 --- a/src/common/entities/album/SavedSearchDTO.ts +++ b/src/common/entities/album/SavedSearchDTO.ts @@ -1,6 +1,6 @@ -import {AlbumBaseDTO} from './AlbumBaseDTO'; -import {PreviewPhotoDTO} from '../PhotoDTO'; -import {SearchQueryDTO} from '../SearchQueryDTO'; +import { AlbumBaseDTO } from './AlbumBaseDTO'; +import { PreviewPhotoDTO } from '../PhotoDTO'; +import { SearchQueryDTO } from '../SearchQueryDTO'; export interface SavedSearchDTO extends AlbumBaseDTO { id: number; diff --git a/src/common/entities/job/JobDTO.ts b/src/common/entities/job/JobDTO.ts index 54ae7f82..f0a23550 100644 --- a/src/common/entities/job/JobDTO.ts +++ b/src/common/entities/job/JobDTO.ts @@ -1,8 +1,7 @@ -import {backendText} from '../../BackendTexts'; +import { backendText } from '../../BackendTexts'; export type fieldType = 'string' | 'number' | 'boolean' | 'number-array'; - export enum DefaultsJobs { Indexing = 1, 'Database Reset' = 2, @@ -11,7 +10,7 @@ export enum DefaultsJobs { 'Thumbnail Generation' = 5, 'Temp Folder Cleaning' = 6, 'Preview Filling' = 7, - 'Preview Reset' = 8 + 'Preview Reset' = 8, } export interface ConfigTemplateEntry { @@ -30,5 +29,5 @@ export interface JobDTO { export const JobDTOUtils = { getHashName: (jobName: string, config: any = {}) => { return jobName + '-' + JSON.stringify(config); - } + }, }; diff --git a/src/common/entities/job/JobProgressDTO.ts b/src/common/entities/job/JobProgressDTO.ts index 7079e417..7aecb71b 100644 --- a/src/common/entities/job/JobProgressDTO.ts +++ b/src/common/entities/job/JobProgressDTO.ts @@ -1,8 +1,11 @@ export enum JobProgressStates { - running = 1, cancelling = 2, interrupted = 3, canceled = 4, finished = 5 + running = 1, + cancelling = 2, + interrupted = 3, + canceled = 4, + finished = 5, } - export interface JobProgressLogDTO { id: number; timestamp: string; @@ -13,14 +16,14 @@ export interface JobProgressDTO { jobName: string; HashName: string; steps: { - all: number, - processed: number, - skipped: number, + all: number; + processed: number; + skipped: number; }; state: JobProgressStates; logs: JobProgressLogDTO[]; time: { - start: number, - end: number + start: number; + end: number; }; } diff --git a/src/common/entities/job/JobScheduleDTO.ts b/src/common/entities/job/JobScheduleDTO.ts index a4c391e6..89901d74 100644 --- a/src/common/entities/job/JobScheduleDTO.ts +++ b/src/common/entities/job/JobScheduleDTO.ts @@ -1,5 +1,8 @@ export enum JobTriggerType { - never = 1, scheduled = 2, periodic = 3, after = 4 + never = 1, + scheduled = 2, + periodic = 3, + after = 4, } export interface JobTrigger { @@ -12,12 +15,12 @@ export interface NeverJobTrigger { export interface ScheduledJobTrigger extends JobTrigger { type: JobTriggerType.scheduled; - time: number; // data time + time: number; // data time } export interface PeriodicJobTrigger extends JobTrigger { type: JobTriggerType.periodic; - periodicity: number; // 0-6: week days 7 every day + periodicity: number; // 0-6: week days 7 every day atTime: number; // day time min value: 0, max: 23*60+59 } @@ -31,15 +34,19 @@ export interface JobScheduleDTO { jobName: string; config: any; allowParallelRun: boolean; - trigger: NeverJobTrigger | ScheduledJobTrigger | PeriodicJobTrigger | AfterJobTrigger; + trigger: + | NeverJobTrigger + | ScheduledJobTrigger + | PeriodicJobTrigger + | AfterJobTrigger; } - export const JobScheduleDTOUtils = { - getNextDayOfTheWeek: (refDate: Date, dayOfWeek: number): Date => { const date = new Date(refDate); - date.setDate(refDate.getDate() + (dayOfWeek + 1 + 7 - refDate.getDay()) % 7); + date.setDate( + refDate.getDate() + ((dayOfWeek + 1 + 7 - refDate.getDay()) % 7) + ); if (date.getDay() === refDate.getDay()) { return new Date(refDate); } @@ -50,7 +57,10 @@ export const JobScheduleDTOUtils = { nextValidDate: (date: Date, h: number, m: number, dayDiff: number): Date => { date.setUTCSeconds(0); date.setUTCMilliseconds(0); - if (date.getUTCHours() < h || (date.getUTCHours() === h && date.getUTCMinutes() < m)) { + if ( + date.getUTCHours() < h || + (date.getUTCHours() === h && date.getUTCMinutes() < m) + ) { date.setUTCHours(h); date.setUTCMinutes(m); } else { @@ -67,18 +77,31 @@ export const JobScheduleDTOUtils = { return new Date(schedule.trigger.time); case JobTriggerType.periodic: - const hour = Math.min(23, Math.floor(schedule.trigger.atTime / 60)); const minute = schedule.trigger.atTime % 60; - if (schedule.trigger.periodicity <= 6) { // Between Monday and Sunday - const nextRunDate = JobScheduleDTOUtils.getNextDayOfTheWeek(refDate, schedule.trigger.periodicity); - return JobScheduleDTOUtils.nextValidDate(nextRunDate, hour, minute, 7 * 24 * 60 * 60 * 1000); + if (schedule.trigger.periodicity <= 6) { + // Between Monday and Sunday + const nextRunDate = JobScheduleDTOUtils.getNextDayOfTheWeek( + refDate, + schedule.trigger.periodicity + ); + return JobScheduleDTOUtils.nextValidDate( + nextRunDate, + hour, + minute, + 7 * 24 * 60 * 60 * 1000 + ); } // every day - return JobScheduleDTOUtils.nextValidDate(new Date(refDate), hour, minute, 24 * 60 * 60 * 1000); + return JobScheduleDTOUtils.nextValidDate( + new Date(refDate), + hour, + minute, + 24 * 60 * 60 * 1000 + ); } return null; - } + }, }; diff --git a/src/common/entities/settings/BasicConfigDTO.ts b/src/common/entities/settings/BasicConfigDTO.ts index 7d9c31b2..4e4b29d1 100644 --- a/src/common/entities/settings/BasicConfigDTO.ts +++ b/src/common/entities/settings/BasicConfigDTO.ts @@ -1,5 +1,4 @@ -import {IPrivateConfig} from '../../config/private/PrivateConfig'; - +import { IPrivateConfig } from '../../config/private/PrivateConfig'; export interface BasicConfigDTO { imagesFolder: string; @@ -19,7 +18,7 @@ export const BasicConfigDTOUtil = { tempFolder: s.Server.Media.tempFolder, applicationTitle: s.Client.applicationTitle, publicUrl: s.Client.publicUrl, - urlBase: s.Client.urlBase + urlBase: s.Client.urlBase, }), mapToConf: (config: IPrivateConfig, input: BasicConfigDTO) => { config.Server.port = input.port; @@ -29,5 +28,5 @@ export const BasicConfigDTOUtil = { config.Client.publicUrl = input.publicUrl; config.Client.urlBase = input.urlBase; config.Client.applicationTitle = input.applicationTitle; - } + }, }; diff --git a/src/common/entities/settings/OtherConfigDTO.ts b/src/common/entities/settings/OtherConfigDTO.ts index 8501da68..69deae21 100644 --- a/src/common/entities/settings/OtherConfigDTO.ts +++ b/src/common/entities/settings/OtherConfigDTO.ts @@ -1,6 +1,5 @@ -import {ServerConfig, ServerThreadingConfig} from '../../config/private/PrivateConfig'; -import {ClientConfig, ClientOtherConfig} from '../../config/public/ClientConfig'; - +import { ServerThreadingConfig } from '../../config/private/PrivateConfig'; +import { ClientOtherConfig } from '../../config/public/ClientConfig'; export interface OtherConfigDTO { Server: ServerThreadingConfig; diff --git a/src/common/event/Event.ts b/src/common/event/Event.ts index a851dcba..59fa8be3 100644 --- a/src/common/event/Event.ts +++ b/src/common/event/Event.ts @@ -3,15 +3,14 @@ export class Event { protected singleHandlers: ((data?: T) => void)[] = []; public on(handler: (data?: T) => void): void { - if (typeof (handler) !== 'function') { + if (typeof handler !== 'function') { throw new Error('Event::on: Handler is not a function'); } this.handlers.push(handler); } - public once(handler: (data?: T) => void): void { - if (typeof (handler) !== 'function') { + if (typeof handler !== 'function') { throw new Error('Event::once: Handler is not a function'); } this.singleHandlers.push(handler); @@ -26,8 +25,8 @@ export class Event { } public off(handler: (data?: T) => void): void { - this.handlers = this.handlers.filter(h => h !== handler); - this.singleHandlers = this.singleHandlers.filter(h => h !== handler); + this.handlers = this.handlers.filter((h) => h !== handler); + this.singleHandlers = this.singleHandlers.filter((h) => h !== handler); } public allOff(): void { @@ -37,10 +36,10 @@ export class Event { public trigger(data?: T): void { if (this.handlers) { - this.handlers.slice(0).forEach(h => h(data)); + this.handlers.slice(0).forEach((h) => h(data)); } if (this.singleHandlers) { - this.singleHandlers.slice(0).forEach(h => h(data)); + this.singleHandlers.slice(0).forEach((h) => h(data)); this.singleHandlers = []; } } diff --git a/src/common/event/EventLimit.ts b/src/common/event/EventLimit.ts index 005a9e69..54ce3b2a 100644 --- a/src/common/event/EventLimit.ts +++ b/src/common/event/EventLimit.ts @@ -1,5 +1,4 @@ export class EventLimit { - private lastTriggerValue: T = null; private handlers: Array> = []; @@ -19,7 +18,9 @@ export class EventLimit { } public off(limit: T, handler: (data?: T) => void): void { - this.handlers = this.handlers.filter(h => h.handler !== handler && h.limit !== limit); + this.handlers = this.handlers.filter( + (h) => h.handler !== handler && h.limit !== limit + ); } public allOff(): void { @@ -28,13 +29,16 @@ export class EventLimit { public trigger = (data?: T) => { if (this.handlers) { - this.handlers.slice(0).forEach(h => { - if (h.limit <= data && (h.lastTriggerValue < h.limit || h.lastTriggerValue == null)) { + this.handlers.slice(0).forEach((h) => { + if ( + h.limit <= data && + (h.lastTriggerValue < h.limit || h.lastTriggerValue == null) + ) { h.fire(data); } h.lastTriggerValue = data; }); - this.handlers = this.handlers.filter(h => h.isValid()); + this.handlers = this.handlers.filter((h) => h.isValid()); } this.lastTriggerValue = data; }; @@ -43,8 +47,7 @@ export class EventLimit { class EventLimitHandler { public lastTriggerValue: T = null; - constructor(public limit: T, public handler: (data?: T) => void) { - } + constructor(public limit: T, public handler: (data?: T) => void) {} public fire(data?: T): void { this.handler(data); diff --git a/src/frontend/app/app.component.ts b/src/frontend/app/app.component.ts index 267ac521..e9bd52f1 100644 --- a/src/frontend/app/app.component.ts +++ b/src/frontend/app/app.component.ts @@ -1,27 +1,26 @@ -import {Component, OnDestroy, OnInit} from '@angular/core'; -import {AuthenticationService} from './model/network/authentication.service'; -import {Router} from '@angular/router'; -import {Config} from '../../common/config/public/Config'; -import {Title} from '@angular/platform-browser'; -import {ShareService} from './ui/gallery/share.service'; +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { AuthenticationService } from './model/network/authentication.service'; +import { Router } from '@angular/router'; +import { Config } from '../../common/config/public/Config'; +import { Title } from '@angular/platform-browser'; +import { ShareService } from './ui/gallery/share.service'; import 'hammerjs'; -import {Subscription} from 'rxjs'; -import {QueryParams} from '../../common/QueryParams'; +import { Subscription } from 'rxjs'; +import { QueryParams } from '../../common/QueryParams'; @Component({ selector: 'app-pi-gallery2', - template: ` - ` + template: ` `, }) export class AppComponent implements OnInit, OnDestroy { - private subscription: Subscription = null; - constructor(private router: Router, - private authenticationService: AuthenticationService, - private shareService: ShareService, - private title: Title) { - } + constructor( + private router: Router, + private authenticationService: AuthenticationService, + private shareService: ShareService, + private title: Title + ) {} async ngOnInit(): Promise { this.title.setTitle(Config.Client.applicationTitle); @@ -36,9 +35,7 @@ export class AppComponent implements OnInit, OnDestroy { return this.toLogin(); } } - }); - } ngOnDestroy(): void { @@ -48,14 +45,18 @@ export class AppComponent implements OnInit, OnDestroy { } private isLoginPage(): boolean { - return this.router.isActive('login', true) || this.router.isActive('shareLogin', false); + return ( + this.router.isActive('login', true) || + this.router.isActive('shareLogin', false) + ); } private async toLogin(): Promise { if (this.shareService.isSharing()) { const q: any = {}; - q[QueryParams.gallery.sharingKey_query] = this.shareService.getSharingKey(); - await this.router.navigate(['shareLogin'], {queryParams: q}); + q[QueryParams.gallery.sharingKey_query] = + this.shareService.getSharingKey(); + await this.router.navigate(['shareLogin'], { queryParams: q }); return; } else { await this.router.navigate(['login']); diff --git a/src/frontend/app/app.module.ts b/src/frontend/app/app.module.ts index 1e53acfc..6bfb7c60 100644 --- a/src/frontend/app/app.module.ts +++ b/src/frontend/app/app.module.ts @@ -1,136 +1,145 @@ -import {Injectable, NgModule} from '@angular/core'; -import {BrowserModule, HAMMER_GESTURE_CONFIG, HammerGestureConfig, HammerModule} from '@angular/platform-browser'; -import {FormsModule} from '@angular/forms'; -import {AppComponent} from './app.component'; -import {UserService} from './model/network/user.service'; -import {ContentService} from './ui/gallery/content.service'; -import {NetworkService} from './model/network/network.service'; -import {GalleryCacheService} from './ui/gallery/cache.gallery.service'; -import {FullScreenService} from './ui/gallery/fullscreen.service'; -import {AuthenticationService} from './model/network/authentication.service'; -import {UserMangerSettingsComponent} from './ui/settings/usermanager/usermanager.settings.component'; -import {FrameComponent} from './ui/frame/frame.component'; -import {LeafletModule} from '@asymmetrik/ngx-leaflet'; -import {LoadingBarModule} from '@ngx-loading-bar/core'; -import {GalleryLightboxMediaComponent} from './ui/gallery/lightbox/media/media.lightbox.gallery.component'; -import {GalleryPhotoLoadingComponent} from './ui/gallery/grid/photo/loading/loading.photo.grid.gallery.component'; -import {GalleryNavigatorComponent} from './ui/gallery/navigator/navigator.gallery.component'; -import {GallerySearchComponent} from './ui/gallery/search/search.gallery.component'; -import {GalleryLightboxComponent} from './ui/gallery/lightbox/lightbox.gallery.component'; -import {GalleryDirectoryComponent} from './ui/gallery/directories/directory/directory.gallery.component'; -import {GalleryGridComponent} from './ui/gallery/grid/grid.gallery.component'; -import {GalleryPhotoComponent} from './ui/gallery/grid/photo/photo.grid.gallery.component'; -import {LoginComponent} from './ui/login/login.component'; -import {AdminComponent} from './ui/admin/admin.component'; -import {GalleryComponent} from './ui/gallery/gallery.component'; -import {StringifyRole} from './pipes/StringifyRolePipe'; -import {GPXFilesFilterPipe} from './pipes/GPXFilesFilterPipe'; -import {GalleryMapComponent} from './ui/gallery/map/map.gallery.component'; -import {GalleryMapLightboxComponent} from './ui/gallery/map/lightbox/lightbox.map.gallery.component'; -import {ThumbnailManagerService} from './ui/gallery/thumbnailManager.service'; -import {OverlayService} from './ui/gallery/overlay.service'; -import {GalleryShareComponent} from './ui/gallery/share/share.gallery.component'; -import {ShareLoginComponent} from './ui/sharelogin/share-login.component'; -import {ShareService} from './ui/gallery/share.service'; -import {ModalModule} from 'ngx-bootstrap/modal'; -import {BsDatepickerModule} from 'ngx-bootstrap/datepicker'; -import {DatabaseSettingsComponent} from './ui/settings/database/database.settings.component'; -import {ToastrModule} from 'ngx-toastr'; -import {BrowserAnimationsModule} from '@angular/platform-browser/animations'; -import {NotificationService} from './model/notification.service'; -import {JwBootstrapSwitchNg2Module} from 'jw-bootstrap-switch-ng2'; -import {ClipboardModule} from 'ngx-clipboard'; -import {NavigationService} from './model/navigation.service'; -import {InfoPanelLightboxComponent} from './ui/gallery/lightbox/infopanel/info-panel.lightbox.gallery.component'; -import {MapSettingsComponent} from './ui/settings/map/map.settings.component'; -import {TooltipModule} from 'ngx-bootstrap/tooltip'; -import {BsDropdownModule} from 'ngx-bootstrap/dropdown'; -import {CollapseModule} from 'ngx-bootstrap/collapse'; -import {PopoverModule} from 'ngx-bootstrap/popover'; -import {ThumbnailSettingsComponent} from './ui/settings/thumbnail/thumbnail.settings.component'; -import {SearchSettingsComponent} from './ui/settings/search/search.settings.component'; -import {SettingsService} from './ui/settings/settings.service'; -import {ShareSettingsComponent} from './ui/settings/share/share.settings.component'; -import {BasicSettingsComponent} from './ui/settings/basic/basic.settings.component'; -import {OtherSettingsComponent} from './ui/settings/other/other.settings.component'; -import {HTTP_INTERCEPTORS, HttpClient, HttpClientModule} from '@angular/common/http'; -import {DefaultUrlSerializer, UrlSerializer, UrlTree} from '@angular/router'; -import {IndexingSettingsComponent} from './ui/settings/indexing/indexing.settings.component'; -import {LanguageComponent} from './ui/language/language.component'; -import {QueryService} from './model/query.service'; -import {IconizeSortingMethod} from './pipes/IconizeSortingMethod'; -import {StringifySortingMethod} from './pipes/StringifySortingMethod'; -import {RandomQueryBuilderGalleryComponent} from './ui/gallery/random-query-builder/random-query-builder.gallery.component'; -import {RandomPhotoSettingsComponent} from './ui/settings/random-photo/random-photo.settings.component'; -import {VideoSettingsComponent} from './ui/settings/video/video.settings.component'; -import {DurationPipe} from './pipes/DurationPipe'; -import {MapService} from './ui/gallery/map/map.service'; -import {MetaFileSettingsComponent} from './ui/settings/metafiles/metafile.settings.component'; -import {ThumbnailLoaderService} from './ui/gallery/thumbnailLoader.service'; -import {FileSizePipe} from './pipes/FileSizePipe'; -import {DuplicateService} from './ui/duplicates/duplicates.service'; -import {DuplicateComponent} from './ui/duplicates/duplicates.component'; -import {DuplicatesPhotoComponent} from './ui/duplicates/photo/photo.duplicates.component'; -import {SeededRandomService} from './model/seededRandom.service'; -import {FacesComponent} from './ui/faces/faces.component'; -import {FacesService} from './ui/faces/faces.service'; -import {FaceComponent} from './ui/faces/face/face.component'; -import {VersionService} from './model/version.service'; -import {DirectoriesComponent} from './ui/gallery/directories/directories.component'; -import {ControlsLightboxComponent} from './ui/gallery/lightbox/controls/controls.lightbox.gallery.component'; -import {FacesSettingsComponent} from './ui/settings/faces/faces.settings.component'; -import {TimepickerModule} from 'ngx-bootstrap/timepicker'; -import {TimeStampDatePickerComponent} from './ui/utils/timestamp-datepicker/datepicker.component'; -import {TimeStampTimePickerComponent} from './ui/utils/timestamp-timepicker/timepicker.component'; -import {PhotoSettingsComponent} from './ui/settings/photo/photo.settings.component'; -import {JobProgressComponent} from './ui/settings/jobs/progress/job-progress.settings.component'; -import {JobsSettingsComponent} from './ui/settings/jobs/jobs.settings.component'; -import {ScheduledJobsService} from './ui/settings/scheduled-jobs.service'; -import {BackendtextService} from './model/backendtext.service'; -import {JobButtonComponent} from './ui/settings/jobs/button/job-button.settings.component'; -import {ErrorInterceptor} from './model/network/helper/error.interceptor'; -import {CSRFInterceptor} from './model/network/helper/csrf.interceptor'; -import {SettingsEntryComponent} from './ui/settings/_abstract/settings-entry/settings-entry.component'; -import {GallerySearchQueryEntryComponent} from './ui/gallery/search/query-enrty/query-entry.search.gallery.component'; -import {StringifySearchQuery} from './pipes/StringifySearchQuery'; -import {AutoCompleteService} from './ui/gallery/search/autocomplete.service'; -import {SearchQueryParserService} from './ui/gallery/search/search-query-parser.service'; -import {GallerySearchFieldBaseComponent} from './ui/gallery/search/search-field-base/search-field-base.gallery.component'; -import {AppRoutingModule} from './app.routing'; -import {CookieService} from 'ngx-cookie-service'; -import {LeafletMarkerClusterModule} from '@asymmetrik/ngx-leaflet-markercluster'; -import {icon, Marker} from 'leaflet'; -import {AlbumsComponent} from './ui/albums/albums.component'; -import {AlbumComponent} from './ui/albums/album/album.component'; -import {AlbumsService} from './ui/albums/albums.service'; -import {GallerySearchQueryBuilderComponent} from './ui/gallery/search/query-builder/query-bulder.gallery.component'; -import {SavedSearchPopupComponent} from './ui/albums/saved-search-popup/saved-search-popup.component'; -import {AlbumsSettingsComponent} from './ui/settings/albums/albums.settings.component'; -import {MarkdownModule} from 'ngx-markdown'; -import {GalleryBlogComponent} from './ui/gallery/blog/blog.gallery.component'; -import {MDFilesFilterPipe} from './pipes/MDFilesFilterPipe'; -import {FileDTOToPathPipe} from './pipes/FileDTOToPathPipe'; -import {BlogService} from './ui/gallery/blog/blog.service'; -import {PhotoFilterPipe} from './pipes/PhotoFilterPipe'; -import {PreviewSettingsComponent} from './ui/settings/preview/preview.settings.component'; -import {GallerySearchFieldComponent} from './ui/gallery/search/search-field/search-field.gallery.component'; -import {GalleryFilterComponent} from './ui/gallery/filter/filter.gallery.component'; -import {GallerySortingService} from './ui/gallery/navigator/sorting.service'; -import {FilterService} from './ui/gallery/filter/filter.service'; +import { Injectable, NgModule } from '@angular/core'; +import { + BrowserModule, + HAMMER_GESTURE_CONFIG, + HammerGestureConfig, + HammerModule, +} from '@angular/platform-browser'; +import { FormsModule } from '@angular/forms'; +import { AppComponent } from './app.component'; +import { UserService } from './model/network/user.service'; +import { ContentService } from './ui/gallery/content.service'; +import { NetworkService } from './model/network/network.service'; +import { GalleryCacheService } from './ui/gallery/cache.gallery.service'; +import { FullScreenService } from './ui/gallery/fullscreen.service'; +import { AuthenticationService } from './model/network/authentication.service'; +import { UserMangerSettingsComponent } from './ui/settings/usermanager/usermanager.settings.component'; +import { FrameComponent } from './ui/frame/frame.component'; +import { LeafletModule } from '@asymmetrik/ngx-leaflet'; +import { LoadingBarModule } from '@ngx-loading-bar/core'; +import { GalleryLightboxMediaComponent } from './ui/gallery/lightbox/media/media.lightbox.gallery.component'; +import { GalleryPhotoLoadingComponent } from './ui/gallery/grid/photo/loading/loading.photo.grid.gallery.component'; +import { GalleryNavigatorComponent } from './ui/gallery/navigator/navigator.gallery.component'; +import { GallerySearchComponent } from './ui/gallery/search/search.gallery.component'; +import { GalleryLightboxComponent } from './ui/gallery/lightbox/lightbox.gallery.component'; +import { GalleryDirectoryComponent } from './ui/gallery/directories/directory/directory.gallery.component'; +import { GalleryGridComponent } from './ui/gallery/grid/grid.gallery.component'; +import { GalleryPhotoComponent } from './ui/gallery/grid/photo/photo.grid.gallery.component'; +import { LoginComponent } from './ui/login/login.component'; +import { AdminComponent } from './ui/admin/admin.component'; +import { GalleryComponent } from './ui/gallery/gallery.component'; +import { StringifyRole } from './pipes/StringifyRolePipe'; +import { GPXFilesFilterPipe } from './pipes/GPXFilesFilterPipe'; +import { GalleryMapComponent } from './ui/gallery/map/map.gallery.component'; +import { GalleryMapLightboxComponent } from './ui/gallery/map/lightbox/lightbox.map.gallery.component'; +import { ThumbnailManagerService } from './ui/gallery/thumbnailManager.service'; +import { OverlayService } from './ui/gallery/overlay.service'; +import { GalleryShareComponent } from './ui/gallery/share/share.gallery.component'; +import { ShareLoginComponent } from './ui/sharelogin/share-login.component'; +import { ShareService } from './ui/gallery/share.service'; +import { ModalModule } from 'ngx-bootstrap/modal'; +import { BsDatepickerModule } from 'ngx-bootstrap/datepicker'; +import { DatabaseSettingsComponent } from './ui/settings/database/database.settings.component'; +import { ToastrModule } from 'ngx-toastr'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { NotificationService } from './model/notification.service'; +import { JwBootstrapSwitchNg2Module } from 'jw-bootstrap-switch-ng2'; +import { ClipboardModule } from 'ngx-clipboard'; +import { NavigationService } from './model/navigation.service'; +import { InfoPanelLightboxComponent } from './ui/gallery/lightbox/infopanel/info-panel.lightbox.gallery.component'; +import { MapSettingsComponent } from './ui/settings/map/map.settings.component'; +import { TooltipModule } from 'ngx-bootstrap/tooltip'; +import { BsDropdownModule } from 'ngx-bootstrap/dropdown'; +import { CollapseModule } from 'ngx-bootstrap/collapse'; +import { PopoverModule } from 'ngx-bootstrap/popover'; +import { ThumbnailSettingsComponent } from './ui/settings/thumbnail/thumbnail.settings.component'; +import { SearchSettingsComponent } from './ui/settings/search/search.settings.component'; +import { SettingsService } from './ui/settings/settings.service'; +import { ShareSettingsComponent } from './ui/settings/share/share.settings.component'; +import { BasicSettingsComponent } from './ui/settings/basic/basic.settings.component'; +import { OtherSettingsComponent } from './ui/settings/other/other.settings.component'; +import { + HTTP_INTERCEPTORS, + HttpClient, + HttpClientModule, +} from '@angular/common/http'; +import { DefaultUrlSerializer, UrlSerializer, UrlTree } from '@angular/router'; +import { IndexingSettingsComponent } from './ui/settings/indexing/indexing.settings.component'; +import { LanguageComponent } from './ui/language/language.component'; +import { QueryService } from './model/query.service'; +import { IconizeSortingMethod } from './pipes/IconizeSortingMethod'; +import { StringifySortingMethod } from './pipes/StringifySortingMethod'; +import { RandomQueryBuilderGalleryComponent } from './ui/gallery/random-query-builder/random-query-builder.gallery.component'; +import { RandomPhotoSettingsComponent } from './ui/settings/random-photo/random-photo.settings.component'; +import { VideoSettingsComponent } from './ui/settings/video/video.settings.component'; +import { DurationPipe } from './pipes/DurationPipe'; +import { MapService } from './ui/gallery/map/map.service'; +import { MetaFileSettingsComponent } from './ui/settings/metafiles/metafile.settings.component'; +import { ThumbnailLoaderService } from './ui/gallery/thumbnailLoader.service'; +import { FileSizePipe } from './pipes/FileSizePipe'; +import { DuplicateService } from './ui/duplicates/duplicates.service'; +import { DuplicateComponent } from './ui/duplicates/duplicates.component'; +import { DuplicatesPhotoComponent } from './ui/duplicates/photo/photo.duplicates.component'; +import { SeededRandomService } from './model/seededRandom.service'; +import { FacesComponent } from './ui/faces/faces.component'; +import { FacesService } from './ui/faces/faces.service'; +import { FaceComponent } from './ui/faces/face/face.component'; +import { VersionService } from './model/version.service'; +import { DirectoriesComponent } from './ui/gallery/directories/directories.component'; +import { ControlsLightboxComponent } from './ui/gallery/lightbox/controls/controls.lightbox.gallery.component'; +import { FacesSettingsComponent } from './ui/settings/faces/faces.settings.component'; +import { TimepickerModule } from 'ngx-bootstrap/timepicker'; +import { TimeStampDatePickerComponent } from './ui/utils/timestamp-datepicker/datepicker.component'; +import { TimeStampTimePickerComponent } from './ui/utils/timestamp-timepicker/timepicker.component'; +import { PhotoSettingsComponent } from './ui/settings/photo/photo.settings.component'; +import { JobProgressComponent } from './ui/settings/jobs/progress/job-progress.settings.component'; +import { JobsSettingsComponent } from './ui/settings/jobs/jobs.settings.component'; +import { ScheduledJobsService } from './ui/settings/scheduled-jobs.service'; +import { BackendtextService } from './model/backendtext.service'; +import { JobButtonComponent } from './ui/settings/jobs/button/job-button.settings.component'; +import { ErrorInterceptor } from './model/network/helper/error.interceptor'; +import { CSRFInterceptor } from './model/network/helper/csrf.interceptor'; +import { SettingsEntryComponent } from './ui/settings/_abstract/settings-entry/settings-entry.component'; +import { GallerySearchQueryEntryComponent } from './ui/gallery/search/query-enrty/query-entry.search.gallery.component'; +import { StringifySearchQuery } from './pipes/StringifySearchQuery'; +import { AutoCompleteService } from './ui/gallery/search/autocomplete.service'; +import { SearchQueryParserService } from './ui/gallery/search/search-query-parser.service'; +import { GallerySearchFieldBaseComponent } from './ui/gallery/search/search-field-base/search-field-base.gallery.component'; +import { AppRoutingModule } from './app.routing'; +import { CookieService } from 'ngx-cookie-service'; +import { LeafletMarkerClusterModule } from '@asymmetrik/ngx-leaflet-markercluster'; +import { icon, Marker } from 'leaflet'; +import { AlbumsComponent } from './ui/albums/albums.component'; +import { AlbumComponent } from './ui/albums/album/album.component'; +import { AlbumsService } from './ui/albums/albums.service'; +import { GallerySearchQueryBuilderComponent } from './ui/gallery/search/query-builder/query-bulder.gallery.component'; +import { SavedSearchPopupComponent } from './ui/albums/saved-search-popup/saved-search-popup.component'; +import { AlbumsSettingsComponent } from './ui/settings/albums/albums.settings.component'; +import { MarkdownModule } from 'ngx-markdown'; +import { GalleryBlogComponent } from './ui/gallery/blog/blog.gallery.component'; +import { MDFilesFilterPipe } from './pipes/MDFilesFilterPipe'; +import { FileDTOToPathPipe } from './pipes/FileDTOToPathPipe'; +import { BlogService } from './ui/gallery/blog/blog.service'; +import { PhotoFilterPipe } from './pipes/PhotoFilterPipe'; +import { PreviewSettingsComponent } from './ui/settings/preview/preview.settings.component'; +import { GallerySearchFieldComponent } from './ui/gallery/search/search-field/search-field.gallery.component'; +import { GalleryFilterComponent } from './ui/gallery/filter/filter.gallery.component'; +import { GallerySortingService } from './ui/gallery/navigator/sorting.service'; +import { FilterService } from './ui/gallery/filter/filter.service'; @Injectable() export class MyHammerConfig extends HammerGestureConfig { events: string[] = ['pinch']; overrides = { - pan: {threshold: 1}, - swipe: {direction: 31}, // enable swipe up - pinch: {enable: true} + pan: { threshold: 1 }, + swipe: { direction: 31 }, // enable swipe up + pinch: { enable: true }, }; } - export class CustomUrlSerializer implements UrlSerializer { - private defaultUrlSerializer: DefaultUrlSerializer = new DefaultUrlSerializer(); + private defaultUrlSerializer: DefaultUrlSerializer = + new DefaultUrlSerializer(); parse(url: string): UrlTree { // Encode parentheses @@ -140,7 +149,10 @@ export class CustomUrlSerializer implements UrlSerializer { } serialize(tree: UrlTree): string { - return this.defaultUrlSerializer.serialize(tree).replace(/%28/g, '(').replace(/%29/g, ')'); + return this.defaultUrlSerializer + .serialize(tree) + .replace(/%28/g, '(') + .replace(/%29/g, ')'); } } @@ -157,11 +169,10 @@ const iconDefault = icon({ iconAnchor: [12, 41], popupAnchor: [1, -34], tooltipAnchor: [16, -28], - shadowSize: [41, 41] + shadowSize: [41, 41], }); Marker.prototype.options.icon = iconDefault; - @NgModule({ imports: [ BrowserModule, @@ -183,9 +194,10 @@ Marker.prototype.options.icon = iconDefault; LoadingBarModule, LeafletModule, LeafletMarkerClusterModule, - MarkdownModule.forRoot({loader: HttpClient}), + MarkdownModule.forRoot({ loader: HttpClient }), ], - declarations: [AppComponent, + declarations: [ + AppComponent, LoginComponent, ShareLoginComponent, GalleryComponent, @@ -260,13 +272,13 @@ Marker.prototype.options.icon = iconDefault; MDFilesFilterPipe, StringifySearchQuery, FileDTOToPathPipe, - PhotoFilterPipe + PhotoFilterPipe, ], providers: [ - {provide: HTTP_INTERCEPTORS, useClass: CSRFInterceptor, multi: true}, - {provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true}, - {provide: UrlSerializer, useClass: CustomUrlSerializer}, - {provide: HAMMER_GESTURE_CONFIG, useClass: MyHammerConfig}, + { provide: HTTP_INTERCEPTORS, useClass: CSRFInterceptor, multi: true }, + { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }, + { provide: UrlSerializer, useClass: CustomUrlSerializer }, + { provide: HAMMER_GESTURE_CONFIG, useClass: MyHammerConfig }, StringifySortingMethod, NetworkService, ShareService, @@ -295,9 +307,8 @@ Marker.prototype.options.icon = iconDefault; VersionService, ScheduledJobsService, BackendtextService, - CookieService + CookieService, ], - bootstrap: [AppComponent] + bootstrap: [AppComponent], }) -export class AppModule { -} +export class AppModule {} diff --git a/src/frontend/app/app.routing.ts b/src/frontend/app/app.routing.ts index 9cc402e4..6864ca83 100644 --- a/src/frontend/app/app.routing.ts +++ b/src/frontend/app/app.routing.ts @@ -1,19 +1,23 @@ -import {NgModule} from '@angular/core'; -import {RouterModule, Routes, UrlMatchResult, UrlSegment} from '@angular/router'; -import {LoginComponent} from './ui/login/login.component'; -import {GalleryComponent} from './ui/gallery/gallery.component'; -import {AdminComponent} from './ui/admin/admin.component'; -import {ShareLoginComponent} from './ui/sharelogin/share-login.component'; -import {QueryParams} from '../../common/QueryParams'; -import {DuplicateComponent} from './ui/duplicates/duplicates.component'; -import {FacesComponent} from './ui/faces/faces.component'; -import {AuthGuard} from './model/network/helper/auth.guard'; -import {AlbumsComponent} from './ui/albums/albums.component'; +import { NgModule } from '@angular/core'; +import { + RouterModule, + Routes, + UrlMatchResult, + UrlSegment, +} from '@angular/router'; +import { LoginComponent } from './ui/login/login.component'; +import { GalleryComponent } from './ui/gallery/gallery.component'; +import { AdminComponent } from './ui/admin/admin.component'; +import { ShareLoginComponent } from './ui/sharelogin/share-login.component'; +import { QueryParams } from '../../common/QueryParams'; +import { DuplicateComponent } from './ui/duplicates/duplicates.component'; +import { FacesComponent } from './ui/faces/faces.component'; +import { AuthGuard } from './model/network/helper/auth.guard'; +import { AlbumsComponent } from './ui/albums/albums.component'; export function galleryMatcherFunction( - segments: UrlSegment[]): UrlMatchResult | null { - - + segments: UrlSegment[] +): UrlMatchResult | null { if (segments.length === 0) { return null; } @@ -24,19 +28,28 @@ export function galleryMatcherFunction( if (segments.length > 1) { posParams[QueryParams.gallery.directory] = segments[1]; } - return {consumed: segments.slice(0, Math.min(segments.length, 2)), posParams}; + return { + consumed: segments.slice(0, Math.min(segments.length, 2)), + posParams, + }; } if (path === 'search') { if (segments.length > 1) { posParams[QueryParams.gallery.search.query] = segments[1]; } - return {consumed: segments.slice(0, Math.min(segments.length, 2)), posParams}; + return { + consumed: segments.slice(0, Math.min(segments.length, 2)), + posParams, + }; } if (path === 'share') { if (segments.length > 1) { posParams[QueryParams.gallery.sharingKey_params] = segments[1]; } - return {consumed: segments.slice(0, Math.min(segments.length, 2)), posParams}; + return { + consumed: segments.slice(0, Math.min(segments.length, 2)), + posParams, + }; } return null; } @@ -44,45 +57,43 @@ export function galleryMatcherFunction( const routes: Routes = [ { path: 'login', - component: LoginComponent + component: LoginComponent, }, { path: 'shareLogin', - component: ShareLoginComponent + component: ShareLoginComponent, }, { path: 'admin', component: AdminComponent, - canActivate: [AuthGuard] + canActivate: [AuthGuard], }, { path: 'duplicates', component: DuplicateComponent, - canActivate: [AuthGuard] + canActivate: [AuthGuard], }, { path: 'albums', component: AlbumsComponent, - canActivate: [AuthGuard] + canActivate: [AuthGuard], }, { path: 'faces', component: FacesComponent, - canActivate: [AuthGuard] + canActivate: [AuthGuard], }, { matcher: galleryMatcherFunction, component: GalleryComponent, - canActivate: [AuthGuard] + canActivate: [AuthGuard], }, - {path: '', redirectTo: '/login', pathMatch: 'full'}, - {path: '**', redirectTo: '/login', pathMatch: 'full'} + { path: '', redirectTo: '/login', pathMatch: 'full' }, + { path: '**', redirectTo: '/login', pathMatch: 'full' }, ]; - @NgModule({ imports: [RouterModule.forRoot(routes)], - exports: [RouterModule] + exports: [RouterModule], }) -export class AppRoutingModule { -} +export class AppRoutingModule {} diff --git a/src/frontend/app/model/IRenderable.ts b/src/frontend/app/model/IRenderable.ts index 03291a80..1891966e 100644 --- a/src/frontend/app/model/IRenderable.ts +++ b/src/frontend/app/model/IRenderable.ts @@ -11,6 +11,11 @@ export interface Dimension { export const DimensionUtils = { toString: (dim: Dimension) => { - return {top: dim.top + 'px', left: dim.left + 'px', width: dim.width + 'px', height: dim.height + 'px'}; - } + return { + top: dim.top + 'px', + left: dim.left + 'px', + width: dim.width + 'px', + height: dim.height + 'px', + }; + }, }; diff --git a/src/frontend/app/model/backendtext.service.spec.ts b/src/frontend/app/model/backendtext.service.spec.ts index 089e8405..09fd03c0 100644 --- a/src/frontend/app/model/backendtext.service.spec.ts +++ b/src/frontend/app/model/backendtext.service.spec.ts @@ -1,19 +1,16 @@ -import {inject, TestBed} from '@angular/core/testing'; -import {BackendtextService} from './backendtext.service'; -import {backendTexts} from '../../../common/BackendTexts'; - +import { inject, TestBed } from '@angular/core/testing'; +import { BackendtextService } from './backendtext.service'; +import { backendTexts } from '../../../common/BackendTexts'; describe('BackendTextService', () => { beforeEach(() => { TestBed.configureTestingModule({ - providers: [ - BackendtextService - ] + providers: [BackendtextService], }); }); - - it('should call UserDTO service login', inject([BackendtextService], + it('should call UserDTO service login', inject( + [BackendtextService], (backendTextService: BackendtextService) => { const getTexts = (obj: any) => { for (const key of Object.keys(obj)) { @@ -25,6 +22,6 @@ describe('BackendTextService', () => { } }; getTexts(backendTexts); - })); - + } + )); }); diff --git a/src/frontend/app/model/backendtext.service.ts b/src/frontend/app/model/backendtext.service.ts index 3273bffb..5533ef3f 100644 --- a/src/frontend/app/model/backendtext.service.ts +++ b/src/frontend/app/model/backendtext.service.ts @@ -1,13 +1,10 @@ -import {Injectable} from '@angular/core'; -import {backendText, backendTexts} from '../../../common/BackendTexts'; -import {DefaultsJobs} from '../../../common/entities/job/JobDTO'; +import { Injectable } from '@angular/core'; +import { backendText, backendTexts } from '../../../common/BackendTexts'; +import { DefaultsJobs } from '../../../common/entities/job/JobDTO'; @Injectable() export class BackendtextService { - - - constructor() { - } + constructor() {} public get(id: backendText): string { switch (id) { diff --git a/src/frontend/app/model/navigation.service.ts b/src/frontend/app/model/navigation.service.ts index fc816f70..525605f2 100644 --- a/src/frontend/app/model/navigation.service.ts +++ b/src/frontend/app/model/navigation.service.ts @@ -1,25 +1,25 @@ -import {Injectable} from '@angular/core'; +import { Injectable } from '@angular/core'; -import {Router} from '@angular/router'; -import {ShareService} from '../ui/gallery/share.service'; +import { Router } from '@angular/router'; +import { ShareService } from '../ui/gallery/share.service'; @Injectable() export class NavigationService { - - - constructor(private router: Router, - private shareService: ShareService) { - - } + constructor(private router: Router, private shareService: ShareService) {} public isLoginPage(): boolean { - return this.router.isActive('login', true) || this.router.isActive('shareLogin', true); + return ( + this.router.isActive('login', true) || + this.router.isActive('shareLogin', true) + ); } public async toLogin(): Promise { await this.shareService.wait(); if (this.shareService.isSharing()) { - return this.router.navigate(['shareLogin'], {queryParams: {sk: this.shareService.getSharingKey()}}); + return this.router.navigate(['shareLogin'], { + queryParams: { sk: this.shareService.getSharingKey() }, + }); } else { return this.router.navigate(['login']); } diff --git a/src/frontend/app/model/network/autehentication.service.spec.ts b/src/frontend/app/model/network/autehentication.service.spec.ts index 3208aa43..df616d04 100644 --- a/src/frontend/app/model/network/autehentication.service.spec.ts +++ b/src/frontend/app/model/network/autehentication.service.spec.ts @@ -1,16 +1,16 @@ -import {inject, TestBed} from '@angular/core/testing'; -import {UserService} from './user.service'; -import {UserDTO} from '../../../../common/entities/UserDTO'; -import {LoginCredential} from '../../../../common/entities/LoginCredential'; -import {AuthenticationService} from './authentication.service'; -import {NetworkService} from './network.service'; -import {ErrorDTO} from '../../../../common/entities/Error'; -import {VersionService} from '../version.service'; -import {ShareService} from '../../ui/gallery/share.service'; +import { inject, TestBed } from '@angular/core/testing'; +import { UserService } from './user.service'; +import { UserDTO } from '../../../../common/entities/UserDTO'; +import { LoginCredential } from '../../../../common/entities/LoginCredential'; +import { AuthenticationService } from './authentication.service'; +import { NetworkService } from './network.service'; +import { ErrorDTO } from '../../../../common/entities/Error'; +import { VersionService } from '../version.service'; +import { ShareService } from '../../ui/gallery/share.service'; class MockUserService { public login(credential: LoginCredential): Promise { - return Promise.resolve({name: 'testUserName'} as UserDTO); + return Promise.resolve({ name: 'testUserName' } as UserDTO); } public async getSessionUser(): Promise { @@ -19,13 +19,11 @@ class MockUserService { } class MockNetworkService { - addGlobalErrorHandler(fn: (error: ErrorDTO) => boolean): void { - } + addGlobalErrorHandler(fn: (error: ErrorDTO) => boolean): void {} } class MockShareService { - onNewUser(user: any): void { - } + onNewUser(user: any): void {} } describe('AuthenticationService', () => { @@ -34,33 +32,35 @@ describe('AuthenticationService', () => { TestBed.configureTestingModule({ providers: [ VersionService, - {provide: NetworkService, useClass: MockNetworkService}, - {provide: UserService, useClass: MockUserService}, - {provide: ShareService, useClass: MockShareService}, + { provide: NetworkService, useClass: MockNetworkService }, + { provide: UserService, useClass: MockUserService }, + { provide: ShareService, useClass: MockShareService }, AuthenticationService, - ] + ], }); }); - - it('should call UserDTO service login', inject([AuthenticationService, UserService], + it('should call UserDTO service login', inject( + [AuthenticationService, UserService], async (authService: AuthenticationService, userService: UserService) => { spyOn(userService, 'login').and.callThrough(); expect(userService.login).not.toHaveBeenCalled(); await authService.login(null); expect(userService.login).toHaveBeenCalled(); - })); + } + )); - it('should have NO Authenticated use', inject([AuthenticationService], + it('should have NO Authenticated use', inject( + [AuthenticationService], (authService: AuthenticationService) => { expect(authService.user.value).toBe(null); expect(authService.isAuthenticated()).toBe(false); - })); + } + )); - - it('should have Authenticated use', (done) => inject([AuthenticationService], - (authService: AuthenticationService) => { + it('should have Authenticated use', (done) => + inject([AuthenticationService], (authService: AuthenticationService) => { spyOn(authService.user, 'next').and.callThrough(); authService.user.subscribe((user) => { if (user == null) { @@ -73,5 +73,4 @@ describe('AuthenticationService', () => { }); authService.login({} as any); })()); - }); diff --git a/src/frontend/app/model/network/authentication.service.ts b/src/frontend/app/model/network/authentication.service.ts index 3189e670..b1b8e6bb 100644 --- a/src/frontend/app/model/network/authentication.service.ts +++ b/src/frontend/app/model/network/authentication.service.ts @@ -11,25 +11,32 @@ import {ShareService} from '../../ui/gallery/share.service'; import {CookieService} from 'ngx-cookie-service'; /* Injected config / user from server side */ -// tslint:disable-next-line:no-internal-module no-namespace +// eslint-disable-next-line @typescript-eslint/prefer-namespace-keyword, @typescript-eslint/no-namespace declare module ServerInject { export let user: UserDTO; } -@Injectable({providedIn: 'root'}) +@Injectable({ providedIn: 'root' }) export class AuthenticationService { - public readonly user: BehaviorSubject; - constructor(private userService: UserService, - private networkService: NetworkService, - private shareService: ShareService, - private cookieService: CookieService) { + constructor( + private userService: UserService, + private networkService: NetworkService, + private shareService: ShareService, + private cookieService: CookieService + ) { this.user = new BehaviorSubject(null); // picking up session.. - if (this.isAuthenticated() === false && this.cookieService.get(CookieNames.session) != null) { - if (typeof ServerInject !== 'undefined' && typeof ServerInject.user !== 'undefined') { + if ( + this.isAuthenticated() === false && + this.cookieService.get(CookieNames.session) != null + ) { + if ( + typeof ServerInject !== 'undefined' && + typeof ServerInject.user !== 'undefined' + ) { this.user.next(ServerInject.user); } this.getSessionUser().catch(console.error); @@ -37,7 +44,7 @@ export class AuthenticationService { if (Config.Client.authenticationRequired === false) { this.user.next({ name: UserRoles[Config.Client.unAuthenticatedUserRole], - role: Config.Client.unAuthenticatedUserRole + role: Config.Client.unAuthenticatedUserRole, } as UserDTO); } } @@ -101,6 +108,4 @@ export class AuthenticationService { console.error(error); } } - - } diff --git a/src/frontend/app/model/network/helper/auth.guard.ts b/src/frontend/app/model/network/helper/auth.guard.ts index d3f4f025..99c9ad37 100644 --- a/src/frontend/app/model/network/helper/auth.guard.ts +++ b/src/frontend/app/model/network/helper/auth.guard.ts @@ -1,16 +1,23 @@ -import {Injectable} from '@angular/core'; -import {ActivatedRouteSnapshot, CanActivate, RouterStateSnapshot} from '@angular/router'; -import {AuthenticationService} from '../authentication.service'; -import {NavigationService} from '../../navigation.service'; +import { Injectable } from '@angular/core'; +import { + ActivatedRouteSnapshot, + CanActivate, + RouterStateSnapshot, +} from '@angular/router'; +import { AuthenticationService } from '../authentication.service'; +import { NavigationService } from '../../navigation.service'; - -@Injectable({providedIn: 'root'}) +@Injectable({ providedIn: 'root' }) export class AuthGuard implements CanActivate { - constructor(private authenticationService: AuthenticationService, - private navigationService: NavigationService) { - } + constructor( + private authenticationService: AuthenticationService, + private navigationService: NavigationService + ) {} - canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { + canActivate( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): boolean { if (this.authenticationService.isAuthenticated() === true) { return true; } diff --git a/src/frontend/app/model/network/helper/csrf.interceptor.ts b/src/frontend/app/model/network/helper/csrf.interceptor.ts index d66bdb0b..3fe9b28b 100644 --- a/src/frontend/app/model/network/helper/csrf.interceptor.ts +++ b/src/frontend/app/model/network/helper/csrf.interceptor.ts @@ -1,22 +1,28 @@ -import {Injectable} from '@angular/core'; -import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http'; -import {Observable} from 'rxjs'; -import {AuthenticationService} from '../authentication.service'; - +import { Injectable } from '@angular/core'; +import { + HttpEvent, + HttpHandler, + HttpInterceptor, + HttpRequest, +} from '@angular/common/http'; +import { Observable } from 'rxjs'; +import { AuthenticationService } from '../authentication.service'; @Injectable() export class CSRFInterceptor implements HttpInterceptor { - constructor(private authenticationService: AuthenticationService) { - } + constructor(private authenticationService: AuthenticationService) {} - intercept(request: HttpRequest, next: HttpHandler): Observable> { + intercept( + request: HttpRequest, + next: HttpHandler + ): Observable> { // add authorization header with jwt token if available const currentUser = this.authenticationService.user.value; if (currentUser && currentUser.csrfToken) { request = request.clone({ setHeaders: { - 'CSRF-Token': `${currentUser.csrfToken}` - } + 'CSRF-Token': `${currentUser.csrfToken}`, + }, }); } return next.handle(request); diff --git a/src/frontend/app/model/network/helper/error.interceptor.ts b/src/frontend/app/model/network/helper/error.interceptor.ts index 93fde6b5..beec1a85 100644 --- a/src/frontend/app/model/network/helper/error.interceptor.ts +++ b/src/frontend/app/model/network/helper/error.interceptor.ts @@ -1,23 +1,32 @@ -import {Injectable} from '@angular/core'; -import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http'; -import {Observable, throwError} from 'rxjs'; -import {catchError} from 'rxjs/operators'; -import {AuthenticationService} from '../authentication.service'; +import { Injectable } from '@angular/core'; +import { + HttpEvent, + HttpHandler, + HttpInterceptor, + HttpRequest, +} from '@angular/common/http'; +import { Observable, throwError } from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import { AuthenticationService } from '../authentication.service'; @Injectable() export class ErrorInterceptor implements HttpInterceptor { - constructor(private authenticationService: AuthenticationService) { - } + constructor(private authenticationService: AuthenticationService) {} - intercept(request: HttpRequest, next: HttpHandler): Observable> { - return next.handle(request).pipe(catchError(err => { - if (err.status === 401) { - // auto logout if 401 response returned from api - this.authenticationService.logout(); - } + intercept( + request: HttpRequest, + next: HttpHandler + ): Observable> { + return next.handle(request).pipe( + catchError((err) => { + if (err.status === 401) { + // auto logout if 401 response returned from api + this.authenticationService.logout(); + } - const error = err.error.message || err.statusText; - return throwError(error); - })); + const error = err.error.message || err.statusText; + return throwError(error); + }) + ); } } diff --git a/src/frontend/app/model/network/network.service.spec.ts b/src/frontend/app/model/network/network.service.spec.ts index 42738881..35000010 100644 --- a/src/frontend/app/model/network/network.service.spec.ts +++ b/src/frontend/app/model/network/network.service.spec.ts @@ -1,15 +1,16 @@ -import {getTestBed, inject, TestBed} from '@angular/core/testing'; -import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing'; -import {NetworkService} from './network.service'; -import {Message} from '../../../../common/entities/Message'; -import {LoadingBarService} from '@ngx-loading-bar/core'; -import {VersionService} from '../version.service'; - +import { getTestBed, inject, TestBed } from '@angular/core/testing'; +import { + HttpClientTestingModule, + HttpTestingController, +} from '@angular/common/http/testing'; +import { NetworkService } from './network.service'; +import { Message } from '../../../../common/entities/Message'; +import { LoadingBarService } from '@ngx-loading-bar/core'; +import { VersionService } from '../version.service'; describe('NetworkService Success tests', () => { - const testUrl = '/test/url'; - const testData = {data: 'testData'}; + const testData = { data: 'testData' }; const testResponse = 'testResponse'; const testResponseMessage = new Message(null, testResponse); let injector; @@ -18,11 +19,7 @@ describe('NetworkService Success tests', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], - providers: [ - VersionService, - LoadingBarService, - NetworkService - ] + providers: [VersionService, LoadingBarService, NetworkService], }); injector = getTestBed(); httpMock = TestBed.inject(HttpTestingController); @@ -32,98 +29,106 @@ describe('NetworkService Success tests', () => { httpMock.verify(); }); - it('should call GET', inject([NetworkService], (networkService: NetworkService) => { + it('should call GET', inject( + [NetworkService], + (networkService: NetworkService) => { + networkService + .getJson(testUrl) + .then((res: string) => { + expect(res).toBe(testResponse); + }) + .catch((err) => { + console.error(err); + expect(err).toBeUndefined(); + }); - networkService.getJson(testUrl).then((res: string) => { - expect(res).toBe(testResponse); - }).catch((err) => { - console.error(err); - expect(err).toBeUndefined(); - }); + const mockReq = httpMock.expectOne({ method: 'GET' }); + expect(mockReq.cancelled).toBeFalsy(); + expect(mockReq.request.responseType).toEqual('json'); + mockReq.flush(testResponseMessage); + } + )); - const mockReq = httpMock.expectOne({method: 'GET'}); - expect(mockReq.cancelled).toBeFalsy(); - expect(mockReq.request.responseType).toEqual('json'); - mockReq.flush(testResponseMessage); - })); + it('should call POST', inject( + [NetworkService], + (networkService: NetworkService) => { + networkService + .postJson(testUrl, testData) + .then((res: string) => { + expect(res).toBe(testResponse); + }) + .catch((err) => { + console.error(err); + expect(err).toBeUndefined(); + }); - it('should call POST', inject([NetworkService], (networkService: NetworkService) => { + let mockReq = httpMock.expectOne('/api' + testUrl); + expect(mockReq.cancelled).toBeFalsy(); + expect(mockReq.request.responseType).toEqual('json'); + mockReq.flush(testResponseMessage); + expect(mockReq.request.body).toBe(testData); - networkService.postJson(testUrl, testData).then((res: string) => { - expect(res).toBe(testResponse); - }).catch((err) => { - console.error(err); - expect(err).toBeUndefined(); - }); + networkService + .postJson(testUrl) + .then((res: string) => { + expect(res).toBe(testResponse); + }) + .catch((err) => { + console.error(err); + expect(err).toBeUndefined(); + }); - let mockReq = httpMock.expectOne('/api' + testUrl); - expect(mockReq.cancelled).toBeFalsy(); - expect(mockReq.request.responseType).toEqual('json'); - mockReq.flush(testResponseMessage); - expect(mockReq.request.body).toBe(testData); + mockReq = httpMock.expectOne('/api' + testUrl); + expect(mockReq.cancelled).toBeFalsy(); + expect(mockReq.request.responseType).toEqual('json'); + expect(mockReq.request.body).toEqual({}); + mockReq.flush(testResponseMessage); + } + )); - networkService.postJson(testUrl).then((res: string) => { - expect(res).toBe(testResponse); - }).catch((err) => { - console.error(err); - expect(err).toBeUndefined(); - }); + it('should call DELETE', inject( + [NetworkService], + (networkService: NetworkService) => { + networkService.deleteJson(testUrl).then((res: any) => { + expect(res).toBe(testResponse); + }); - mockReq = httpMock.expectOne('/api' + testUrl); - expect(mockReq.cancelled).toBeFalsy(); - expect(mockReq.request.responseType).toEqual('json'); - expect(mockReq.request.body).toEqual({}); - mockReq.flush(testResponseMessage); + const mockReq = httpMock.expectOne({ method: 'DELETE' }); + expect(mockReq.cancelled).toBeFalsy(); + expect(mockReq.request.responseType).toEqual('json'); + mockReq.flush(testResponseMessage); + } + )); + it('should call PUT', inject( + [NetworkService], + (networkService: NetworkService) => { + networkService.putJson(testUrl, testData).then((res: any) => { + expect(res).toBe(testResponse); + }); - })); + let mockReq = httpMock.expectOne({}); + expect(mockReq.cancelled).toBeFalsy(); + expect(mockReq.request.responseType).toEqual('json'); + expect(mockReq.request.body).toEqual(testData); + mockReq.flush(testResponseMessage); + networkService.putJson(testUrl).then((res: any) => { + expect(res).toBe(testResponse); + }); - it('should call DELETE', inject([NetworkService], (networkService: NetworkService) => { - - networkService.deleteJson(testUrl).then((res: any) => { - expect(res).toBe(testResponse); - }); - - const mockReq = httpMock.expectOne({method: 'DELETE'}); - expect(mockReq.cancelled).toBeFalsy(); - expect(mockReq.request.responseType).toEqual('json'); - mockReq.flush(testResponseMessage); - })); - - it('should call PUT', inject([NetworkService], (networkService: NetworkService) => { - - networkService.putJson(testUrl, testData).then((res: any) => { - expect(res).toBe(testResponse); - }); - - - let mockReq = httpMock.expectOne({}); - expect(mockReq.cancelled).toBeFalsy(); - expect(mockReq.request.responseType).toEqual('json'); - expect(mockReq.request.body).toEqual(testData); - mockReq.flush(testResponseMessage); - - - networkService.putJson(testUrl).then((res: any) => { - expect(res).toBe(testResponse); - }); - - mockReq = httpMock.expectOne({}); - expect(mockReq.cancelled).toBeFalsy(); - expect(mockReq.request.responseType).toEqual('json'); - expect(mockReq.request.body).toEqual({}); - mockReq.flush(testResponseMessage); - - })); - + mockReq = httpMock.expectOne({}); + expect(mockReq.cancelled).toBeFalsy(); + expect(mockReq.request.responseType).toEqual('json'); + expect(mockReq.request.body).toEqual({}); + mockReq.flush(testResponseMessage); + } + )); }); - describe('NetworkService Fail tests', () => { - const testUrl = '/test/url'; - const testData = {data: 'testData'}; + const testData = { data: 'testData' }; const testError = 'testError'; let injector; let httpMock: HttpTestingController; @@ -131,11 +136,7 @@ describe('NetworkService Fail tests', () => { beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], - providers: [ - VersionService, - LoadingBarService, - NetworkService - ] + providers: [VersionService, LoadingBarService, NetworkService], }); injector = getTestBed(); httpMock = TestBed.inject(HttpTestingController); @@ -145,64 +146,89 @@ describe('NetworkService Fail tests', () => { httpMock.verify(); }); - it('should call GET with error', inject([NetworkService], (networkService: NetworkService) => { + it('should call GET with error', inject( + [NetworkService], + (networkService: NetworkService) => { + networkService + .getJson(testUrl) + .then((res: any) => { + expect(res).toBe(null); + }) + .catch((err) => { + expect(err).toBe( + 'Http failure response for /api/test/url: 0 ' + testError + ); + }); - networkService.getJson(testUrl).then((res: any) => { - expect(res).toBe(null); - }).catch((err) => { - expect(err).toBe('Http failure response for /api/test/url: 0 ' + testError); - }); + const mockReq = httpMock.expectOne({ method: 'GET' }); + expect(mockReq.cancelled).toBeFalsy(); + expect(mockReq.request.responseType).toEqual('json'); + mockReq.error(null, { statusText: testError }); + } + )); + it('should call POST with error', inject( + [NetworkService], + (networkService: NetworkService) => { + networkService + .postJson(testUrl, testData) + .then((res: any) => { + expect(res).toBe(null); + }) + .catch((err) => { + expect(err).toBe( + 'Http failure response for /api/test/url: 0 ' + testError + ); + }); - const mockReq = httpMock.expectOne({method: 'GET'}); - expect(mockReq.cancelled).toBeFalsy(); - expect(mockReq.request.responseType).toEqual('json'); - mockReq.error(null, {statusText: testError}); - })); + const mockReq = httpMock.expectOne({ method: 'POST' }); + expect(mockReq.cancelled).toBeFalsy(); + expect(mockReq.request.responseType).toEqual('json'); + expect(mockReq.request.body).toEqual(testData); + mockReq.error(null, { statusText: testError }); + } + )); - it('should call POST with error', inject([NetworkService], (networkService: NetworkService) => { + it('should call PUT with error', inject( + [NetworkService], + (networkService: NetworkService) => { + networkService + .putJson(testUrl, testData) + .then((res: any) => { + expect(res).toBe(null); + }) + .catch((err) => { + expect(err).toBe( + 'Http failure response for /api/test/url: 0 ' + testError + ); + }); - networkService.postJson(testUrl, testData).then((res: any) => { - expect(res).toBe(null); - }).catch((err) => { - expect(err).toBe('Http failure response for /api/test/url: 0 ' + testError); - }); + const mockReq = httpMock.expectOne({ method: 'PUT' }); + expect(mockReq.cancelled).toBeFalsy(); + expect(mockReq.request.responseType).toEqual('json'); + expect(mockReq.request.body).toEqual(testData); + mockReq.error(null, { statusText: testError }); + } + )); - const mockReq = httpMock.expectOne({method: 'POST'}); - expect(mockReq.cancelled).toBeFalsy(); - expect(mockReq.request.responseType).toEqual('json'); - expect(mockReq.request.body).toEqual(testData); - mockReq.error(null, {statusText: testError}); - })); + it('should call DELETE with error', inject( + [NetworkService], + (networkService: NetworkService) => { + networkService + .deleteJson(testUrl) + .then((res: any) => { + expect(res).toBe(null); + }) + .catch((err) => { + expect(err).toBe( + 'Http failure response for /api/test/url: 0 ' + testError + ); + }); - it('should call PUT with error', inject([NetworkService], (networkService: NetworkService) => { - - networkService.putJson(testUrl, testData).then((res: any) => { - expect(res).toBe(null); - }).catch((err) => { - expect(err).toBe('Http failure response for /api/test/url: 0 ' + testError); - }); - - - const mockReq = httpMock.expectOne({method: 'PUT'}); - expect(mockReq.cancelled).toBeFalsy(); - expect(mockReq.request.responseType).toEqual('json'); - expect(mockReq.request.body).toEqual(testData); - mockReq.error(null, {statusText: testError}); - - })); - - it('should call DELETE with error', inject([NetworkService], (networkService: NetworkService) => { - - networkService.deleteJson(testUrl).then((res: any) => { - expect(res).toBe(null); - }).catch((err) => { - expect(err).toBe('Http failure response for /api/test/url: 0 ' + testError); - }); - - const mockReq = httpMock.expectOne({method: 'DELETE'}); - expect(mockReq.cancelled).toBeFalsy(); - expect(mockReq.request.responseType).toEqual('json'); - mockReq.error(null, {statusText: testError}); - })); + const mockReq = httpMock.expectOne({ method: 'DELETE' }); + expect(mockReq.cancelled).toBeFalsy(); + expect(mockReq.request.responseType).toEqual('json'); + mockReq.error(null, { statusText: testError }); + } + )); }); diff --git a/src/frontend/app/model/network/network.service.ts b/src/frontend/app/model/network/network.service.ts index 8a62f44c..7c21bedc 100644 --- a/src/frontend/app/model/network/network.service.ts +++ b/src/frontend/app/model/network/network.service.ts @@ -1,23 +1,23 @@ -import {Injectable} from '@angular/core'; -import {HttpClient, HttpResponse} from '@angular/common/http'; -import {Message} from '../../../../common/entities/Message'; -import {LoadingBarService} from '@ngx-loading-bar/core'; -import {ErrorCodes, ErrorDTO} from '../../../../common/entities/Error'; -import {Config} from '../../../../common/config/public/Config'; -import {Utils} from '../../../../common/Utils'; -import {CustomHeaders} from '../../../../common/CustomHeaders'; -import {VersionService} from '../version.service'; +import { Injectable } from '@angular/core'; +import { HttpClient, HttpResponse } from '@angular/common/http'; +import { Message } from '../../../../common/entities/Message'; +import { LoadingBarService } from '@ngx-loading-bar/core'; +import { ErrorCodes, ErrorDTO } from '../../../../common/entities/Error'; +import { Config } from '../../../../common/config/public/Config'; +import { Utils } from '../../../../common/Utils'; +import { CustomHeaders } from '../../../../common/CustomHeaders'; +import { VersionService } from '../version.service'; @Injectable() export class NetworkService { - readonly apiBaseUrl = Utils.concatUrls(Config.Client.urlBase, '/api'); private globalErrorHandlers: Array<(error: ErrorDTO) => boolean> = []; - constructor(private http: HttpClient, - private loadingBarService: LoadingBarService, - private versionService: VersionService) { - } + constructor( + private http: HttpClient, + private loadingBarService: LoadingBarService, + private versionService: VersionService + ) {} public static buildUrl(url: string, data?: { [key: string]: any }): string { if (data) { @@ -36,8 +36,6 @@ export class NetworkService { } public getXML(url: string): Promise { - - this.loadingBarService.useRef().start(); const process = (res: string): Document => { @@ -51,15 +49,14 @@ export class NetworkService { return this.handleError(error); }; - return this.http.get(this.apiBaseUrl + url, {responseType: 'text'}) + return this.http + .get(this.apiBaseUrl + url, { responseType: 'text' }) .toPromise() .then(process) .catch(err); } public getText(url: string): Promise { - - this.loadingBarService.useRef().start(); const process = (res: string): string => { @@ -72,7 +69,8 @@ export class NetworkService { return this.handleError(error); }; - return this.http.get(this.apiBaseUrl + url, {responseType: 'text'}) + return this.http + .get(this.apiBaseUrl + url, { responseType: 'text' }) .toPromise() .then(process) .catch(err); @@ -98,7 +96,11 @@ export class NetworkService { this.globalErrorHandlers.push(fn); } - private callJson(method: 'get' | 'post' | 'delete' | 'put', url: string, data: any = {}): Promise { + private callJson( + method: 'get' | 'post' | 'delete' | 'put', + url: string, + data: any = {} + ): Promise { const body = data; this.loadingBarService.useRef().start(); @@ -107,7 +109,9 @@ export class NetworkService { this.loadingBarService.useRef().complete(); const msg = res.body; if (res.headers.has(CustomHeaders.dataVersion)) { - this.versionService.onNewVersion(res.headers.get(CustomHeaders.dataVersion)); + this.versionService.onNewVersion( + res.headers.get(CustomHeaders.dataVersion) + ); } if (!!msg.error) { if (msg.error.code) { @@ -125,29 +129,34 @@ export class NetworkService { switch (method) { case 'get': - return this.http.get>(this.apiBaseUrl + url, {observe: 'response'}) + return this.http + .get>(this.apiBaseUrl + url, { observe: 'response' }) .toPromise() .then(process) .catch(err); case 'delete': - return this.http.delete>(this.apiBaseUrl + url, {observe: 'response'}) + return this.http + .delete>(this.apiBaseUrl + url, { observe: 'response' }) .toPromise() .then(process) .catch(err); case 'post': - return this.http.post>(this.apiBaseUrl + url, body, {observe: 'response'}) + return this.http + .post>(this.apiBaseUrl + url, body, { + observe: 'response', + }) .toPromise() .then(process) .catch(err); case 'put': - return this.http.put>(this.apiBaseUrl + url, body, {observe: 'response'}) + return this.http + .put>(this.apiBaseUrl + url, body, { observe: 'response' }) .toPromise() .then(process) .catch(err); default: throw new Error('Unknown method'); } - } private handleError(error: any): Promise { diff --git a/src/frontend/app/model/network/user.service.spec.ts b/src/frontend/app/model/network/user.service.spec.ts index 72ae784a..faf8f9eb 100644 --- a/src/frontend/app/model/network/user.service.spec.ts +++ b/src/frontend/app/model/network/user.service.spec.ts @@ -1,11 +1,11 @@ -import {inject, TestBed} from '@angular/core/testing'; -import {HttpClientTestingModule} from '@angular/common/http/testing'; -import {NetworkService} from './network.service'; -import {UserService} from './user.service'; -import {LoginCredential} from '../../../../common/entities/LoginCredential'; -import {LoadingBarService} from '@ngx-loading-bar/core'; -import {ShareService} from '../../ui/gallery/share.service'; -import {VersionService} from '../version.service'; +import { inject, TestBed } from '@angular/core/testing'; +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { NetworkService } from './network.service'; +import { UserService } from './user.service'; +import { LoginCredential } from '../../../../common/entities/LoginCredential'; +import { LoadingBarService } from '@ngx-loading-bar/core'; +import { ShareService } from '../../ui/gallery/share.service'; +import { VersionService } from '../version.service'; class MockShareService { wait(): Promise { @@ -18,8 +18,6 @@ class MockShareService { } describe('UserService', (): void => { - - beforeEach((): void => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], @@ -28,28 +26,41 @@ describe('UserService', (): void => { UserService, LoadingBarService, NetworkService, - {provide: ShareService, useClass: MockShareService} - ] + { provide: ShareService, useClass: MockShareService }, + ], }); }); - it('should call postJson at login', inject([UserService, NetworkService], - async (userService: UserService, networkService: NetworkService): Promise => { + it('should call postJson at login', inject( + [UserService, NetworkService], + async ( + userService: UserService, + networkService: NetworkService + ): Promise => { spyOn(networkService, 'postJson'); const credential = new LoginCredential('name', 'pass'); await userService.login(credential); expect(networkService.postJson).toHaveBeenCalled(); - expect((networkService.postJson as any).calls.argsFor(0)).toEqual(['/user/login', {loginCredential: credential}]); - })); + expect((networkService.postJson as any).calls.argsFor(0)).toEqual([ + '/user/login', + { loginCredential: credential }, + ]); + } + )); - it('should call getJson at getSessionUser', inject([UserService, NetworkService], - async (userService: UserService, networkService: NetworkService): Promise => { + it('should call getJson at getSessionUser', inject( + [UserService, NetworkService], + async ( + userService: UserService, + networkService: NetworkService + ): Promise => { spyOn(networkService, 'getJson'); await userService.getSessionUser(); expect(networkService.getJson).toHaveBeenCalled(); - expect((networkService.getJson as any).calls.argsFor(0)).toEqual(['/user/me']); - })); - - + expect((networkService.getJson as any).calls.argsFor(0)).toEqual([ + '/user/me', + ]); + } + )); }); diff --git a/src/frontend/app/model/network/user.service.ts b/src/frontend/app/model/network/user.service.ts index cf938c89..cf6002eb 100644 --- a/src/frontend/app/model/network/user.service.ts +++ b/src/frontend/app/model/network/user.service.ts @@ -1,29 +1,36 @@ -import {Injectable} from '@angular/core'; -import {LoginCredential} from '../../../../common/entities/LoginCredential'; -import {NetworkService} from './network.service'; -import {UserDTO} from '../../../../common/entities/UserDTO'; -import {Config} from '../../../../common/config/public/Config'; -import {ShareService} from '../../ui/gallery/share.service'; -import {QueryParams} from '../../../../common/QueryParams'; +import { Injectable } from '@angular/core'; +import { LoginCredential } from '../../../../common/entities/LoginCredential'; +import { NetworkService } from './network.service'; +import { UserDTO } from '../../../../common/entities/UserDTO'; +import { Config } from '../../../../common/config/public/Config'; +import { ShareService } from '../../ui/gallery/share.service'; +import { QueryParams } from '../../../../common/QueryParams'; @Injectable() export class UserService { - - constructor(private networkService: NetworkService, - private shareService: ShareService) { - } + constructor( + private networkService: NetworkService, + private shareService: ShareService + ) {} public async logout(): Promise { return this.networkService.postJson('/user/logout'); } public async login(credential: LoginCredential): Promise { - return this.networkService.postJson('/user/login', {loginCredential: credential}); + return this.networkService.postJson('/user/login', { + loginCredential: credential, + }); } public async shareLogin(password: string): Promise { - return this.networkService.postJson('/share/login?' + QueryParams.gallery.sharingKey_query - + '=' + this.shareService.getSharingKey(), {password}); + return this.networkService.postJson( + '/share/login?' + + QueryParams.gallery.sharingKey_query + + '=' + + this.shareService.getSharingKey(), + { password } + ); } public async getSessionUser(): Promise { @@ -31,11 +38,11 @@ export class UserService { if (Config.Client.Sharing.enabled === true) { if (this.shareService.isSharing()) { const query: any = {}; - query[QueryParams.gallery.sharingKey_query] = this.shareService.getSharingKey(); + query[QueryParams.gallery.sharingKey_query] = + this.shareService.getSharingKey(); return this.networkService.getJson('/user/me', query); } } return this.networkService.getJson('/user/me'); } - } diff --git a/src/frontend/app/model/notification.service.ts b/src/frontend/app/model/notification.service.ts index 473305e5..31015333 100644 --- a/src/frontend/app/model/notification.service.ts +++ b/src/frontend/app/model/notification.service.ts @@ -1,9 +1,12 @@ -import {Injectable} from '@angular/core'; -import {ToastrService} from 'ngx-toastr'; -import {NetworkService} from './network/network.service'; -import {AuthenticationService} from './network/authentication.service'; -import {NotificationDTO, NotificationType} from '../../../common/entities/NotificationDTO'; -import {UserDTO, UserRoles} from '../../../common/entities/UserDTO'; +import { Injectable } from '@angular/core'; +import { ToastrService } from 'ngx-toastr'; +import { NetworkService } from './network/network.service'; +import { AuthenticationService } from './network/authentication.service'; +import { + NotificationDTO, + NotificationType, +} from '../../../common/entities/NotificationDTO'; +import { UserDTO, UserRoles } from '../../../common/entities/UserDTO'; export interface CountedNotificationDTO extends NotificationDTO { count: number; @@ -11,24 +14,26 @@ export interface CountedNotificationDTO extends NotificationDTO { @Injectable() export class NotificationService { - options = { positionClass: 'toast-top-center', - animate: 'flyLeft' + animate: 'flyLeft', }; countedNotifications: CountedNotificationDTO[] = []; numberOfNotifications = 0; lastUser: UserDTO = null; - constructor(private toastr: ToastrService, - private networkService: NetworkService, - private authService: AuthenticationService) { - + constructor( + private toastr: ToastrService, + private networkService: NetworkService, + private authService: AuthenticationService + ) { this.authService.user.subscribe(() => { - if (this.authService.isAuthenticated() && + if ( + this.authService.isAuthenticated() && (!this.lastUser || this.lastUser.id !== this.authService.user.value.id) && - this.authService.user.value.role >= UserRoles.Guest) { + this.authService.user.value.role >= UserRoles.Guest + ) { this.getServerNotifications(); } this.lastUser = this.authService.user.value; @@ -40,40 +45,48 @@ export class NotificationService { } groupNotifications(notifications: NotificationDTO[]): void { - const groups: { [key: string]: { notification: NotificationDTO, count: number } } = {}; - notifications.forEach(n => { + const groups: { + [key: string]: { notification: NotificationDTO; count: number }; + } = {}; + notifications.forEach((n) => { let key = n.message; if (n.details) { key += JSON.stringify(n.details); } - groups[key] = groups[key] || {notification: n, count: 0}; + groups[key] = groups[key] || { notification: n, count: 0 }; groups[key].count++; }); this.numberOfNotifications = notifications.length; this.countedNotifications = []; for (const key of Object.keys(groups)) { - (groups[key].notification as CountedNotificationDTO).count = groups[key].count; - this.countedNotifications.push(groups[key].notification as CountedNotificationDTO); + (groups[key].notification as CountedNotificationDTO).count = + groups[key].count; + this.countedNotifications.push( + groups[key].notification as CountedNotificationDTO + ); } - } async getServerNotifications(): Promise { try { - this.groupNotifications((await this.networkService.getJson('/notifications')) || []); + this.groupNotifications( + (await this.networkService.getJson( + '/notifications' + )) || [] + ); this.countedNotifications.forEach((noti) => { let msg = '(' + noti.count + ') ' + noti.message; if (noti.details) { msg += ' Details: ' + JSON.stringify(noti.details); } switch (noti.type) { - case NotificationType.error: + case NotificationType.error: this.error(msg, $localize`Server error`); break; - case NotificationType.warning: + case NotificationType.warning: this.warning(msg, $localize`Server error`); break; - case NotificationType.info: + case NotificationType.info: this.info(msg, $localize`Server info`); break; } diff --git a/src/frontend/app/model/page.helper.ts b/src/frontend/app/model/page.helper.ts index 576872dd..007bd5c4 100644 --- a/src/frontend/app/model/page.helper.ts +++ b/src/frontend/app/model/page.helper.ts @@ -1,14 +1,17 @@ export class PageHelper { private static readonly supportPageOffset = window.pageXOffset !== undefined; - private static readonly isCSS1Compat = ((document.compatMode || '') === 'CSS1Compat'); + private static readonly isCSS1Compat = + (document.compatMode || '') === 'CSS1Compat'; private static readonly body = document.getElementsByTagName('body')[0]; - constructor() { - - } + constructor() {} public static get ScrollY(): number { - return this.supportPageOffset ? window.pageYOffset : this.isCSS1Compat ? document.documentElement.scrollTop : document.body.scrollTop; + return this.supportPageOffset + ? window.pageYOffset + : this.isCSS1Compat + ? document.documentElement.scrollTop + : document.body.scrollTop; } public static set ScrollY(value: number) { @@ -16,13 +19,23 @@ export class PageHelper { } public static get MaxScrollY(): number { - return Math.max(document.body.scrollHeight, document.body.offsetHeight, - document.documentElement.clientHeight, document.documentElement.scrollHeight, - document.documentElement.offsetHeight) - window.innerHeight; + return ( + Math.max( + document.body.scrollHeight, + document.body.offsetHeight, + document.documentElement.clientHeight, + document.documentElement.scrollHeight, + document.documentElement.offsetHeight + ) - window.innerHeight + ); } public static get ScrollX(): number { - return this.supportPageOffset ? window.pageXOffset : this.isCSS1Compat ? document.documentElement.scrollLeft : document.body.scrollLeft; + return this.supportPageOffset + ? window.pageXOffset + : this.isCSS1Compat + ? document.documentElement.scrollLeft + : document.body.scrollLeft; } public static showScrollY(): void { diff --git a/src/frontend/app/model/query.service.ts b/src/frontend/app/model/query.service.ts index f07f18fb..877bef10 100644 --- a/src/frontend/app/model/query.service.ts +++ b/src/frontend/app/model/query.service.ts @@ -1,23 +1,29 @@ -import {Injectable} from '@angular/core'; -import {ShareService} from '../ui/gallery/share.service'; -import {MediaDTO} from '../../../common/entities/MediaDTO'; -import {QueryParams} from '../../../common/QueryParams'; -import {Utils} from '../../../common/Utils'; -import {ContentService} from '../ui/gallery/content.service'; -import {Config} from '../../../common/config/public/Config'; -import {ParentDirectoryDTO, SubDirectoryDTO} from '../../../common/entities/DirectoryDTO'; +import { Injectable } from '@angular/core'; +import { ShareService } from '../ui/gallery/share.service'; +import { MediaDTO } from '../../../common/entities/MediaDTO'; +import { QueryParams } from '../../../common/QueryParams'; +import { Utils } from '../../../common/Utils'; +import { ContentService } from '../ui/gallery/content.service'; +import { Config } from '../../../common/config/public/Config'; +import { + ParentDirectoryDTO, + SubDirectoryDTO, +} from '../../../common/entities/DirectoryDTO'; @Injectable() export class QueryService { - - - constructor(private shareService: ShareService, - private galleryService: ContentService) { - } + constructor( + private shareService: ShareService, + private galleryService: ContentService + ) {} getMediaStringId(media: MediaDTO): string { if (this.galleryService.isSearchResult()) { - return Utils.concatUrls(media.directory.path, media.directory.name, media.name); + return Utils.concatUrls( + media.directory.path, + media.directory.name, + media.name + ); } else { return media.name; } @@ -30,27 +36,33 @@ export class QueryService { } if (Config.Client.Sharing.enabled === true) { if (this.shareService.isSharing()) { - query[QueryParams.gallery.sharingKey_query] = this.shareService.getSharingKey(); + query[QueryParams.gallery.sharingKey_query] = + this.shareService.getSharingKey(); } } return query; } - getParamsForDirs(directory: ParentDirectoryDTO | SubDirectoryDTO): { [key: string]: any } { + getParamsForDirs(directory: ParentDirectoryDTO | SubDirectoryDTO): { + [key: string]: any; + } { const params: { [key: string]: any } = {}; if (Config.Client.Sharing.enabled === true) { if (this.shareService.isSharing()) { - params[QueryParams.gallery.sharingKey_query] = this.shareService.getSharingKey(); + params[QueryParams.gallery.sharingKey_query] = + this.shareService.getSharingKey(); } } - if (directory && directory.lastModified && directory.lastScanned && - !directory.isPartial) { + if ( + directory && + directory.lastModified && + directory.lastScanned && + !directory.isPartial + ) { params[QueryParams.gallery.knownLastModified] = directory.lastModified; params[QueryParams.gallery.knownLastScanned] = directory.lastScanned; } return params; - } - } diff --git a/src/frontend/app/model/seededRandom.service.ts b/src/frontend/app/model/seededRandom.service.ts index c45a2ca7..678ba226 100644 --- a/src/frontend/app/model/seededRandom.service.ts +++ b/src/frontend/app/model/seededRandom.service.ts @@ -1,8 +1,7 @@ -import {Injectable} from '@angular/core'; +import { Injectable } from '@angular/core'; @Injectable() export class SeededRandomService { - private static readonly baseSeed = Math.random() * 2147483647; private seed: number; @@ -19,8 +18,7 @@ export class SeededRandomService { } get(): number { - this.seed = (this.seed * 16807 % 2147483647); + this.seed = (this.seed * 16807) % 2147483647; return this.seed / 2147483647; } - } diff --git a/src/frontend/app/model/version.service.ts b/src/frontend/app/model/version.service.ts index ec3d62cb..223990b4 100644 --- a/src/frontend/app/model/version.service.ts +++ b/src/frontend/app/model/version.service.ts @@ -1,9 +1,8 @@ -import {Injectable} from '@angular/core'; -import {BehaviorSubject} from 'rxjs'; +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; @Injectable() export class VersionService { - public version: BehaviorSubject; constructor() { diff --git a/src/frontend/app/pipes/DurationPipe.ts b/src/frontend/app/pipes/DurationPipe.ts index 6118c438..ef812b28 100644 --- a/src/frontend/app/pipes/DurationPipe.ts +++ b/src/frontend/app/pipes/DurationPipe.ts @@ -1,10 +1,8 @@ -import {Pipe, PipeTransform} from '@angular/core'; +import { Pipe, PipeTransform } from '@angular/core'; - -@Pipe({name: 'duration'}) +@Pipe({ name: 'duration' }) export class DurationPipe implements PipeTransform { - constructor() { - } + constructor() {} transform(time: number, separator: ':' | 'string' = 'string'): string { const h = Math.floor(time / 1000 / 60 / 60); @@ -13,9 +11,9 @@ export class DurationPipe implements PipeTransform { time %= 1000 * 60; const s = Math.floor(time / 1000); - if (separator === ':') { - const leftPad = (x: any): string => String(x).length >= 2 ? x : leftPad(`0${x}`); + const leftPad = (x: any): string => + String(x).length >= 2 ? x : leftPad(`0${x}`); return [h || 0, m || 0, s || 0].map(leftPad).join(':'); } let str = ''; diff --git a/src/frontend/app/pipes/FileDTOToPathPipe.ts b/src/frontend/app/pipes/FileDTOToPathPipe.ts index 0a845e26..857a84b7 100644 --- a/src/frontend/app/pipes/FileDTOToPathPipe.ts +++ b/src/frontend/app/pipes/FileDTOToPathPipe.ts @@ -1,14 +1,18 @@ -import {Pipe, PipeTransform} from '@angular/core'; -import {FileDTO} from '../../../common/entities/FileDTO'; -import {Utils} from '../../../common/Utils'; +import { Pipe, PipeTransform } from '@angular/core'; +import { FileDTO } from '../../../common/entities/FileDTO'; +import { Utils } from '../../../common/Utils'; - -@Pipe({name: 'toPath'}) +@Pipe({ name: 'toPath' }) export class FileDTOToPathPipe implements PipeTransform { transform(metaFile: FileDTO): string | null { if (!metaFile) { return null; } - return Utils.concatUrls('api/gallery/content/', metaFile.directory.path, metaFile.directory.name, metaFile.name); + return Utils.concatUrls( + 'api/gallery/content/', + metaFile.directory.path, + metaFile.directory.name, + metaFile.name + ); } } diff --git a/src/frontend/app/pipes/FileSizePipe.ts b/src/frontend/app/pipes/FileSizePipe.ts index d187f5f3..5ed0c104 100644 --- a/src/frontend/app/pipes/FileSizePipe.ts +++ b/src/frontend/app/pipes/FileSizePipe.ts @@ -1,10 +1,7 @@ -import {Pipe, PipeTransform} from '@angular/core'; +import { Pipe, PipeTransform } from '@angular/core'; - -@Pipe({name: 'fileSize'}) +@Pipe({ name: 'fileSize' }) export class FileSizePipe implements PipeTransform { - - transform(size: number): string { const postFixes = ['B', 'KB', 'MB', 'GB', 'TB']; let index = 0; diff --git a/src/frontend/app/pipes/GPXFilesFilterPipe.ts b/src/frontend/app/pipes/GPXFilesFilterPipe.ts index cc13d76e..8260834b 100644 --- a/src/frontend/app/pipes/GPXFilesFilterPipe.ts +++ b/src/frontend/app/pipes/GPXFilesFilterPipe.ts @@ -1,9 +1,8 @@ -import {Pipe, PipeTransform} from '@angular/core'; -import {FileDTO} from '../../../common/entities/FileDTO'; -import {Config} from '../../../common/config/public/Config'; +import { Pipe, PipeTransform } from '@angular/core'; +import { FileDTO } from '../../../common/entities/FileDTO'; +import { Config } from '../../../common/config/public/Config'; - -@Pipe({name: 'gpxFiles'}) +@Pipe({ name: 'gpxFiles' }) export class GPXFilesFilterPipe implements PipeTransform { transform(metaFiles: FileDTO[]): FileDTO[] | null { if (!Config.Client.MetaFile.gpx) { @@ -12,6 +11,8 @@ export class GPXFilesFilterPipe implements PipeTransform { if (!metaFiles) { return null; } - return metaFiles.filter((f: FileDTO): boolean => f.name.toLocaleLowerCase().endsWith('.gpx')); + return metaFiles.filter((f: FileDTO): boolean => + f.name.toLocaleLowerCase().endsWith('.gpx') + ); } } diff --git a/src/frontend/app/pipes/IconizeSortingMethod.ts b/src/frontend/app/pipes/IconizeSortingMethod.ts index 99c1bcb2..5f5f3c8b 100644 --- a/src/frontend/app/pipes/IconizeSortingMethod.ts +++ b/src/frontend/app/pipes/IconizeSortingMethod.ts @@ -1,8 +1,7 @@ -import {Pipe, PipeTransform} from '@angular/core'; -import {SortingMethods} from '../../../common/entities/SortingMethods'; +import { Pipe, PipeTransform } from '@angular/core'; +import { SortingMethods } from '../../../common/entities/SortingMethods'; - -@Pipe({name: 'iconizeSorting'}) +@Pipe({ name: 'iconizeSorting' }) export class IconizeSortingMethod implements PipeTransform { transform(method: SortingMethods): string { switch (method) { diff --git a/src/frontend/app/pipes/MDFilesFilterPipe.ts b/src/frontend/app/pipes/MDFilesFilterPipe.ts index b4957b17..4cab191a 100644 --- a/src/frontend/app/pipes/MDFilesFilterPipe.ts +++ b/src/frontend/app/pipes/MDFilesFilterPipe.ts @@ -1,13 +1,14 @@ -import {Pipe, PipeTransform} from '@angular/core'; -import {FileDTO} from '../../../common/entities/FileDTO'; +import { Pipe, PipeTransform } from '@angular/core'; +import { FileDTO } from '../../../common/entities/FileDTO'; - -@Pipe({name: 'mdFiles'}) +@Pipe({ name: 'mdFiles' }) export class MDFilesFilterPipe implements PipeTransform { transform(metaFiles: FileDTO[]): FileDTO[] | null { if (!metaFiles) { return null; } - return metaFiles.filter((f: FileDTO): boolean => f.name.toLocaleLowerCase().endsWith('.md')); + return metaFiles.filter((f: FileDTO): boolean => + f.name.toLocaleLowerCase().endsWith('.md') + ); } } diff --git a/src/frontend/app/pipes/PhotoFilterPipe.ts b/src/frontend/app/pipes/PhotoFilterPipe.ts index 28b63132..3177c23b 100644 --- a/src/frontend/app/pipes/PhotoFilterPipe.ts +++ b/src/frontend/app/pipes/PhotoFilterPipe.ts @@ -1,14 +1,15 @@ -import {Pipe, PipeTransform} from '@angular/core'; -import {MediaDTO, MediaDTOUtils} from '../../../common/entities/MediaDTO'; -import {PhotoDTO} from '../../../common/entities/PhotoDTO'; +import { Pipe, PipeTransform } from '@angular/core'; +import { MediaDTO, MediaDTOUtils } from '../../../common/entities/MediaDTO'; +import { PhotoDTO } from '../../../common/entities/PhotoDTO'; - -@Pipe({name: 'photosOnly'}) +@Pipe({ name: 'photosOnly' }) export class PhotoFilterPipe implements PipeTransform { transform(media: MediaDTO[]): PhotoDTO[] | null { if (!media) { return null; } - return media.filter((m: MediaDTO): boolean => MediaDTOUtils.isPhoto(m)) as PhotoDTO[]; + return media.filter((m: MediaDTO): boolean => + MediaDTOUtils.isPhoto(m) + ) as PhotoDTO[]; } } diff --git a/src/frontend/app/pipes/StringifyRolePipe.ts b/src/frontend/app/pipes/StringifyRolePipe.ts index 30cdfc85..647b6026 100644 --- a/src/frontend/app/pipes/StringifyRolePipe.ts +++ b/src/frontend/app/pipes/StringifyRolePipe.ts @@ -1,8 +1,7 @@ -import {Pipe, PipeTransform} from '@angular/core'; -import {UserRoles} from '../../../common/entities/UserDTO'; +import { Pipe, PipeTransform } from '@angular/core'; +import { UserRoles } from '../../../common/entities/UserDTO'; - -@Pipe({name: 'stringifyRole'}) +@Pipe({ name: 'stringifyRole' }) export class StringifyRole implements PipeTransform { transform(role: number): string { return UserRoles[role]; diff --git a/src/frontend/app/pipes/StringifySearchQuery.ts b/src/frontend/app/pipes/StringifySearchQuery.ts index ea262fb9..f8ae80c8 100644 --- a/src/frontend/app/pipes/StringifySearchQuery.ts +++ b/src/frontend/app/pipes/StringifySearchQuery.ts @@ -1,13 +1,10 @@ -import {Pipe, PipeTransform} from '@angular/core'; -import {SearchQueryDTO} from '../../../common/entities/SearchQueryDTO'; -import {SearchQueryParserService} from '../ui/gallery/search/search-query-parser.service'; +import { Pipe, PipeTransform } from '@angular/core'; +import { SearchQueryDTO } from '../../../common/entities/SearchQueryDTO'; +import { SearchQueryParserService } from '../ui/gallery/search/search-query-parser.service'; - -@Pipe({name: 'searchQuery'}) +@Pipe({ name: 'searchQuery' }) export class StringifySearchQuery implements PipeTransform { - constructor( - private searchQueryParserService: SearchQueryParserService) { - } + constructor(private searchQueryParserService: SearchQueryParserService) {} transform(query: SearchQueryDTO): string { return this.searchQueryParserService.stringify(query); diff --git a/src/frontend/app/pipes/StringifySortingMethod.ts b/src/frontend/app/pipes/StringifySortingMethod.ts index c7c56ab6..d36f564d 100644 --- a/src/frontend/app/pipes/StringifySortingMethod.ts +++ b/src/frontend/app/pipes/StringifySortingMethod.ts @@ -1,11 +1,9 @@ -import {Pipe, PipeTransform} from '@angular/core'; -import {SortingMethods} from '../../../common/entities/SortingMethods'; +import { Pipe, PipeTransform } from '@angular/core'; +import { SortingMethods } from '../../../common/entities/SortingMethods'; - -@Pipe({name: 'stringifySorting'}) +@Pipe({ name: 'stringifySorting' }) export class StringifySortingMethod implements PipeTransform { - constructor() { - } + constructor() {} transform(method: SortingMethods): string { switch (method) { diff --git a/src/frontend/app/ui/admin/admin.component.html b/src/frontend/app/ui/admin/admin.component.html index abaaaf65..7d671da2 100644 --- a/src/frontend/app/ui/admin/admin.component.html +++ b/src/frontend/app/ui/admin/admin.component.html @@ -56,13 +56,13 @@ name="simplifiedMode" switch-off-color="warning" switch-on-color="primary" - switch-inverse="true" + [switch-inverse]="true" switch-off-text="Advanced" switch-on-text="Simplified" i18n-switch-off-text i18n-switch-on-text - switch-handle-width="100" - switch-label-width="20" + [switch-handle-width]="100" + [switch-label-width]="20" [(ngModel)]="simplifiedMode" (ngModelChange)="modeToggle()"> @@ -81,7 +81,7 @@ *ngFor="let s of contents; let i=index;" (click)="scrollTo(i)" [hidden]="!s.HasAvailableSettings"> - {{s.Name}} + {{s.Name}} diff --git a/src/frontend/app/ui/admin/admin.component.ts b/src/frontend/app/ui/admin/admin.component.ts index 95d66073..b6ea73ee 100644 --- a/src/frontend/app/ui/admin/admin.component.ts +++ b/src/frontend/app/ui/admin/admin.component.ts @@ -1,54 +1,69 @@ -import {AfterViewInit, Component, ElementRef, OnInit, QueryList, ViewChildren} from '@angular/core'; -import {AuthenticationService} from '../../model/network/authentication.service'; -import {UserRoles} from '../../../../common/entities/UserDTO'; -import {NotificationService} from '../../model/notification.service'; -import {NotificationType} from '../../../../common/entities/NotificationDTO'; -import {NavigationService} from '../../model/navigation.service'; -import {ISettingsComponent} from '../settings/_abstract/ISettingsComponent'; -import {PageHelper} from '../../model/page.helper'; -import {SettingsService} from '../settings/settings.service'; -import {CookieNames} from '../../../../common/CookieNames'; -import {CookieService} from 'ngx-cookie-service'; +import { + AfterViewInit, + Component, + ElementRef, + OnInit, + QueryList, + ViewChildren, +} from '@angular/core'; +import { AuthenticationService } from '../../model/network/authentication.service'; +import { UserRoles } from '../../../../common/entities/UserDTO'; +import { NotificationService } from '../../model/notification.service'; +import { NotificationType } from '../../../../common/entities/NotificationDTO'; +import { NavigationService } from '../../model/navigation.service'; +import { ISettingsComponent } from '../settings/_abstract/ISettingsComponent'; +import { PageHelper } from '../../model/page.helper'; +import { SettingsService } from '../settings/settings.service'; +import { CookieNames } from '../../../../common/CookieNames'; +import { CookieService } from 'ngx-cookie-service'; @Component({ selector: 'app-admin', templateUrl: './admin.component.html', - styleUrls: ['./admin.component.css'] + styleUrls: ['./admin.component.css'], }) export class AdminComponent implements OnInit, AfterViewInit { simplifiedMode = true; @ViewChildren('setting') settingsComponents: QueryList; - @ViewChildren('setting', {read: ElementRef}) settingsComponentsElemRef: QueryList; + @ViewChildren('setting', { read: ElementRef }) + settingsComponentsElemRef: QueryList; contents: ISettingsComponent[] = []; - constructor(private authService: AuthenticationService, - private navigation: NavigationService, - public notificationService: NotificationService, - public settingsService: SettingsService, - private cookieService: CookieService) { + constructor( + private authService: AuthenticationService, + private navigation: NavigationService, + public notificationService: NotificationService, + public settingsService: SettingsService, + private cookieService: CookieService + ) { if (this.cookieService.check(CookieNames.advancedSettings)) { - this.simplifiedMode = !(this.cookieService.get(CookieNames.advancedSettings) === 'true'); + this.simplifiedMode = !( + this.cookieService.get(CookieNames.advancedSettings) === 'true' + ); } } ngAfterViewInit(): void { - setTimeout(() => this.contents = this.settingsComponents.toArray(), 0); + setTimeout(() => (this.contents = this.settingsComponents.toArray()), 0); } scrollTo(i: number): void { - PageHelper.ScrollY = this.settingsComponentsElemRef.toArray()[i].nativeElement.getBoundingClientRect().top + - PageHelper.ScrollY; + PageHelper.ScrollY = + this.settingsComponentsElemRef + .toArray() + [i].nativeElement.getBoundingClientRect().top + PageHelper.ScrollY; } ngOnInit(): void { - if (!this.authService.isAuthenticated() - || this.authService.user.value.role < UserRoles.Admin) { + if ( + !this.authService.isAuthenticated() || + this.authService.user.value.role < UserRoles.Admin + ) { this.navigation.toLogin(); return; } } - public getCss(type: NotificationType): string { switch (type) { case NotificationType.error: @@ -63,7 +78,11 @@ export class AdminComponent implements OnInit, AfterViewInit { modeToggle(): void { // save it for some years - this.cookieService.set(CookieNames.advancedSettings, this.simplifiedMode ? 'false' : 'true', 365 * 50); + this.cookieService.set( + CookieNames.advancedSettings, + this.simplifiedMode ? 'false' : 'true', + 365 * 50 + ); } } diff --git a/src/frontend/app/ui/albums/album/album.component.css b/src/frontend/app/ui/albums/album/album.component.css index 99c513ef..f9ffeb5a 100644 --- a/src/frontend/app/ui/albums/album/album.component.css +++ b/src/frontend/app/ui/albums/album/album.component.css @@ -1,4 +1,4 @@ -.info-button{ +.info-button { margin: 2px; cursor: default; } diff --git a/src/frontend/app/ui/albums/album/album.component.ts b/src/frontend/app/ui/albums/album/album.component.ts index 2ca850cf..ce5a10dd 100644 --- a/src/frontend/app/ui/albums/album/album.component.ts +++ b/src/frontend/app/ui/albums/album/album.component.ts @@ -1,13 +1,16 @@ -import {Component, Input, OnDestroy, OnInit} from '@angular/core'; -import {RouterLink} from '@angular/router'; -import {DomSanitizer, SafeStyle} from '@angular/platform-browser'; -import {Thumbnail, ThumbnailManagerService} from '../../gallery/thumbnailManager.service'; -import {AuthenticationService} from '../../../model/network/authentication.service'; -import {AlbumsService} from '../albums.service'; -import {AlbumBaseDTO} from '../../../../../common/entities/album/AlbumBaseDTO'; -import {Media} from '../../gallery/Media'; -import {SavedSearchDTO} from '../../../../../common/entities/album/SavedSearchDTO'; -import {UserRoles} from '../../../../../common/entities/UserDTO'; +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { RouterLink } from '@angular/router'; +import { DomSanitizer, SafeStyle } from '@angular/platform-browser'; +import { + Thumbnail, + ThumbnailManagerService, +} from '../../gallery/thumbnailManager.service'; +import { AuthenticationService } from '../../../model/network/authentication.service'; +import { AlbumsService } from '../albums.service'; +import { AlbumBaseDTO } from '../../../../../common/entities/album/AlbumBaseDTO'; +import { Media } from '../../gallery/Media'; +import { SavedSearchDTO } from '../../../../../common/entities/album/SavedSearchDTO'; +import { UserRoles } from '../../../../../common/entities/UserDTO'; @Component({ selector: 'app-album', @@ -21,12 +24,12 @@ export class AlbumComponent implements OnInit, OnDestroy { public thumbnail: Thumbnail = null; - constructor(private thumbnailService: ThumbnailManagerService, - private sanitizer: DomSanitizer, - private albumService: AlbumsService, - public authenticationService: AuthenticationService) { - - } + constructor( + private thumbnailService: ThumbnailManagerService, + private sanitizer: DomSanitizer, + private albumService: AlbumsService, + public authenticationService: AuthenticationService + ) {} get IsSavedSearch(): boolean { return this.album && !!this.AsSavedSearch.searchQuery; @@ -37,7 +40,7 @@ export class AlbumComponent implements OnInit, OnDestroy { } get CanUpdate(): boolean { - return this.authenticationService.user.getValue().role >= UserRoles.Admin; + return this.authenticationService.user.getValue().role >= UserRoles.Admin; } get RouterLink(): any[] { @@ -50,16 +53,20 @@ export class AlbumComponent implements OnInit, OnDestroy { ngOnInit(): void { if (this.album.preview) { - this.thumbnail = this.thumbnailService.getThumbnail(new Media(this.album.preview, this.size, this.size)); + this.thumbnail = this.thumbnailService.getThumbnail( + new Media(this.album.preview, this.size, this.size) + ); } - } getSanitizedThUrl(): SafeStyle { - return this.sanitizer.bypassSecurityTrustStyle('url(' + this.thumbnail.Src - .replace(/\(/g, '%28') - .replace(/'/g, '%27') - .replace(/\)/g, '%29') + ')'); + return this.sanitizer.bypassSecurityTrustStyle( + 'url(' + + this.thumbnail.Src.replace(/\(/g, '%28') + .replace(/'/g, '%27') + .replace(/\)/g, '%29') + + ')' + ); } ngOnDestroy(): void { diff --git a/src/frontend/app/ui/albums/albums.component.ts b/src/frontend/app/ui/albums/albums.component.ts index 7d93f563..01cb8a3f 100644 --- a/src/frontend/app/ui/albums/albums.component.ts +++ b/src/frontend/app/ui/albums/albums.component.ts @@ -1,44 +1,52 @@ -import {Component, ElementRef, OnInit, TemplateRef, ViewChild} from '@angular/core'; -import {AlbumsService} from './albums.service'; -import {BsModalService} from 'ngx-bootstrap/modal'; -import {BsModalRef} from 'ngx-bootstrap/modal/bs-modal-ref.service'; -import {SearchQueryTypes, TextSearch} from '../../../../common/entities/SearchQueryDTO'; -import {UserRoles} from '../../../../common/entities/UserDTO'; -import {AuthenticationService} from '../../model/network/authentication.service'; +import { + Component, + ElementRef, + OnInit, + TemplateRef, + ViewChild, +} from '@angular/core'; +import { AlbumsService } from './albums.service'; +import { BsModalService } from 'ngx-bootstrap/modal'; +import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service'; +import { + SearchQueryTypes, + TextSearch, +} from '../../../../common/entities/SearchQueryDTO'; +import { UserRoles } from '../../../../common/entities/UserDTO'; +import { AuthenticationService } from '../../model/network/authentication.service'; @Component({ selector: 'app-albums', templateUrl: './albums.component.html', - styleUrls: ['./albums.component.css'] + styleUrls: ['./albums.component.css'], }) export class AlbumsComponent implements OnInit { - @ViewChild('container', {static: true}) container: ElementRef; + @ViewChild('container', { static: true }) container: ElementRef; public size: number; public savedSearch = { name: '', - searchQuery: {type: SearchQueryTypes.any_text, text: ''} as TextSearch + searchQuery: { type: SearchQueryTypes.any_text, text: '' } as TextSearch, }; private modalRef: BsModalRef; - constructor(public albumsService: AlbumsService, - private modalService: BsModalService, - public authenticationService: AuthenticationService) { + constructor( + public albumsService: AlbumsService, + private modalService: BsModalService, + public authenticationService: AuthenticationService + ) { this.albumsService.getAlbums().catch(console.error); } - ngOnInit(): void { this.updateSize(); } - get CanCreateAlbum(): boolean { return this.authenticationService.user.getValue().role >= UserRoles.Admin; } - public async openModal(template: TemplateRef): Promise { - this.modalRef = this.modalService.show(template, {class: 'modal-lg'}); + this.modalRef = this.modalService.show(template, { class: 'modal-lg' }); document.body.style.paddingRight = '0px'; } @@ -48,7 +56,10 @@ export class AlbumsComponent implements OnInit { } async saveSearch(): Promise { - await this.albumsService.addSavedSearch(this.savedSearch.name, this.savedSearch.searchQuery); + await this.albumsService.addSavedSearch( + this.savedSearch.name, + this.savedSearch.searchQuery + ); this.hideModal(); } @@ -56,9 +67,7 @@ export class AlbumsComponent implements OnInit { const size = 220 + 5; // body - container margin const containerWidth = this.container.nativeElement.clientWidth - 30; - this.size = (containerWidth / Math.round((containerWidth / size))) - 5; + this.size = containerWidth / Math.round(containerWidth / size) - 5; } - - } diff --git a/src/frontend/app/ui/albums/albums.service.ts b/src/frontend/app/ui/albums/albums.service.ts index ff6f75e1..8b3e8607 100644 --- a/src/frontend/app/ui/albums/albums.service.ts +++ b/src/frontend/app/ui/albums/albums.service.ts @@ -1,9 +1,8 @@ -import {Injectable} from '@angular/core'; -import {NetworkService} from '../../model/network/network.service'; -import {BehaviorSubject} from 'rxjs'; -import {AlbumBaseDTO} from '../../../../common/entities/album/AlbumBaseDTO'; -import {SearchQueryDTO} from '../../../../common/entities/SearchQueryDTO'; - +import { Injectable } from '@angular/core'; +import { NetworkService } from '../../model/network/network.service'; +import { BehaviorSubject } from 'rxjs'; +import { AlbumBaseDTO } from '../../../../common/entities/album/AlbumBaseDTO'; +import { SearchQueryDTO } from '../../../../common/entities/SearchQueryDTO'; @Injectable() export class AlbumsService { @@ -13,10 +12,12 @@ export class AlbumsService { this.albums = new BehaviorSubject(null); } - public async getAlbums(): Promise { - this.albums.next((await this.networkService.getJson('/albums')) - .sort((a, b): number => a.name.localeCompare(b.name))); + this.albums.next( + (await this.networkService.getJson('/albums')).sort( + (a, b): number => a.name.localeCompare(b.name) + ) + ); } async deleteAlbum(album: AlbumBaseDTO): Promise { @@ -24,8 +25,14 @@ export class AlbumsService { await this.getAlbums(); } - async addSavedSearch(name: string, searchQuery: SearchQueryDTO): Promise { - await this.networkService.putJson('/albums/saved-searches', {name, searchQuery}); + async addSavedSearch( + name: string, + searchQuery: SearchQueryDTO + ): Promise { + await this.networkService.putJson('/albums/saved-searches', { + name, + searchQuery, + }); await this.getAlbums(); } } diff --git a/src/frontend/app/ui/albums/saved-search-popup/saved-search-popup.component.ts b/src/frontend/app/ui/albums/saved-search-popup/saved-search-popup.component.ts index e196b704..9cdab1f4 100644 --- a/src/frontend/app/ui/albums/saved-search-popup/saved-search-popup.component.ts +++ b/src/frontend/app/ui/albums/saved-search-popup/saved-search-popup.component.ts @@ -1,23 +1,22 @@ -import {Component, Input, TemplateRef} from '@angular/core'; -import {BsModalService} from 'ngx-bootstrap/modal'; -import {BsModalRef} from 'ngx-bootstrap/modal/bs-modal-ref.service'; -import {SearchQueryDTO} from '../../../../../common/entities/SearchQueryDTO'; +import { Component, Input, TemplateRef } from '@angular/core'; +import { BsModalService } from 'ngx-bootstrap/modal'; +import { BsModalRef } from 'ngx-bootstrap/modal/bs-modal-ref.service'; +import { SearchQueryDTO } from '../../../../../common/entities/SearchQueryDTO'; @Component({ selector: 'app-saved-search-popup-btn', templateUrl: './saved-search-popup.component.html', - styleUrls: ['./saved-search-popup.component.css'] + styleUrls: ['./saved-search-popup.component.css'], }) export class SavedSearchPopupComponent { @Input() disabled: boolean; - @Input() savedSearchDTO: { name: string, searchQuery: SearchQueryDTO }; + @Input() savedSearchDTO: { name: string; searchQuery: SearchQueryDTO }; private modalRef: BsModalRef; - constructor(private modalService: BsModalService) { - } + constructor(private modalService: BsModalService) {} public async openModal(template: TemplateRef): Promise { - this.modalRef = this.modalService.show(template, {class: 'modal-lg'}); + this.modalRef = this.modalService.show(template, { class: 'modal-lg' }); document.body.style.paddingRight = '0px'; } @@ -25,7 +24,5 @@ export class SavedSearchPopupComponent { this.modalRef.hide(); this.modalRef = null; } - - } diff --git a/src/frontend/app/ui/duplicates/duplicates.component.css b/src/frontend/app/ui/duplicates/duplicates.component.css index 6d4eb798..c2783908 100644 --- a/src/frontend/app/ui/duplicates/duplicates.component.css +++ b/src/frontend/app/ui/duplicates/duplicates.component.css @@ -1,16 +1,16 @@ -.card{ +.card { margin: 8px 0; } -.row{ +.row { margin: 5px 0; cursor: pointer; } -.row:hover{ +.row:hover { background-color: #f8f9fa; } -a{ +a { color: #212529; } diff --git a/src/frontend/app/ui/duplicates/duplicates.component.ts b/src/frontend/app/ui/duplicates/duplicates.component.ts index b146fdfa..823d6829 100644 --- a/src/frontend/app/ui/duplicates/duplicates.component.ts +++ b/src/frontend/app/ui/duplicates/duplicates.component.ts @@ -1,13 +1,13 @@ -import {Component, HostListener, OnDestroy} from '@angular/core'; -import {DuplicateService} from './duplicates.service'; -import {Utils} from '../../../../common/Utils'; -import {QueryService} from '../../model/query.service'; -import {DuplicatesDTO} from '../../../../common/entities/DuplicatesDTO'; -import {DirectoryPathDTO} from '../../../../common/entities/DirectoryDTO'; -import {Subscription} from 'rxjs'; -import {Config} from '../../../../common/config/public/Config'; -import {PageHelper} from '../../model/page.helper'; -import {MediaDTO} from '../../../../common/entities/MediaDTO'; +import { Component, HostListener, OnDestroy } from '@angular/core'; +import { DuplicateService } from './duplicates.service'; +import { Utils } from '../../../../common/Utils'; +import { QueryService } from '../../model/query.service'; +import { DuplicatesDTO } from '../../../../common/entities/DuplicatesDTO'; +import { DirectoryPathDTO } from '../../../../common/entities/DirectoryDTO'; +import { Subscription } from 'rxjs'; +import { Config } from '../../../../common/config/public/Config'; +import { PageHelper } from '../../model/page.helper'; +import { MediaDTO } from '../../../../common/entities/MediaDTO'; interface GroupedDuplicate { name: string; @@ -17,69 +17,100 @@ interface GroupedDuplicate { @Component({ selector: 'app-duplicate', templateUrl: './duplicates.component.html', - styleUrls: ['./duplicates.component.css'] + styleUrls: ['./duplicates.component.css'], }) export class DuplicateComponent implements OnDestroy { - directoryGroups: GroupedDuplicate[] = null; renderedDirGroups: GroupedDuplicate[] = null; renderedIndex = { group: -1, - pairs: 0 + pairs: 0, }; subscription: Subscription; renderTimer: number = null; duplicateCount = { pairs: 0, - photos: 0 + photos: 0, }; - constructor(public duplicateService: DuplicateService, - public queryService: QueryService) { + constructor( + public duplicateService: DuplicateService, + public queryService: QueryService + ) { this.duplicateService.getDuplicates().catch(console.error); - this.subscription = this.duplicateService.duplicates.subscribe((duplicates: DuplicatesDTO[]): void => { - this.directoryGroups = []; - this.renderedIndex = {group: -1, pairs: 0}; - this.renderedDirGroups = []; - this.duplicateCount = { - pairs: 0, - photos: 0 - }; - if (duplicates === null) { - return; - } - this.duplicateCount.photos = duplicates.reduce((prev: number, curr): number => prev + curr.media.length, 0); - this.duplicateCount.pairs = duplicates.length; - - const getMostFrequentDir = (dupls: DuplicatesDTO[]): DirectoryPathDTO | null => { - if (dupls.length === 0) { - return null; + this.subscription = this.duplicateService.duplicates.subscribe( + (duplicates: DuplicatesDTO[]): void => { + this.directoryGroups = []; + this.renderedIndex = { group: -1, pairs: 0 }; + this.renderedDirGroups = []; + this.duplicateCount = { + pairs: 0, + photos: 0, + }; + if (duplicates === null) { + return; } - const dirFrequency: { [key: string]: { count: number, dir: DirectoryPathDTO } } = {}; - dupls.forEach((d): void => d.media.forEach((m): void => { - const k = Utils.concatUrls(m.directory.path, m.directory.name); - dirFrequency[k] = dirFrequency[k] || {dir: m.directory, count: 0}; - dirFrequency[k].count++; - })); - let max: { count: number, dir: DirectoryPathDTO } = {count: -1, dir: null}; - for (const freq of Object.values(dirFrequency)) { - if (max.count <= freq.count) { - max = freq; + this.duplicateCount.photos = duplicates.reduce( + (prev: number, curr): number => prev + curr.media.length, + 0 + ); + this.duplicateCount.pairs = duplicates.length; + + const getMostFrequentDir = ( + dupls: DuplicatesDTO[] + ): DirectoryPathDTO | null => { + if (dupls.length === 0) { + return null; } - } - return max.dir; - }; + const dirFrequency: { + [key: string]: { count: number; dir: DirectoryPathDTO }; + } = {}; + dupls.forEach((d): void => + d.media.forEach((m): void => { + const k = Utils.concatUrls(m.directory.path, m.directory.name); + dirFrequency[k] = dirFrequency[k] || { + dir: m.directory, + count: 0, + }; + dirFrequency[k].count++; + }) + ); + let max: { count: number; dir: DirectoryPathDTO } = { + count: -1, + dir: null, + }; + for (const freq of Object.values(dirFrequency)) { + if (max.count <= freq.count) { + max = freq; + } + } + return max.dir; + }; - while (duplicates.length > 0) { - const dir = getMostFrequentDir(duplicates); - const group = duplicates.filter((d): MediaDTO => - d.media.find((m): boolean => m.directory.name === dir.name && m.directory.path === dir.path)); - duplicates = duplicates.filter((d): boolean => - !d.media.find((m): boolean => m.directory.name === dir.name && m.directory.path === dir.path)); - this.directoryGroups.push({name: this.getDirectoryPath(dir) + ' (' + group.length + ')', duplicates: group}); + while (duplicates.length > 0) { + const dir = getMostFrequentDir(duplicates); + const group = duplicates.filter( + (d): MediaDTO => + d.media.find( + (m): boolean => + m.directory.name === dir.name && m.directory.path === dir.path + ) + ); + duplicates = duplicates.filter( + (d): boolean => + !d.media.find( + (m): boolean => + m.directory.name === dir.name && m.directory.path === dir.path + ) + ); + this.directoryGroups.push({ + name: this.getDirectoryPath(dir) + ' (' + group.length + ')', + duplicates: group, + }); + } + this.renderMore(); } - this.renderMore(); - }); + ); } ngOnDestroy(): void { @@ -103,38 +134,46 @@ export class DuplicateComponent implements OnDestroy { return; } - if (this.renderedIndex.group === this.directoryGroups.length - 1 && + if ( + this.renderedIndex.group === this.directoryGroups.length - 1 && this.renderedIndex.pairs >= - this.directoryGroups[this.renderedIndex.group].duplicates.length) { + this.directoryGroups[this.renderedIndex.group].duplicates.length + ) { return; } if (this.shouldRenderMore()) { - if (this.renderedDirGroups.length === 0 || + if ( + this.renderedDirGroups.length === 0 || this.renderedIndex.pairs >= - this.directoryGroups[this.renderedIndex.group].duplicates.length) { + this.directoryGroups[this.renderedIndex.group].duplicates.length + ) { this.renderedDirGroups.push({ name: this.directoryGroups[++this.renderedIndex.group].name, - duplicates: [] + duplicates: [], }); this.renderedIndex.pairs = 0; } - this.renderedDirGroups[this.renderedDirGroups.length - 1].duplicates - .push(this.directoryGroups[this.renderedIndex.group].duplicates[this.renderedIndex.pairs++]); + this.renderedDirGroups[this.renderedDirGroups.length - 1].duplicates.push( + this.directoryGroups[this.renderedIndex.group].duplicates[ + this.renderedIndex.pairs++ + ] + ); this.renderTimer = window.setTimeout(this.renderMore, 0); } }; - @HostListener('window:scroll') onScroll(): void { this.renderMore(); } private shouldRenderMore(): boolean { - return Config.Client.Other.enableOnScrollRendering === false || - PageHelper.ScrollY >= PageHelper.MaxScrollY * 0.7 - || (document.body.clientHeight) * 0.85 < window.innerHeight; + return ( + Config.Client.Other.enableOnScrollRendering === false || + PageHelper.ScrollY >= PageHelper.MaxScrollY * 0.7 || + document.body.clientHeight * 0.85 < window.innerHeight + ); } } diff --git a/src/frontend/app/ui/duplicates/duplicates.service.ts b/src/frontend/app/ui/duplicates/duplicates.service.ts index fa723bda..d7400db0 100644 --- a/src/frontend/app/ui/duplicates/duplicates.service.ts +++ b/src/frontend/app/ui/duplicates/duplicates.service.ts @@ -1,12 +1,10 @@ -import {Injectable} from '@angular/core'; -import {NetworkService} from '../../model/network/network.service'; -import {DuplicatesDTO} from '../../../../common/entities/DuplicatesDTO'; -import {BehaviorSubject} from 'rxjs'; - +import { Injectable } from '@angular/core'; +import { NetworkService } from '../../model/network/network.service'; +import { DuplicatesDTO } from '../../../../common/entities/DuplicatesDTO'; +import { BehaviorSubject } from 'rxjs'; @Injectable() export class DuplicateService { - public duplicates: BehaviorSubject; constructor(private networkService: NetworkService) { @@ -14,7 +12,8 @@ export class DuplicateService { } public async getDuplicates(): Promise { - this.duplicates.next(await this.networkService.getJson('/admin/duplicates')); + this.duplicates.next( + await this.networkService.getJson('/admin/duplicates') + ); } - } diff --git a/src/frontend/app/ui/duplicates/photo/photo.duplicates.component.ts b/src/frontend/app/ui/duplicates/photo/photo.duplicates.component.ts index 76aef444..a7d87357 100644 --- a/src/frontend/app/ui/duplicates/photo/photo.duplicates.component.ts +++ b/src/frontend/app/ui/duplicates/photo/photo.duplicates.component.ts @@ -1,31 +1,29 @@ -import {Component, Input, OnDestroy, OnInit} from '@angular/core'; -import {MediaDTO} from '../../../../../common/entities/MediaDTO'; -import {IconThumbnail, ThumbnailManagerService} from '../../gallery/thumbnailManager.service'; -import {MediaIcon} from '../../gallery/MediaIcon'; +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { MediaDTO } from '../../../../../common/entities/MediaDTO'; +import { + IconThumbnail, + ThumbnailManagerService, +} from '../../gallery/thumbnailManager.service'; +import { MediaIcon } from '../../gallery/MediaIcon'; @Component({ selector: 'app-duplicates-photo', templateUrl: './photo.duplicates.component.html', - styleUrls: ['./photo.duplicates.component.css'] + styleUrls: ['./photo.duplicates.component.css'], }) export class DuplicatesPhotoComponent implements OnInit, OnDestroy { @Input() media: MediaDTO; thumbnail: IconThumbnail; - - constructor(private thumbnailService: ThumbnailManagerService) { - } - + constructor(private thumbnailService: ThumbnailManagerService) {} ngOnInit(): void { this.thumbnail = this.thumbnailService.getIcon(new MediaIcon(this.media)); - } ngOnDestroy(): void { this.thumbnail.destroy(); } - } diff --git a/src/frontend/app/ui/faces/Person.ts b/src/frontend/app/ui/faces/Person.ts index 141ae06b..6f725a32 100644 --- a/src/frontend/app/ui/faces/Person.ts +++ b/src/frontend/app/ui/faces/Person.ts @@ -1,6 +1,6 @@ -import {PersonDTO} from '../../../../common/entities/PersonDTO'; -import {Config} from '../../../../common/config/public/Config'; -import {Utils} from '../../../../common/Utils'; +import { PersonDTO } from '../../../../common/entities/PersonDTO'; +import { Config } from '../../../../common/config/public/Config'; +import { Utils } from '../../../../common/Utils'; export class Person implements PersonDTO { isFavourite: boolean; @@ -8,11 +8,14 @@ export class Person implements PersonDTO { id: number; name: string; - - constructor() { - } + constructor() {} public static getThumbnailUrl(that: PersonDTO): string { - return Utils.concatUrls(Config.Client.urlBase, '/api/person/', encodeURIComponent(that.name), '/thumbnail'); + return Utils.concatUrls( + Config.Client.urlBase, + '/api/person/', + encodeURIComponent(that.name), + '/thumbnail' + ); } } diff --git a/src/frontend/app/ui/faces/face/face.component.ts b/src/frontend/app/ui/faces/face/face.component.ts index f889f839..c73ea478 100644 --- a/src/frontend/app/ui/faces/face/face.component.ts +++ b/src/frontend/app/ui/faces/face/face.component.ts @@ -1,12 +1,19 @@ -import {Component, Input, OnDestroy, OnInit} from '@angular/core'; -import {RouterLink} from '@angular/router'; -import {PersonDTO} from '../../../../../common/entities/PersonDTO'; -import {DomSanitizer, SafeStyle} from '@angular/platform-browser'; -import {PersonThumbnail, ThumbnailManagerService} from '../../gallery/thumbnailManager.service'; -import {FacesService} from '../faces.service'; -import {AuthenticationService} from '../../../model/network/authentication.service'; -import {Config} from '../../../../../common/config/public/Config'; -import {SearchQueryTypes, TextSearch, TextSearchQueryMatchTypes} from '../../../../../common/entities/SearchQueryDTO'; +import { Component, Input, OnDestroy, OnInit } from '@angular/core'; +import { RouterLink } from '@angular/router'; +import { PersonDTO } from '../../../../../common/entities/PersonDTO'; +import { DomSanitizer, SafeStyle } from '@angular/platform-browser'; +import { + PersonThumbnail, + ThumbnailManagerService, +} from '../../gallery/thumbnailManager.service'; +import { FacesService } from '../faces.service'; +import { AuthenticationService } from '../../../model/network/authentication.service'; +import { Config } from '../../../../../common/config/public/Config'; +import { + SearchQueryTypes, + TextSearch, + TextSearchQueryMatchTypes, +} from '../../../../../common/entities/SearchQueryDTO'; @Component({ selector: 'app-face', @@ -21,15 +28,18 @@ export class FaceComponent implements OnInit, OnDestroy { public thumbnail: PersonThumbnail = null; public searchQueryDTOstr: string; - constructor(private thumbnailService: ThumbnailManagerService, - private sanitizer: DomSanitizer, - private faceService: FacesService, - public authenticationService: AuthenticationService) { - - } + constructor( + private thumbnailService: ThumbnailManagerService, + private sanitizer: DomSanitizer, + private faceService: FacesService, + public authenticationService: AuthenticationService + ) {} get CanUpdate(): boolean { - return this.authenticationService.user.getValue().role >= Config.Client.Faces.writeAccessMinRole; + return ( + this.authenticationService.user.getValue().role >= + Config.Client.Faces.writeAccessMinRole + ); } ngOnInit(): void { @@ -37,16 +47,18 @@ export class FaceComponent implements OnInit, OnDestroy { this.searchQueryDTOstr = JSON.stringify({ type: SearchQueryTypes.person, text: this.person.name, - matchType: TextSearchQueryMatchTypes.exact_match + matchType: TextSearchQueryMatchTypes.exact_match, } as TextSearch); - } getSanitizedThUrl(): SafeStyle { - return this.sanitizer.bypassSecurityTrustStyle('url(' + this.thumbnail.Src - .replace(/\(/g, '%28') - .replace(/'/g, '%27') - .replace(/\)/g, '%29') + ')'); + return this.sanitizer.bypassSecurityTrustStyle( + 'url(' + + this.thumbnail.Src.replace(/\(/g, '%28') + .replace(/'/g, '%27') + .replace(/\)/g, '%29') + + ')' + ); } ngOnDestroy(): void { @@ -58,7 +70,9 @@ export class FaceComponent implements OnInit, OnDestroy { async toggleFavourite($event: MouseEvent): Promise { $event.preventDefault(); $event.stopPropagation(); - await this.faceService.setFavourite(this.person, !this.person.isFavourite).catch(console.error); + await this.faceService + .setFavourite(this.person, !this.person.isFavourite) + .catch(console.error); } } diff --git a/src/frontend/app/ui/faces/faces.component.css b/src/frontend/app/ui/faces/faces.component.css index 4b7c6d99..87d1f048 100644 --- a/src/frontend/app/ui/faces/faces.component.css +++ b/src/frontend/app/ui/faces/faces.component.css @@ -3,11 +3,11 @@ app-face { display: inline-block; } -.no-face-msg{ +.no-face-msg { height: 100vh; text-align: center; } -.no-face-msg h2{ +.no-face-msg h2 { color: #6c757d; } diff --git a/src/frontend/app/ui/faces/faces.component.html b/src/frontend/app/ui/faces/faces.component.html index 47be3ed0..d5a02034 100644 --- a/src/frontend/app/ui/faces/faces.component.html +++ b/src/frontend/app/ui/faces/faces.component.html @@ -12,7 +12,8 @@
-

:( No faces to show. +

:( + No faces to show.

diff --git a/src/frontend/app/ui/faces/faces.component.ts b/src/frontend/app/ui/faces/faces.component.ts index 45aedad9..e7a35cc6 100644 --- a/src/frontend/app/ui/faces/faces.component.ts +++ b/src/frontend/app/ui/faces/faces.component.ts @@ -1,39 +1,37 @@ -import {Component, ElementRef, OnInit, ViewChild} from '@angular/core'; -import {FacesService} from './faces.service'; -import {QueryService} from '../../model/query.service'; -import {map} from 'rxjs/operators'; -import {PersonDTO} from '../../../../common/entities/PersonDTO'; -import {Observable} from 'rxjs'; +import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'; +import { FacesService } from './faces.service'; +import { QueryService } from '../../model/query.service'; +import { map } from 'rxjs/operators'; +import { PersonDTO } from '../../../../common/entities/PersonDTO'; +import { Observable } from 'rxjs'; @Component({ selector: 'app-faces', templateUrl: './faces.component.html', - styleUrls: ['./faces.component.css'] + styleUrls: ['./faces.component.css'], }) export class FacesComponent implements OnInit { - @ViewChild('container', {static: true}) container: ElementRef; + @ViewChild('container', { static: true }) container: ElementRef; public size: number; favourites: Observable; nonFavourites: Observable; - constructor(public facesService: FacesService, - public queryService: QueryService) { + constructor( + public facesService: FacesService, + public queryService: QueryService + ) { this.facesService.getPersons().catch(console.error); const personCmp = (p1: PersonDTO, p2: PersonDTO) => { return p1.name.localeCompare(p2.name); }; this.favourites = this.facesService.persons.pipe( - map(value => value.filter(p => p.isFavourite) - .sort(personCmp)) + map((value) => value.filter((p) => p.isFavourite).sort(personCmp)) ); this.nonFavourites = this.facesService.persons.pipe( - map(value => - value.filter(p => !p.isFavourite) - .sort(personCmp)) + map((value) => value.filter((p) => !p.isFavourite).sort(personCmp)) ); } - ngOnInit(): void { this.updateSize(); } @@ -42,9 +40,7 @@ export class FacesComponent implements OnInit { const size = 220 + 5; // body - container margin const containerWidth = this.container.nativeElement.clientWidth - 30; - this.size = (containerWidth / Math.round((containerWidth / size))) - 5; + this.size = containerWidth / Math.round(containerWidth / size) - 5; } - - } diff --git a/src/frontend/app/ui/faces/faces.service.ts b/src/frontend/app/ui/faces/faces.service.ts index c0605956..9b4dfbdb 100644 --- a/src/frontend/app/ui/faces/faces.service.ts +++ b/src/frontend/app/ui/faces/faces.service.ts @@ -1,8 +1,7 @@ -import {Injectable} from '@angular/core'; -import {NetworkService} from '../../model/network/network.service'; -import {BehaviorSubject} from 'rxjs'; -import {PersonDTO} from '../../../../common/entities/PersonDTO'; - +import { Injectable } from '@angular/core'; +import { NetworkService } from '../../model/network/network.service'; +import { BehaviorSubject } from 'rxjs'; +import { PersonDTO } from '../../../../common/entities/PersonDTO'; @Injectable() export class FacesService { @@ -12,8 +11,14 @@ export class FacesService { this.persons = new BehaviorSubject([]); } - public async setFavourite(person: PersonDTO, isFavourite: boolean): Promise { - const updated = await this.networkService.postJson('/person/' + person.name, {isFavourite}); + public async setFavourite( + person: PersonDTO, + isFavourite: boolean + ): Promise { + const updated = await this.networkService.postJson( + '/person/' + person.name, + { isFavourite } + ); const updatesList = this.persons.getValue(); for (let i = 0; i < updatesList.length; i++) { if (updatesList[i].id === updated.id) { @@ -25,7 +30,10 @@ export class FacesService { } public async getPersons(): Promise { - this.persons.next((await this.networkService.getJson('/person')).sort((a, b): number => a.name.localeCompare(b.name))); + this.persons.next( + (await this.networkService.getJson('/person')).sort( + (a, b): number => a.name.localeCompare(b.name) + ) + ); } - } diff --git a/src/frontend/app/ui/frame/frame.component.css b/src/frontend/app/ui/frame/frame.component.css index 1b430cef..5cef9769 100644 --- a/src/frontend/app/ui/frame/frame.component.css +++ b/src/frontend/app/ui/frame/frame.component.css @@ -74,7 +74,7 @@ a.dropdown-item, button.dropdown-item { padding: 0.3rem 1.0rem 0.3rem 0.8rem; } -a.dropdown-item span, button.dropdown-item span { +a.dropdown-item span, button.dropdown-item span { padding-right: 0.8rem; } diff --git a/src/frontend/app/ui/frame/frame.component.html b/src/frontend/app/ui/frame/frame.component.html index c17d335b..4b788ddd 100644 --- a/src/frontend/app/ui/frame/frame.component.html +++ b/src/frontend/app/ui/frame/frame.component.html @@ -12,7 +12,7 @@ diff --git a/src/frontend/app/ui/gallery/gallery.component.ts b/src/frontend/app/ui/gallery/gallery.component.ts index 116db73e..17655847 100644 --- a/src/frontend/app/ui/gallery/gallery.component.ts +++ b/src/frontend/app/ui/gallery/gallery.component.ts @@ -1,29 +1,33 @@ -import {Component, OnDestroy, OnInit, ViewChild} from '@angular/core'; -import {AuthenticationService} from '../../model/network/authentication.service'; -import {ActivatedRoute, Params, Router} from '@angular/router'; -import {ContentService, ContentWrapperWithError, DirectoryContent} from './content.service'; -import {GalleryGridComponent} from './grid/grid.gallery.component'; -import {Config} from '../../../../common/config/public/Config'; -import {ShareService} from './share.service'; -import {NavigationService} from '../../model/navigation.service'; -import {UserRoles} from '../../../../common/entities/UserDTO'; -import {interval, Observable, Subscription} from 'rxjs'; -import {PageHelper} from '../../model/page.helper'; -import {PhotoDTO} from '../../../../common/entities/PhotoDTO'; -import {QueryParams} from '../../../../common/QueryParams'; -import {take} from 'rxjs/operators'; -import {GallerySortingService} from './navigator/sorting.service'; -import {MediaDTO} from '../../../../common/entities/MediaDTO'; -import {FilterService} from './filter/filter.service'; +import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'; +import { AuthenticationService } from '../../model/network/authentication.service'; +import { ActivatedRoute, Params, Router } from '@angular/router'; +import { + ContentService, + ContentWrapperWithError, + DirectoryContent, +} from './content.service'; +import { GalleryGridComponent } from './grid/grid.gallery.component'; +import { Config } from '../../../../common/config/public/Config'; +import { ShareService } from './share.service'; +import { NavigationService } from '../../model/navigation.service'; +import { UserRoles } from '../../../../common/entities/UserDTO'; +import { interval, Observable, Subscription } from 'rxjs'; +import { PageHelper } from '../../model/page.helper'; +import { PhotoDTO } from '../../../../common/entities/PhotoDTO'; +import { QueryParams } from '../../../../common/QueryParams'; +import { take } from 'rxjs/operators'; +import { GallerySortingService } from './navigator/sorting.service'; +import { MediaDTO } from '../../../../common/entities/MediaDTO'; +import { FilterService } from './filter/filter.service'; @Component({ selector: 'app-gallery', templateUrl: './gallery.component.html', - styleUrls: ['./gallery.component.css'] + styleUrls: ['./gallery.component.css'], }) export class GalleryComponent implements OnInit, OnDestroy { - - @ViewChild(GalleryGridComponent, {static: false}) grid: GalleryGridComponent; + @ViewChild(GalleryGridComponent, { static: false }) + grid: GalleryGridComponent; public showSearchBar = false; public showShare = false; @@ -32,7 +36,12 @@ export class GalleryComponent implements OnInit, OnDestroy { config = Config; public isPhotoWithLocation = false; - public countDown: { day: number, hour: number, minute: number, second: number } = null; + public countDown: { + day: number; + hour: number; + minute: number; + second: number; + } = null; public readonly mapEnabled: boolean; public directoryContent: DirectoryContent; public readonly mediaObs: Observable; @@ -41,22 +50,23 @@ export class GalleryComponent implements OnInit, OnDestroy { content: null, route: null, timer: null, - sorting: null + sorting: null, }; - constructor(public galleryService: ContentService, - private authService: AuthenticationService, - private router: Router, - private shareService: ShareService, - private route: ActivatedRoute, - private navigation: NavigationService, - private filterService: FilterService, - private sortingService: GallerySortingService) { + constructor( + public galleryService: ContentService, + private authService: AuthenticationService, + private router: Router, + private shareService: ShareService, + private route: ActivatedRoute, + private navigation: NavigationService, + private filterService: FilterService, + private sortingService: GallerySortingService + ) { this.mapEnabled = Config.Client.Map.enabled; PageHelper.showScrollY(); } - get ContentWrapper(): ContentWrapperWithError { return this.galleryService.content.value; } @@ -66,12 +76,20 @@ export class GalleryComponent implements OnInit, OnDestroy { return; } // if the timer is longer than 10 years, just do not show it - if ((this.shareService.sharingSubject.value.expires - Date.now()) / 1000 / 86400 / 365 > 10) { + if ( + (this.shareService.sharingSubject.value.expires - Date.now()) / + 1000 / + 86400 / + 365 > + 10 + ) { return; } - t = Math.floor((this.shareService.sharingSubject.value.expires - Date.now()) / 1000); - this.countDown = ({} as any); + t = Math.floor( + (this.shareService.sharingSubject.value.expires - Date.now()) / 1000 + ); + this.countDown = {} as any; this.countDown.day = Math.floor(t / 86400); t -= this.countDown.day * 86400; this.countDown.hour = Math.floor(t / 3600) % 24; @@ -98,24 +116,36 @@ export class GalleryComponent implements OnInit, OnDestroy { async ngOnInit(): Promise { await this.shareService.wait(); - if (!this.authService.isAuthenticated() && + if ( + !this.authService.isAuthenticated() && (!this.shareService.isSharing() || - (this.shareService.isSharing() && Config.Client.Sharing.passwordProtected === true))) { - + (this.shareService.isSharing() && + Config.Client.Sharing.passwordProtected === true)) + ) { return this.navigation.toLogin(); } - this.showSearchBar = Config.Client.Search.enabled && this.authService.canSearch(); - this.showShare = Config.Client.Sharing.enabled && this.authService.isAuthorized(UserRoles.User); - this.showRandomPhotoBuilder = Config.Client.RandomPhoto.enabled && this.authService.isAuthorized(UserRoles.User); - this.subscription.content = this.sortingService.applySorting( - this.filterService.applyFilters(this.galleryService.directoryContent)).subscribe((dc: DirectoryContent) => { - this.onContentChange(dc); - }); + this.showSearchBar = + Config.Client.Search.enabled && this.authService.canSearch(); + this.showShare = + Config.Client.Sharing.enabled && + this.authService.isAuthorized(UserRoles.User); + this.showRandomPhotoBuilder = + Config.Client.RandomPhoto.enabled && + this.authService.isAuthorized(UserRoles.User); + this.subscription.content = this.sortingService + .applySorting( + this.filterService.applyFilters(this.galleryService.directoryContent) + ) + .subscribe((dc: DirectoryContent) => { + this.onContentChange(dc); + }); this.subscription.route = this.route.params.subscribe(this.onRoute); if (this.shareService.isSharing()) { this.$counter = interval(1000); - this.subscription.timer = this.$counter.subscribe((x): void => this.updateTimer(x)); + this.subscription.timer = this.$counter.subscribe((x): void => + this.updateTimer(x) + ); } /* this.subscription.sorting = this.galleryService.sorting.subscribe((): void => { @@ -127,17 +157,24 @@ export class GalleryComponent implements OnInit, OnDestroy { private onRoute = async (params: Params): Promise => { const searchQuery = params[QueryParams.gallery.search.query]; if (searchQuery) { - this.galleryService.search(searchQuery).catch(console.error); return; } - if (params[QueryParams.gallery.sharingKey_params] && params[QueryParams.gallery.sharingKey_params] !== '') { - const sharing = await this.shareService.currentSharing.pipe(take(1)).toPromise(); + if ( + params[QueryParams.gallery.sharingKey_params] && + params[QueryParams.gallery.sharingKey_params] !== '' + ) { + const sharing = await this.shareService.currentSharing + .pipe(take(1)) + .toPromise(); const qParams: { [key: string]: any } = {}; - qParams[QueryParams.gallery.sharingKey_query] = this.shareService.getSharingKey(); - this.router.navigate(['/gallery', sharing.path], {queryParams: qParams}).catch(console.error); + qParams[QueryParams.gallery.sharingKey_query] = + this.shareService.getSharingKey(); + this.router + .navigate(['/gallery', sharing.path], { queryParams: qParams }) + .catch(console.error); return; } @@ -158,7 +195,8 @@ export class GalleryComponent implements OnInit, OnDestroy { this.isPhotoWithLocation = false; for (const media of content.media as PhotoDTO[]) { - if (media.metadata && + if ( + media.metadata && media.metadata.positionData && media.metadata.positionData.GPSData && media.metadata.positionData.GPSData.longitude diff --git a/src/frontend/app/ui/gallery/grid/GridMedia.ts b/src/frontend/app/ui/gallery/grid/GridMedia.ts index ac26fccf..3b76736b 100644 --- a/src/frontend/app/ui/gallery/grid/GridMedia.ts +++ b/src/frontend/app/ui/gallery/grid/GridMedia.ts @@ -1,12 +1,18 @@ -import {Media} from '../Media'; -import {MediaDTO, MediaDTOUtils} from '../../../../../common/entities/MediaDTO'; -import {PhotoDTO} from '../../../../../common/entities/PhotoDTO'; -import {VideoDTO} from '../../../../../common/entities/VideoDTO'; +import { Media } from '../Media'; +import { + MediaDTO, + MediaDTOUtils, +} from '../../../../../common/entities/MediaDTO'; +import { PhotoDTO } from '../../../../../common/entities/PhotoDTO'; +import { VideoDTO } from '../../../../../common/entities/VideoDTO'; export class GridMedia extends Media { - - - constructor(media: MediaDTO, renderWidth: number, renderHeight: number, public rowId: number) { + constructor( + media: MediaDTO, + renderWidth: number, + renderHeight: number, + public rowId: number + ) { super(media, renderWidth, renderHeight); } diff --git a/src/frontend/app/ui/gallery/grid/GridRowBuilder.ts b/src/frontend/app/ui/gallery/grid/GridRowBuilder.ts index 369dad87..0659b3fc 100644 --- a/src/frontend/app/ui/gallery/grid/GridRowBuilder.ts +++ b/src/frontend/app/ui/gallery/grid/GridRowBuilder.ts @@ -1,19 +1,21 @@ -import {MediaDTO} from '../../../../../common/entities/MediaDTO'; +import { MediaDTO } from '../../../../../common/entities/MediaDTO'; export class GridRowBuilder { - private photoRow: MediaDTO[] = []; private photoIndex = 0; // index of the last pushed media to the photoRow - - constructor(private photos: MediaDTO[], - private startIndex: number, - private photoMargin: number, - private containerWidth: number) { + constructor( + private photos: MediaDTO[], + private startIndex: number, + private photoMargin: number, + private containerWidth: number + ) { this.photoIndex = startIndex; if (this.containerWidth <= 0) { - throw new Error('container width cant be <=0, got:' + this.containerWidth); + throw new Error( + 'container width cant be <=0, got:' + this.containerWidth + ); } } @@ -37,10 +39,12 @@ export class GridRowBuilder { } public adjustRowHeightBetween(minHeight: number, maxHeight: number): void { - while (this.calcRowHeight() > maxHeight && this.addPhoto() === true) { // row too high -> add more images + while (this.calcRowHeight() > maxHeight && this.addPhoto() === true) { + // row too high -> add more images } - while (this.calcRowHeight() < minHeight && this.removePhoto() === true) { // roo too small -> remove images + while (this.calcRowHeight() < minHeight && this.removePhoto() === true) { + // roo too small -> remove images } // keep at least one media int thr row @@ -53,11 +57,15 @@ export class GridRowBuilder { let width = 0; for (const item of this.photoRow) { const size = item.metadata.size; - width += (size.width / size.height); // summing up aspect ratios + width += size.width / size.height; // summing up aspect ratios } - const height = (this.containerWidth - this.photoRow.length * (this.photoMargin * 2) - 1) / width; // cant be equal -> width-1 + const height = + (this.containerWidth - + this.photoRow.length * (this.photoMargin * 2) - + 1) / + width; // cant be equal -> width-1 - return height + (this.photoMargin * 2); + return height + this.photoMargin * 2; } private addPhoto(): boolean { diff --git a/src/frontend/app/ui/gallery/grid/grid.gallery.component.ts b/src/frontend/app/ui/gallery/grid/grid.gallery.component.ts index fb500537..487b93ca 100644 --- a/src/frontend/app/ui/gallery/grid/grid.gallery.component.ts +++ b/src/frontend/app/ui/gallery/grid/grid.gallery.component.ts @@ -10,31 +10,36 @@ import { OnInit, QueryList, ViewChild, - ViewChildren + ViewChildren, } from '@angular/core'; -import {GridRowBuilder} from './GridRowBuilder'; -import {GalleryLightboxComponent} from '../lightbox/lightbox.gallery.component'; -import {GridMedia} from './GridMedia'; -import {GalleryPhotoComponent} from './photo/photo.grid.gallery.component'; -import {OverlayService} from '../overlay.service'; -import {Config} from '../../../../../common/config/public/Config'; -import {PageHelper} from '../../../model/page.helper'; -import {Subscription} from 'rxjs'; -import {ActivatedRoute, Params, Router} from '@angular/router'; -import {QueryService} from '../../../model/query.service'; -import {ContentService} from '../content.service'; -import {MediaDTO, MediaDTOUtils} from '../../../../../common/entities/MediaDTO'; -import {QueryParams} from '../../../../../common/QueryParams'; +import { GridRowBuilder } from './GridRowBuilder'; +import { GalleryLightboxComponent } from '../lightbox/lightbox.gallery.component'; +import { GridMedia } from './GridMedia'; +import { GalleryPhotoComponent } from './photo/photo.grid.gallery.component'; +import { OverlayService } from '../overlay.service'; +import { Config } from '../../../../../common/config/public/Config'; +import { PageHelper } from '../../../model/page.helper'; +import { Subscription } from 'rxjs'; +import { ActivatedRoute, Params, Router } from '@angular/router'; +import { QueryService } from '../../../model/query.service'; +import { ContentService } from '../content.service'; +import { + MediaDTO, + MediaDTOUtils, +} from '../../../../../common/entities/MediaDTO'; +import { QueryParams } from '../../../../../common/QueryParams'; @Component({ selector: 'app-gallery-grid', templateUrl: './grid.gallery.component.html', styleUrls: ['./grid.gallery.component.css'], }) -export class GalleryGridComponent implements OnInit, OnChanges, AfterViewInit, OnDestroy { - - @ViewChild('gridContainer', {static: false}) gridContainer: ElementRef; - @ViewChildren(GalleryPhotoComponent) gridPhotoQL: QueryList; +export class GalleryGridComponent + implements OnInit, OnChanges, AfterViewInit, OnDestroy +{ + @ViewChild('gridContainer', { static: false }) gridContainer: ElementRef; + @ViewChildren(GalleryPhotoComponent) + gridPhotoQL: QueryList; @Input() lightbox: GalleryLightboxComponent; @Input() media: MediaDTO[]; photosToRender: GridMedia[] = []; @@ -43,9 +48,9 @@ export class GalleryGridComponent implements OnInit, OnChanges, AfterViewInit, O public IMAGE_MARGIN = 2; isAfterViewInit = false; subscriptions: { - route: Subscription + route: Subscription; } = { - route: null + route: null, }; delayedRenderUpToPhoto: string = null; private scrollListenerPhotos: GalleryPhotoComponent[] = []; @@ -56,30 +61,35 @@ export class GalleryGridComponent implements OnInit, OnChanges, AfterViewInit, O private helperTime: number = null; private renderedPhotoIndex = 0; - constructor(private overlayService: OverlayService, - private changeDetector: ChangeDetectorRef, - public queryService: QueryService, - private router: Router, - public galleryService: ContentService, - private route: ActivatedRoute) { - - } + constructor( + private overlayService: OverlayService, + private changeDetector: ChangeDetectorRef, + public queryService: QueryService, + private router: Router, + public galleryService: ContentService, + private route: ActivatedRoute + ) {} ngOnChanges(): void { this.onChange(); } ngOnInit(): void { - this.subscriptions.route = this.route.queryParams.subscribe((params: Params): void => { - if (params[QueryParams.gallery.photo] && params[QueryParams.gallery.photo] !== '') { - this.delayedRenderUpToPhoto = params[QueryParams.gallery.photo]; - if (!this.media || this.media.length === 0) { - return; - } + this.subscriptions.route = this.route.queryParams.subscribe( + (params: Params): void => { + if ( + params[QueryParams.gallery.photo] && + params[QueryParams.gallery.photo] !== '' + ) { + this.delayedRenderUpToPhoto = params[QueryParams.gallery.photo]; + if (!this.media || this.media.length === 0) { + return; + } - this.renderUpToMedia(params[QueryParams.gallery.photo]); + this.renderUpToMedia(params[QueryParams.gallery.photo]); + } } - }); + ); } onChange = () => { @@ -96,9 +106,7 @@ export class GalleryGridComponent implements OnInit, OnChanges, AfterViewInit, O }, 0); }; - ngOnDestroy(): void { - if (this.helperTime != null) { clearTimeout(this.helperTime); } @@ -123,7 +131,9 @@ export class GalleryGridComponent implements OnInit, OnChanges, AfterViewInit, O } photoClicked(media: MediaDTO): void { - this.router.navigate([], {queryParams: this.queryService.getParams(media)}); + this.router.navigate([], { + queryParams: this.queryService.getParams(media), + }); } ngAfterViewInit(): void { @@ -131,7 +141,9 @@ export class GalleryGridComponent implements OnInit, OnChanges, AfterViewInit, O if (Config.Client.Other.enableOnScrollThumbnailPrioritising === true) { this.gridPhotoQL.changes.subscribe((): void => { - this.scrollListenerPhotos = this.gridPhotoQL.filter((pc): boolean => pc.ScrollListener); + this.scrollListenerPhotos = this.gridPhotoQL.filter( + (pc): boolean => pc.ScrollListener + ); }); } @@ -144,15 +156,18 @@ export class GalleryGridComponent implements OnInit, OnChanges, AfterViewInit, O } public renderARow(): number { - if (this.renderedPhotoIndex >= this.media.length - || this.containerWidth === 0) { + if ( + this.renderedPhotoIndex >= this.media.length || + this.containerWidth === 0 + ) { return null; } let maxRowHeight = this.getMaxRowHeight(); const minRowHeight = this.screenHeight / this.MAX_ROW_COUNT; - const photoRowBuilder = new GridRowBuilder(this.media, + const photoRowBuilder = new GridRowBuilder( + this.media, this.renderedPhotoIndex, this.IMAGE_MARGIN, this.containerWidth - this.overlayService.getPhantomScrollbarWidth() @@ -166,11 +181,13 @@ export class GalleryGridComponent implements OnInit, OnChanges, AfterViewInit, O maxRowHeight *= 1.2; } const rowHeight = Math.min(photoRowBuilder.calcRowHeight(), maxRowHeight); - const imageHeight = rowHeight - (this.IMAGE_MARGIN * 2); + const imageHeight = rowHeight - this.IMAGE_MARGIN * 2; photoRowBuilder.getPhotoRow().forEach((photo): void => { const imageWidth = imageHeight * MediaDTOUtils.calcAspectRatio(photo); - this.photosToRender.push(new GridMedia(photo, imageWidth, imageHeight, this.renderedPhotoIndex)); + this.photosToRender.push( + new GridMedia(photo, imageWidth, imageHeight, this.renderedPhotoIndex) + ); }); this.renderedPhotoIndex += photoRowBuilder.getPhotoRow().length; @@ -179,18 +196,25 @@ export class GalleryGridComponent implements OnInit, OnChanges, AfterViewInit, O @HostListener('window:scroll') onScroll(): void { - if (!this.onScrollFired && + if ( + !this.onScrollFired && this.media && // should we trigger this at all? - (this.renderedPhotoIndex < this.media.length || this.scrollListenerPhotos.length > 0)) { + (this.renderedPhotoIndex < this.media.length || + this.scrollListenerPhotos.length > 0) + ) { window.requestAnimationFrame((): void => { this.renderPhotos(); if (Config.Client.Other.enableOnScrollThumbnailPrioritising === true) { - this.scrollListenerPhotos.forEach((pc: GalleryPhotoComponent): void => { - pc.onScroll(); - }); - this.scrollListenerPhotos = this.scrollListenerPhotos.filter((pc): boolean => pc.ScrollListener); + this.scrollListenerPhotos.forEach( + (pc: GalleryPhotoComponent): void => { + pc.onScroll(); + } + ); + this.scrollListenerPhotos = this.scrollListenerPhotos.filter( + (pc): boolean => pc.ScrollListener + ); } this.onScrollFired = false; @@ -207,17 +231,21 @@ export class GalleryGridComponent implements OnInit, OnChanges, AfterViewInit, O * Makes sure that the photo with the given mediaString is visible on the screen */ private renderUpToMedia(mediaStringId: string): void { - const index = this.media.findIndex((p): boolean => this.queryService.getMediaStringId(p) === mediaStringId); + const index = this.media.findIndex( + (p): boolean => this.queryService.getMediaStringId(p) === mediaStringId + ); if (index === -1) { - this.router.navigate([], {queryParams: this.queryService.getParams()}); + this.router.navigate([], { queryParams: this.queryService.getParams() }); return; } // Make sure that at leas one more photo is rendered // It is possible that only the last few pixels of a photo is visible, // so not required to render more, but the scrollbar does not trigger more photos to render // (on ligthbox navigation) - while (this.renderedPhotoIndex - 1 < (index + 1) && this.renderARow() !== null) { - } + while ( + this.renderedPhotoIndex - 1 < index + 1 && + this.renderARow() !== null + ) {} } private clearRenderedPhotos(): void { @@ -234,7 +262,6 @@ export class GalleryGridComponent implements OnInit, OnChanges, AfterViewInit, O let lastRowId = null; let i = 0; for (; i < this.media.length && i < this.photosToRender.length; ++i) { - // If a media changed the whole row has to be removed if (this.photosToRender[i].rowId !== lastRowId) { lastSameIndex = i; @@ -245,19 +272,23 @@ export class GalleryGridComponent implements OnInit, OnChanges, AfterViewInit, O } } // if all the same - if (this.photosToRender.length > 0 && + if ( + this.photosToRender.length > 0 && i === this.photosToRender.length && i === this.media.length && - this.photosToRender[i - 1].equals(this.media[i - 1])) { + this.photosToRender[i - 1].equals(this.media[i - 1]) + ) { lastSameIndex = i; } if (lastSameIndex > 0) { - this.photosToRender.splice(lastSameIndex, this.photosToRender.length - lastSameIndex); + this.photosToRender.splice( + lastSameIndex, + this.photosToRender.length - lastSameIndex + ); this.renderedPhotoIndex = lastSameIndex; } else { this.clearRenderedPhotos(); } - } /** @@ -268,27 +299,36 @@ export class GalleryGridComponent implements OnInit, OnChanges, AfterViewInit, O */ private shouldRenderMore(offset: number = 0): boolean { const bottomOffset = this.getMaxRowHeight() * 2; - return Config.Client.Other.enableOnScrollRendering === false || - PageHelper.ScrollY >= (document.body.clientHeight + offset - window.innerHeight) - bottomOffset - || (document.body.clientHeight + offset) * 0.85 < window.innerHeight; + return ( + Config.Client.Other.enableOnScrollRendering === false || + PageHelper.ScrollY >= + document.body.clientHeight + + offset - + window.innerHeight - + bottomOffset || + (document.body.clientHeight + offset) * 0.85 < window.innerHeight + ); } private renderPhotos(numberOfPhotos: number = 0): void { if (!this.media) { return; } - if (this.containerWidth === 0 || + if ( + this.containerWidth === 0 || this.renderedPhotoIndex >= this.media.length || - !this.shouldRenderMore()) { + !this.shouldRenderMore() + ) { return; } - let renderedContentHeight = 0; - while (this.renderedPhotoIndex < this.media.length && - (this.shouldRenderMore(renderedContentHeight) === true || - this.renderedPhotoIndex < numberOfPhotos)) { + while ( + this.renderedPhotoIndex < this.media.length && + (this.shouldRenderMore(renderedContentHeight) === true || + this.renderedPhotoIndex < numberOfPhotos) + ) { const ret = this.renderARow(); if (ret === null) { throw new Error('Grid media rendering failed'); @@ -305,9 +345,11 @@ export class GalleryGridComponent implements OnInit, OnChanges, AfterViewInit, O const pre = PageHelper.isScrollYVisible(); PageHelper.showScrollY(); // if the width changed a bit or the height changed a lot - if (this.containerWidth !== this.gridContainer.nativeElement.clientWidth - || this.screenHeight < window.innerHeight * 0.75 - || this.screenHeight > window.innerHeight * 1.25) { + if ( + this.containerWidth !== this.gridContainer.nativeElement.clientWidth || + this.screenHeight < window.innerHeight * 0.75 || + this.screenHeight > window.innerHeight * 1.25 + ) { this.screenHeight = window.innerHeight; this.containerWidth = this.gridContainer.nativeElement.clientWidth; this.clearRenderedPhotos(); @@ -322,6 +364,4 @@ export class GalleryGridComponent implements OnInit, OnChanges, AfterViewInit, O } return false; } - - } diff --git a/src/frontend/app/ui/gallery/grid/photo/loading/loading.photo.grid.gallery.component.ts b/src/frontend/app/ui/gallery/grid/photo/loading/loading.photo.grid.gallery.component.ts index e7cf02bc..aec0fe61 100644 --- a/src/frontend/app/ui/gallery/grid/photo/loading/loading.photo.grid.gallery.component.ts +++ b/src/frontend/app/ui/gallery/grid/photo/loading/loading.photo.grid.gallery.component.ts @@ -1,4 +1,4 @@ -import {Component, Input} from '@angular/core'; +import { Component, Input } from '@angular/core'; @Component({ selector: 'app-gallery-grid-photo-loading', @@ -6,10 +6,7 @@ import {Component, Input} from '@angular/core'; styleUrls: ['./loading.photo.grid.gallery.component.css'], }) export class GalleryPhotoLoadingComponent { - @Input() animate: boolean; @Input() error: boolean; - - } diff --git a/src/frontend/app/ui/gallery/grid/photo/photo.grid.gallery.component.html b/src/frontend/app/ui/gallery/grid/photo/photo.grid.gallery.component.html index 846a94ca..7cafeb51 100644 --- a/src/frontend/app/ui/gallery/grid/photo/photo.grid.gallery.component.html +++ b/src/frontend/app/ui/gallery/grid/photo/photo.grid.gallery.component.html @@ -17,7 +17,7 @@
+ *ngIf="infoBarVisible">
{{Title}}
@@ -36,7 +36,8 @@ # + {{keyword.value}} # @@ -131,12 +132,12 @@ [name]="'allowParallelRun'+'_'+i" [id]="'allowParallelRun'+'_'+i" switch-on-color="primary" - switch-inverse="true" + [switch-inverse]="true" switch-off-text="Disabled" switch-on-text="Enabled" i18n-switch-off-text i18n-switch-on-text - switch-handle-width="100" - switch-label-width="20" + [switch-handle-width]="100" + [switch-label-width]="20" [(ngModel)]="schedule.allowParallelRun"> diff --git a/src/frontend/app/ui/settings/jobs/jobs.settings.component.ts b/src/frontend/app/ui/settings/jobs/jobs.settings.component.ts index ca4c2d7a..f03f7ea8 100644 --- a/src/frontend/app/ui/settings/jobs/jobs.settings.component.ts +++ b/src/frontend/app/ui/settings/jobs/jobs.settings.component.ts @@ -1,4 +1,4 @@ -import {Component, OnChanges, OnDestroy, OnInit, ViewChild} from '@angular/core'; +import {Component, OnChanges, OnDestroy, OnInit, ViewChild,} from '@angular/core'; import {JobsSettingsService} from './jobs.settings.service'; import {AuthenticationService} from '../../../model/network/authentication.service'; import {NavigationService} from '../../../model/navigation.service'; @@ -12,55 +12,60 @@ import { JobTriggerType, NeverJobTrigger, PeriodicJobTrigger, - ScheduledJobTrigger + ScheduledJobTrigger, } from '../../../../../common/entities/job/JobScheduleDTO'; import {ConfigTemplateEntry} from '../../../../../common/entities/job/JobDTO'; import {ModalDirective} from 'ngx-bootstrap/modal'; -import {JobProgressDTO, JobProgressStates} from '../../../../../common/entities/job/JobProgressDTO'; +import {JobProgressDTO, JobProgressStates,} from '../../../../../common/entities/job/JobProgressDTO'; import {BackendtextService} from '../../../model/backendtext.service'; import {ServerJobConfig} from '../../../../../common/config/private/PrivateConfig'; @Component({ selector: 'app-settings-jobs', templateUrl: './jobs.settings.component.html', - styleUrls: ['./jobs.settings.component.css', - '../_abstract/abstract.settings.component.css'], - providers: [JobsSettingsService] + styleUrls: [ + './jobs.settings.component.css', + '../_abstract/abstract.settings.component.css', + ], + providers: [JobsSettingsService], }) -export class JobsSettingsComponent extends SettingsComponentDirective +export class JobsSettingsComponent + extends SettingsComponentDirective implements OnInit, OnDestroy, OnChanges { - @ViewChild('jobModal', {static: false}) public jobModal: ModalDirective; disableButtons = false; - JobTriggerTypeMap: { key: number, value: string }[]; + JobTriggerTypeMap: { key: number; value: string }[]; JobTriggerType = JobTriggerType; periods: string[] = []; - showDetails: boolean[] = []; + showDetails: { [key: string]: boolean } = {}; JobProgressStates = JobProgressStates; newSchedule: JobScheduleDTO = { name: '', config: null, jobName: '', trigger: { - type: JobTriggerType.never + type: JobTriggerType.never, }, - allowParallelRun: false + allowParallelRun: false, }; - constructor(authService: AuthenticationService, - navigation: NavigationService, - settingsService: JobsSettingsService, - public jobsService: ScheduledJobsService, - public backendTextService: BackendtextService, - notification: NotificationService) { - - super($localize`Jobs`, + constructor( + authService: AuthenticationService, + navigation: NavigationService, + settingsService: JobsSettingsService, + public jobsService: ScheduledJobsService, + public backendTextService: BackendtextService, + notification: NotificationService + ) { + super( + $localize`Jobs`, 'project', authService, navigation, settingsService, notification, - s => s.Server.Jobs); + (s) => s.Server.Jobs + ); this.hasAvailableSettings = !this.simplifiedMode; this.JobTriggerTypeMap = [ @@ -77,10 +82,10 @@ export class JobsSettingsComponent extends SettingsComponentDirective t.Name === JobName); + const job = this.settingsService.availableJobs.value.find( + (t) => t.Name === JobName + ); if (job && job.ConfigTemplate && job.ConfigTemplate.length > 0) { return job.ConfigTemplate; } @@ -108,14 +115,21 @@ export class JobsSettingsComponent extends SettingsComponentDirective t.Name === schedule.jobName); + const job = this.settingsService.availableJobs.value.find( + (t) => t.Name === schedule.jobName + ); schedule.config = schedule.config || {}; if (job.ConfigTemplate) { - job.ConfigTemplate.forEach(ct => schedule.config[ct.id] = ct.defaultValue); + job.ConfigTemplate.forEach( + (ct) => (schedule.config[ct.id] = ct.defaultValue) + ); } } @@ -126,25 +140,32 @@ export class JobsSettingsComponent extends SettingsComponentDirective t.Name === jobName); + const job = this.settingsService.availableJobs.value.find( + (t) => t.Name === jobName + ); this.newSchedule.config = this.newSchedule.config || {}; if (job.ConfigTemplate) { - job.ConfigTemplate.forEach(ct => this.newSchedule.config[ct.id] = ct.defaultValue); + job.ConfigTemplate.forEach( + (ct) => (this.newSchedule.config[ct.id] = ct.defaultValue) + ); } this.jobModal.show(); } - jobTriggerTypeChanged(triggerType: JobTriggerType, schedule: JobScheduleDTO): void { + jobTriggerTypeChanged( + triggerType: JobTriggerType, + schedule: JobScheduleDTO + ): void { schedule.trigger = {type: triggerType} as NeverJobTrigger; switch (triggerType) { case JobTriggerType.scheduled: - (schedule.trigger as unknown as ScheduledJobTrigger).time = (Date.now()); + (schedule.trigger as unknown as ScheduledJobTrigger).time = Date.now(); break; case JobTriggerType.periodic: @@ -157,7 +178,8 @@ export class JobsSettingsComponent extends SettingsComponentDirective parseInt(s, 10)) .filter((i: number) => !isNaN(i) && i > 0); } @@ -167,19 +189,34 @@ export class JobsSettingsComponent extends SettingsComponentDirective { - return this.getNextRunningDate(a, this.states.scheduled.value) - this.getNextRunningDate(b, this.states.scheduled.value); - }); + return (this.states.scheduled.value as JobScheduleDTO[]) + .slice() + .sort((a: JobScheduleDTO, b: JobScheduleDTO) => { + return ( + this.getNextRunningDate(a, this.states.scheduled.value) - + this.getNextRunningDate(b, this.states.scheduled.value) + ); + }); } addNewJob(): void { const jobName = this.newSchedule.jobName; - const count = this.states.scheduled.value.filter((s: JobScheduleDTO) => s.jobName === jobName).length; - this.newSchedule.name = count === 0 ? jobName : this.backendTextService.getJobName(jobName) + ' ' + (count + 1); + const count = this.states.scheduled.value.filter( + (s: JobScheduleDTO) => s.jobName === jobName + ).length; + this.newSchedule.name = + count === 0 + ? jobName + : this.backendTextService.getJobName(jobName) + ' ' + (count + 1); this.states.scheduled.value.push(this.newSchedule); this.jobModal.hide(); } @@ -188,15 +225,26 @@ export class JobsSettingsComponent extends SettingsComponentDirective list.length) { return 0; } if (sch.trigger.type === JobTriggerType.never) { - return list.map(s => s.name).sort().indexOf(sch.name) * -1; + return ( + list + .map((s) => s.name) + .sort() + .indexOf(sch.name) * -1 + ); } if (sch.trigger.type === JobTriggerType.after) { - const parent = list.find(s => s.name === (sch.trigger as AfterJobTrigger).afterScheduleName); + const parent = list.find( + (s) => s.name === (sch.trigger as AfterJobTrigger).afterScheduleName + ); if (parent) { return this.getNextRunningDate(parent, list, depth + 1) + 0.001; } diff --git a/src/frontend/app/ui/settings/jobs/jobs.settings.service.ts b/src/frontend/app/ui/settings/jobs/jobs.settings.service.ts index 4d34c2a1..705fb438 100644 --- a/src/frontend/app/ui/settings/jobs/jobs.settings.service.ts +++ b/src/frontend/app/ui/settings/jobs/jobs.settings.service.ts @@ -1,28 +1,27 @@ -import {Injectable} from '@angular/core'; -import {NetworkService} from '../../../model/network/network.service'; -import {SettingsService} from '../settings.service'; -import {AbstractSettingsService} from '../_abstract/abstract.settings.service'; -import {BehaviorSubject} from 'rxjs'; -import {JobDTO} from '../../../../../common/entities/job/JobDTO'; -import {ServerConfig, ServerJobConfig} from '../../../../../common/config/private/PrivateConfig'; +import { Injectable } from '@angular/core'; +import { NetworkService } from '../../../model/network/network.service'; +import { SettingsService } from '../settings.service'; +import { AbstractSettingsService } from '../_abstract/abstract.settings.service'; +import { BehaviorSubject } from 'rxjs'; +import { JobDTO } from '../../../../../common/entities/job/JobDTO'; +import { ServerJobConfig } from '../../../../../common/config/private/PrivateConfig'; @Injectable() export class JobsSettingsService extends AbstractSettingsService { - - public availableJobs: BehaviorSubject; - constructor(private networkService: NetworkService, - settingsService: SettingsService) { + constructor( + private networkService: NetworkService, + settingsService: SettingsService + ) { super(settingsService); this.availableJobs = new BehaviorSubject([]); } public updateSettings(settings: ServerJobConfig): Promise { - return this.networkService.putJson('/settings/jobs', {settings}); + return this.networkService.putJson('/settings/jobs', { settings }); } - showInSimplifiedMode(): boolean { return false; } @@ -31,9 +30,9 @@ export class JobsSettingsService extends AbstractSettingsService { - this.availableJobs.next(await this.networkService.getJson('/admin/jobs/available')); + this.availableJobs.next( + await this.networkService.getJson('/admin/jobs/available') + ); } - } diff --git a/src/frontend/app/ui/settings/jobs/progress/job-progress.settings.component.html b/src/frontend/app/ui/settings/jobs/progress/job-progress.settings.component.html index 1f1fbd45..d3f8902e 100644 --- a/src/frontend/app/ui/settings/jobs/progress/job-progress.settings.component.html +++ b/src/frontend/app/ui/settings/jobs/progress/job-progress.settings.component.html @@ -25,7 +25,8 @@
-
Processed: {{progress.steps.processed}}
-
Skipped: {{progress.steps.skipped}}
-
Left: {{progress.steps.all - progress.steps.skipped - progress.steps.processed}}
-
All: {{progress.steps.all}}
+
+ Processed + : {{progress.steps.processed}}
+
+ Skipped + : {{progress.steps.skipped}}
+
+ Left + : {{progress.steps.all - progress.steps.skipped - progress.steps.processed}}
+
+ All + : {{progress.steps.all}}
diff --git a/src/frontend/app/ui/settings/jobs/progress/job-progress.settings.component.ts b/src/frontend/app/ui/settings/jobs/progress/job-progress.settings.component.ts index e28bb4e3..08cfa2c4 100644 --- a/src/frontend/app/ui/settings/jobs/progress/job-progress.settings.component.ts +++ b/src/frontend/app/ui/settings/jobs/progress/job-progress.settings.component.ts @@ -1,32 +1,52 @@ -import {Component, Input, OnChanges, OnDestroy, TemplateRef} from '@angular/core'; -import {JobProgressDTO, JobProgressStates} from '../../../../../../common/entities/job/JobProgressDTO'; -import {Subscription, timer} from 'rxjs'; -import {BsModalRef, BsModalService} from 'ngx-bootstrap/modal'; -import {BackendtextService} from '../../../../model/backendtext.service'; +import { + Component, + Input, + OnChanges, + OnDestroy, + TemplateRef, +} from '@angular/core'; +import { + JobProgressDTO, + JobProgressStates, +} from '../../../../../../common/entities/job/JobProgressDTO'; +import { Subscription, timer } from 'rxjs'; +import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; +import { BackendtextService } from '../../../../model/backendtext.service'; @Component({ selector: 'app-settings-job-progress', templateUrl: './job-progress.settings.component.html', - styleUrls: ['./job-progress.settings.component.css'] + styleUrls: ['./job-progress.settings.component.css'], }) export class JobProgressComponent implements OnDestroy, OnChanges { - @Input() progress: JobProgressDTO; JobProgressStates = JobProgressStates; timeCurrentCopy: number; modalRef: BsModalRef; private timerSub: Subscription; - constructor(private modalService: BsModalService, - public backendTextService: BackendtextService) { - } + constructor( + private modalService: BsModalService, + public backendTextService: BackendtextService + ) {} get ProgressTitle(): string { if (!this.progress) { return ''; } - return $localize`processed` + ':' + this.progress.steps.processed + ' + ' + $localize`skipped` + ':' - + this.progress.steps.skipped + ' / ' + $localize`all` + ':' + this.progress.steps.all; + return ( + $localize`processed` + + ':' + + this.progress.steps.processed + + ' + ' + + $localize`skipped` + + ':' + + this.progress.steps.skipped + + ' / ' + + $localize`all` + + ':' + + this.progress.steps.all + ); } get Name(): string { @@ -40,30 +60,44 @@ export class JobProgressComponent implements OnDestroy, OnChanges { if (!this.progress || this.progress.steps.processed === 0) { return 0; } - return (this.progress.time.end - this.progress.time.start) / - (this.progress.steps.processed) * (this.progress.steps.all - this.progress.steps.skipped); + return ( + ((this.progress.time.end - this.progress.time.start) / + this.progress.steps.processed) * + (this.progress.steps.all - this.progress.steps.skipped) + ); } get Running(): boolean { - return this.progress && (this.progress.state === JobProgressStates.running || this.progress.state === JobProgressStates.cancelling); + return ( + this.progress && + (this.progress.state === JobProgressStates.running || + this.progress.state === JobProgressStates.cancelling) + ); } get Stopped(): boolean { - return this.progress && (this.progress.state !== JobProgressStates.running && this.progress.state !== JobProgressStates.cancelling); + return ( + this.progress && + this.progress.state !== JobProgressStates.running && + this.progress.state !== JobProgressStates.cancelling + ); } get TimeLeft(): number { if (!this.progress) { return 0; } - return (this.progress.time.end - this.progress.time.start) / this.progress.steps.all; + return ( + (this.progress.time.end - this.progress.time.start) / + this.progress.steps.all + ); } get TimeElapsed(): number { if (!this.progress) { return 0; } - return (this.timeCurrentCopy - this.progress.time.start); + return this.timeCurrentCopy - this.progress.time.start; } get State(): string { @@ -87,7 +121,7 @@ export class JobProgressComponent implements OnDestroy, OnChanges { } openModal(template: TemplateRef): void { - this.modalRef = this.modalService.show(template, {class: 'modal-lg'}); + this.modalRef = this.modalService.show(template, { class: 'modal-lg' }); } ngOnChanges(): void { @@ -109,8 +143,6 @@ export class JobProgressComponent implements OnDestroy, OnChanges { this.timerSub.unsubscribe(); } } - - } diff --git a/src/frontend/app/ui/settings/map/map.settings.component.html b/src/frontend/app/ui/settings/map/map.settings.component.html index 294a9859..f49b2164 100644 --- a/src/frontend/app/ui/settings/map/map.settings.component.html +++ b/src/frontend/app/ui/settings/map/map.settings.component.html @@ -7,14 +7,14 @@ class="switch" name="enabled" switch-on-color="success" - switch-inverse="true" + [switch-inverse]="true" switch-off-text="Disabled" switch-on-text="Enabled" i18n-switch-off-text i18n-switch-on-text [switch-disabled]="inProgress" - switch-handle-width="100" - switch-label-width="20" + [switch-handle-width]="100" + [switch-label-width]="20" [(ngModel)]="states.enabled.value">
@@ -24,8 +24,6 @@ - - - + [required]="true"> @@ -91,7 +88,7 @@ i18n-name placeholder="Mapbox access token" [ngModel]="states.mapboxAccessToken" - required="true"> + [required]="true"> MapBox needs an access token to work, create one at  https://www.mapbox.com. diff --git a/src/frontend/app/ui/settings/map/map.settings.component.ts b/src/frontend/app/ui/settings/map/map.settings.component.ts index df769e1f..f49e4e2b 100644 --- a/src/frontend/app/ui/settings/map/map.settings.component.ts +++ b/src/frontend/app/ui/settings/map/map.settings.component.ts @@ -1,44 +1,60 @@ -import {Component} from '@angular/core'; -import {MapSettingsService} from './map.settings.service'; -import {SettingsComponentDirective} from '../_abstract/abstract.settings.component'; -import {AuthenticationService} from '../../../model/network/authentication.service'; -import {NavigationService} from '../../../model/navigation.service'; -import {NotificationService} from '../../../model/notification.service'; -import {Utils} from '../../../../../common/Utils'; -import {ClientMapConfig, MapLayers, MapProviders} from '../../../../../common/config/public/ClientConfig'; - +import { Component } from '@angular/core'; +import { MapSettingsService } from './map.settings.service'; +import { SettingsComponentDirective } from '../_abstract/abstract.settings.component'; +import { AuthenticationService } from '../../../model/network/authentication.service'; +import { NavigationService } from '../../../model/navigation.service'; +import { NotificationService } from '../../../model/notification.service'; +import { Utils } from '../../../../../common/Utils'; +import { + ClientMapConfig, + MapLayers, + MapProviders, +} from '../../../../../common/config/public/ClientConfig'; @Component({ selector: 'app-settings-map', templateUrl: './map.settings.component.html', - styleUrls: ['./map.settings.component.css', - '../_abstract/abstract.settings.component.css'], + styleUrls: [ + './map.settings.component.css', + '../_abstract/abstract.settings.component.css', + ], providers: [MapSettingsService], }) export class MapSettingsComponent extends SettingsComponentDirective { - - public mapProviders: { key: number, value: string }[] = []; + public mapProviders: { key: number; value: string }[] = []; public MapProviders = MapProviders; - constructor(authService: AuthenticationService, - navigation: NavigationService, - settingsService: MapSettingsService, - notification: NotificationService) { - super($localize`Map`,'map-marker', authService, navigation, settingsService, notification, s => s.Client.Map); + constructor( + authService: AuthenticationService, + navigation: NavigationService, + settingsService: MapSettingsService, + notification: NotificationService + ) { + super( + $localize`Map`, + 'map-marker', + authService, + navigation, + settingsService, + notification, + (s) => s.Client.Map + ); this.mapProviders = Utils.enumToArray(MapProviders); } - addNewLayer(): void { this.states.customLayers.value.push({ name: 'Layer-' + this.states.customLayers.value.length, - url: '' + url: '', }); } removeLayer(layer: MapLayers): void { - this.states.customLayers.value.splice(this.states.customLayers.value.indexOf(layer), 1); + this.states.customLayers.value.splice( + this.states.customLayers.value.indexOf(layer), + 1 + ); } } diff --git a/src/frontend/app/ui/settings/map/map.settings.service.ts b/src/frontend/app/ui/settings/map/map.settings.service.ts index 660aa6ad..a8e7874b 100644 --- a/src/frontend/app/ui/settings/map/map.settings.service.ts +++ b/src/frontend/app/ui/settings/map/map.settings.service.ts @@ -1,15 +1,16 @@ -import {Injectable} from '@angular/core'; -import {NetworkService} from '../../../model/network/network.service'; -import {SettingsService} from '../settings.service'; -import {AbstractSettingsService} from '../_abstract/abstract.settings.service'; -import {ClientMapConfig} from '../../../../../common/config/public/ClientConfig'; +import { Injectable } from '@angular/core'; +import { NetworkService } from '../../../model/network/network.service'; +import { SettingsService } from '../settings.service'; +import { AbstractSettingsService } from '../_abstract/abstract.settings.service'; +import { ClientMapConfig } from '../../../../../common/config/public/ClientConfig'; @Injectable() export class MapSettingsService extends AbstractSettingsService { - constructor(private networkService: NetworkService, - settingsService: SettingsService) { + constructor( + private networkService: NetworkService, + settingsService: SettingsService + ) { super(settingsService); - } showInSimplifiedMode(): boolean { @@ -17,7 +18,6 @@ export class MapSettingsService extends AbstractSettingsService } public updateSettings(settings: ClientMapConfig): Promise { - return this.networkService.putJson('/settings/map', {settings}); + return this.networkService.putJson('/settings/map', { settings }); } - } diff --git a/src/frontend/app/ui/settings/metafiles/metafile.settings.component.ts b/src/frontend/app/ui/settings/metafiles/metafile.settings.component.ts index 2f89bde6..6c70496b 100644 --- a/src/frontend/app/ui/settings/metafiles/metafile.settings.component.ts +++ b/src/frontend/app/ui/settings/metafiles/metafile.settings.component.ts @@ -1,29 +1,37 @@ -import {Component} from '@angular/core'; -import {MetaFileSettingsService} from './metafile.settings.service'; -import {SettingsComponentDirective} from '../_abstract/abstract.settings.component'; -import {AuthenticationService} from '../../../model/network/authentication.service'; -import {NavigationService} from '../../../model/navigation.service'; -import {NotificationService} from '../../../model/notification.service'; -import {ClientMetaFileConfig} from '../../../../../common/config/public/ClientConfig'; - +import { Component } from '@angular/core'; +import { MetaFileSettingsService } from './metafile.settings.service'; +import { SettingsComponentDirective } from '../_abstract/abstract.settings.component'; +import { AuthenticationService } from '../../../model/network/authentication.service'; +import { NavigationService } from '../../../model/navigation.service'; +import { NotificationService } from '../../../model/notification.service'; +import { ClientMetaFileConfig } from '../../../../../common/config/public/ClientConfig'; @Component({ selector: 'app-settings-meta-file', templateUrl: './metafile.settings.component.html', - styleUrls: ['./metafile.settings.component.css', - '../_abstract/abstract.settings.component.css'], + styleUrls: [ + './metafile.settings.component.css', + '../_abstract/abstract.settings.component.css', + ], providers: [MetaFileSettingsService], }) export class MetaFileSettingsComponent extends SettingsComponentDirective { - - constructor(authService: AuthenticationService, - navigation: NavigationService, - settingsService: MetaFileSettingsService, - notification: NotificationService) { - super($localize`Meta file`, 'file', authService, navigation, settingsService, notification, s => s.Client.MetaFile); + constructor( + authService: AuthenticationService, + navigation: NavigationService, + settingsService: MetaFileSettingsService, + notification: NotificationService + ) { + super( + $localize`Meta file`, + 'file', + authService, + navigation, + settingsService, + notification, + (s) => s.Client.MetaFile + ); } - - } diff --git a/src/frontend/app/ui/settings/metafiles/metafile.settings.service.ts b/src/frontend/app/ui/settings/metafiles/metafile.settings.service.ts index 46283d31..7ed471da 100644 --- a/src/frontend/app/ui/settings/metafiles/metafile.settings.service.ts +++ b/src/frontend/app/ui/settings/metafiles/metafile.settings.service.ts @@ -1,28 +1,27 @@ -import {Injectable} from '@angular/core'; -import {NetworkService} from '../../../model/network/network.service'; -import {SettingsService} from '../settings.service'; -import {AbstractSettingsService} from '../_abstract/abstract.settings.service'; -import {ClientConfig, ClientMetaFileConfig} from '../../../../../common/config/public/ClientConfig'; +import { Injectable } from '@angular/core'; +import { NetworkService } from '../../../model/network/network.service'; +import { SettingsService } from '../settings.service'; +import { AbstractSettingsService } from '../_abstract/abstract.settings.service'; +import { ClientMetaFileConfig } from '../../../../../common/config/public/ClientConfig'; @Injectable() export class MetaFileSettingsService extends AbstractSettingsService { - constructor(private networkService: NetworkService, - settingsService: SettingsService) { + constructor( + private networkService: NetworkService, + settingsService: SettingsService + ) { super(settingsService); - } public isSupported(): boolean { return this.settingsService.settings.value.Client.Map.enabled === true; } - showInSimplifiedMode(): boolean { return false; } public updateSettings(settings: ClientMetaFileConfig): Promise { - return this.networkService.putJson('/settings/metafile', {settings}); + return this.networkService.putJson('/settings/metafile', { settings }); } - } diff --git a/src/frontend/app/ui/settings/other/other.settings.component.html b/src/frontend/app/ui/settings/other/other.settings.component.html index 98db415c..1b9a78ed 100644 --- a/src/frontend/app/ui/settings/other/other.settings.component.html +++ b/src/frontend/app/ui/settings/other/other.settings.component.html @@ -1,4 +1,4 @@ - +
{{Name}} @@ -25,7 +25,7 @@ [ngModel]="states.Server.thumbnailThreads" [options]="threads" [simplifiedMode]="simplifiedMode || states.Server.enabled.value == false" - required="true"> + [required]="true"> @@ -77,7 +77,6 @@ -

Navigation bar:

@@ -87,22 +86,17 @@ i18n-description i18n-name [ngModel]="states.Client.NavBar.showItemCount" [simplifiedMode]="simplifiedMode" - required="true"> + [required]="true"> - - - - - + [required]="true"> + [required]="true"> -
diff --git a/src/frontend/app/ui/settings/random-photo/random-photo.settings.component.ts b/src/frontend/app/ui/settings/random-photo/random-photo.settings.component.ts index 822151bc..f284e4a2 100644 --- a/src/frontend/app/ui/settings/random-photo/random-photo.settings.component.ts +++ b/src/frontend/app/ui/settings/random-photo/random-photo.settings.component.ts @@ -1,28 +1,37 @@ -import {Component} from '@angular/core'; -import {SettingsComponentDirective} from '../_abstract/abstract.settings.component'; -import {AuthenticationService} from '../../../model/network/authentication.service'; -import {NavigationService} from '../../../model/navigation.service'; -import {NotificationService} from '../../../model/notification.service'; -import {RandomPhotoSettingsService} from './random-photo.settings.service'; -import {ClientRandomPhotoConfig} from '../../../../../common/config/public/ClientConfig'; +import { Component } from '@angular/core'; +import { SettingsComponentDirective } from '../_abstract/abstract.settings.component'; +import { AuthenticationService } from '../../../model/network/authentication.service'; +import { NavigationService } from '../../../model/navigation.service'; +import { NotificationService } from '../../../model/notification.service'; +import { RandomPhotoSettingsService } from './random-photo.settings.service'; +import { ClientRandomPhotoConfig } from '../../../../../common/config/public/ClientConfig'; @Component({ selector: 'app-settings-random-photo', templateUrl: './random-photo.settings.component.html', - styleUrls: ['./random-photo.settings.component.css', - '../_abstract/abstract.settings.component.css'], + styleUrls: [ + './random-photo.settings.component.css', + '../_abstract/abstract.settings.component.css', + ], providers: [RandomPhotoSettingsService], }) export class RandomPhotoSettingsComponent extends SettingsComponentDirective { - - constructor(authService: AuthenticationService, - navigation: NavigationService, - settingsService: RandomPhotoSettingsService, - notification: NotificationService) { - super($localize`Random Photo`, 'random', authService, navigation, settingsService, notification, s => s.Client.RandomPhoto); + constructor( + authService: AuthenticationService, + navigation: NavigationService, + settingsService: RandomPhotoSettingsService, + notification: NotificationService + ) { + super( + $localize`Random Photo`, + 'random', + authService, + navigation, + settingsService, + notification, + (s) => s.Client.RandomPhoto + ); } - - } diff --git a/src/frontend/app/ui/settings/random-photo/random-photo.settings.service.ts b/src/frontend/app/ui/settings/random-photo/random-photo.settings.service.ts index fee66d09..04378c21 100644 --- a/src/frontend/app/ui/settings/random-photo/random-photo.settings.service.ts +++ b/src/frontend/app/ui/settings/random-photo/random-photo.settings.service.ts @@ -1,29 +1,31 @@ -import {Injectable} from '@angular/core'; -import {NetworkService} from '../../../model/network/network.service'; -import {SettingsService} from '../settings.service'; -import {AbstractSettingsService} from '../_abstract/abstract.settings.service'; -import {DatabaseType, ServerConfig} from '../../../../../common/config/private/PrivateConfig'; -import {ClientConfig, ClientSearchConfig} from '../../../../../common/config/public/ClientConfig'; +import { Injectable } from '@angular/core'; +import { NetworkService } from '../../../model/network/network.service'; +import { SettingsService } from '../settings.service'; +import { AbstractSettingsService } from '../_abstract/abstract.settings.service'; +import { DatabaseType } from '../../../../../common/config/private/PrivateConfig'; +import { ClientSearchConfig } from '../../../../../common/config/public/ClientConfig'; @Injectable() export class RandomPhotoSettingsService extends AbstractSettingsService { - constructor(private networkService: NetworkService, - settingsService: SettingsService) { + constructor( + private networkService: NetworkService, + settingsService: SettingsService + ) { super(settingsService); - } - public showInSimplifiedMode(): boolean { return false; } public isSupported(): boolean { - return this.settingsService.settings.value.Server.Database.type !== DatabaseType.memory; + return ( + this.settingsService.settings.value.Server.Database.type !== + DatabaseType.memory + ); } public updateSettings(settings: ClientSearchConfig): Promise { - return this.networkService.putJson('/settings/randomPhoto', {settings}); + return this.networkService.putJson('/settings/randomPhoto', { settings }); } - } diff --git a/src/frontend/app/ui/settings/scheduled-jobs.service.ts b/src/frontend/app/ui/settings/scheduled-jobs.service.ts index 0d0d0c68..aebf3ac1 100644 --- a/src/frontend/app/ui/settings/scheduled-jobs.service.ts +++ b/src/frontend/app/ui/settings/scheduled-jobs.service.ts @@ -1,30 +1,35 @@ -import {EventEmitter, Injectable} from '@angular/core'; -import {BehaviorSubject} from 'rxjs'; -import {JobProgressDTO, JobProgressStates} from '../../../../common/entities/job/JobProgressDTO'; -import {NetworkService} from '../../model/network/network.service'; -import {JobScheduleDTO} from '../../../../common/entities/job/JobScheduleDTO'; -import {JobDTO, JobDTOUtils} from '../../../../common/entities/job/JobDTO'; -import {BackendtextService} from '../../model/backendtext.service'; -import {NotificationService} from '../../model/notification.service'; +import { EventEmitter, Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; +import { + JobProgressDTO, + JobProgressStates, +} from '../../../../common/entities/job/JobProgressDTO'; +import { NetworkService } from '../../model/network/network.service'; +import { JobScheduleDTO } from '../../../../common/entities/job/JobScheduleDTO'; +import { JobDTOUtils } from '../../../../common/entities/job/JobDTO'; +import { BackendtextService } from '../../model/backendtext.service'; +import { NotificationService } from '../../model/notification.service'; @Injectable() export class ScheduledJobsService { - - public progress: BehaviorSubject<{ [key: string]: JobProgressDTO }>; public onJobFinish: EventEmitter = new EventEmitter(); timer: number = null; public jobStartingStopping: { [key: string]: boolean } = {}; private subscribers = 0; - constructor(private networkService: NetworkService, - private notification: NotificationService, - private backendTextService: BackendtextService) { + constructor( + private networkService: NetworkService, + private notification: NotificationService, + private backendTextService: BackendtextService + ) { this.progress = new BehaviorSubject({}); } getProgress(schedule: JobScheduleDTO): JobProgressDTO { - return this.progress.value[JobDTOUtils.getHashName(schedule.jobName, schedule.config)]; + return this.progress.value[ + JobDTOUtils.getHashName(schedule.jobName, schedule.config) + ]; } subscribeToProgress(): void { @@ -39,15 +44,22 @@ export class ScheduledJobsService { return await this.loadProgress(); } - public async start(jobName: string, config?: any, soloStart: boolean = false, allowParallelRun = false): Promise { + public async start( + jobName: string, + config?: any, + soloStart: boolean = false, + allowParallelRun = false + ): Promise { try { this.jobStartingStopping[jobName] = true; - await this.networkService.postJson('/admin/jobs/scheduled/' + jobName + '/start', + await this.networkService.postJson( + '/admin/jobs/scheduled/' + jobName + '/start', { config, allowParallelRun, - soloStart - }); + soloStart, + } + ); // placeholder to force showing running job this.addDummyProgress(jobName, config); } catch (e) { @@ -60,24 +72,37 @@ export class ScheduledJobsService { public async stop(jobName: string): Promise { this.jobStartingStopping[jobName] = true; - await this.networkService.postJson('/admin/jobs/scheduled/' + jobName + '/stop'); + await this.networkService.postJson( + '/admin/jobs/scheduled/' + jobName + '/stop' + ); delete this.jobStartingStopping[jobName]; this.forceUpdate(); } protected async loadProgress(): Promise { const prevPrg = this.progress.value; - this.progress.next(await this.networkService.getJson<{ [key: string]: JobProgressDTO }>('/admin/jobs/scheduled/progress')); + this.progress.next( + await this.networkService.getJson<{ [key: string]: JobProgressDTO }>( + '/admin/jobs/scheduled/progress' + ) + ); for (const prg of Object.keys(prevPrg)) { - if (!this.progress.value.hasOwnProperty(prg) || + if ( + !this.progress.value.hasOwnProperty(prg) || // state changed from running to finished ((prevPrg[prg].state === JobProgressStates.running || - prevPrg[prg].state === JobProgressStates.cancelling) && - !(this.progress.value[prg].state === JobProgressStates.running || - this.progress.value[prg].state === JobProgressStates.cancelling) - )) { + prevPrg[prg].state === JobProgressStates.cancelling) && + !( + this.progress.value[prg].state === JobProgressStates.running || + this.progress.value[prg].state === JobProgressStates.cancelling + )) + ) { this.onJobFinish.emit(prg); - this.notification.success($localize`Job finished` + ': ' + this.backendTextService.getJobName(prevPrg[prg].jobName)); + this.notification.success( + $localize`Job finished` + + ': ' + + this.backendTextService.getJobName(prevPrg[prg].jobName) + ); } } } @@ -103,15 +128,16 @@ export class ScheduledJobsService { jobName, state: JobProgressStates.running, HashName: JobDTOUtils.getHashName(jobName, config), - logs: [], steps: { + logs: [], + steps: { skipped: 0, processed: 0, - all: 0 + all: 0, }, time: { start: Date.now(), - end: Date.now() - } + end: Date.now(), + }, }; this.progress.next(prgs); } @@ -124,5 +150,4 @@ export class ScheduledJobsService { private decSubscribers(): void { this.subscribers--; } - } diff --git a/src/frontend/app/ui/settings/search/search.settings.component.html b/src/frontend/app/ui/settings/search/search.settings.component.html index cf3fc586..f3e4f0e5 100644 --- a/src/frontend/app/ui/settings/search/search.settings.component.html +++ b/src/frontend/app/ui/settings/search/search.settings.component.html @@ -8,14 +8,14 @@ class="switch" name="enabled" switch-on-color="success" - switch-inverse="true" + [switch-inverse]="true" switch-off-text="Disabled" switch-on-text="Enabled" i18n-switch-off-text i18n-switch-on-text [switch-disabled]="inProgress || !settingsService.isSupported()" - switch-handle-width="100" - switch-label-width="20" + [switch-handle-width]="100" + [switch-label-width]="20" [(ngModel)]="states.enabled.value">
@@ -26,8 +26,6 @@ - - + [required]="true"> -
Search is not supported with these settings diff --git a/src/frontend/app/ui/settings/search/search.settings.component.ts b/src/frontend/app/ui/settings/search/search.settings.component.ts index 57f80275..ebda60b4 100644 --- a/src/frontend/app/ui/settings/search/search.settings.component.ts +++ b/src/frontend/app/ui/settings/search/search.settings.component.ts @@ -1,28 +1,37 @@ -import {Component} from '@angular/core'; -import {SettingsComponentDirective} from '../_abstract/abstract.settings.component'; -import {AuthenticationService} from '../../../model/network/authentication.service'; -import {NavigationService} from '../../../model/navigation.service'; -import {NotificationService} from '../../../model/notification.service'; -import {SearchSettingsService} from './search.settings.service'; -import {ClientSearchConfig} from '../../../../../common/config/public/ClientConfig'; +import { Component } from '@angular/core'; +import { SettingsComponentDirective } from '../_abstract/abstract.settings.component'; +import { AuthenticationService } from '../../../model/network/authentication.service'; +import { NavigationService } from '../../../model/navigation.service'; +import { NotificationService } from '../../../model/notification.service'; +import { SearchSettingsService } from './search.settings.service'; +import { ClientSearchConfig } from '../../../../../common/config/public/ClientConfig'; @Component({ selector: 'app-settings-search', templateUrl: './search.settings.component.html', - styleUrls: ['./search.settings.component.css', - '../_abstract/abstract.settings.component.css'], + styleUrls: [ + './search.settings.component.css', + '../_abstract/abstract.settings.component.css', + ], providers: [SearchSettingsService], }) export class SearchSettingsComponent extends SettingsComponentDirective { - - constructor(authService: AuthenticationService, - navigation: NavigationService, - settingsService: SearchSettingsService, - notification: NotificationService) { - super($localize`Search`, 'magnifying-glass', authService, navigation, settingsService, notification, s => s.Client.Search); + constructor( + authService: AuthenticationService, + navigation: NavigationService, + settingsService: SearchSettingsService, + notification: NotificationService + ) { + super( + $localize`Search`, + 'magnifying-glass', + authService, + navigation, + settingsService, + notification, + (s) => s.Client.Search + ); } - - } diff --git a/src/frontend/app/ui/settings/search/search.settings.service.ts b/src/frontend/app/ui/settings/search/search.settings.service.ts index 7b57b4e8..1e046083 100644 --- a/src/frontend/app/ui/settings/search/search.settings.service.ts +++ b/src/frontend/app/ui/settings/search/search.settings.service.ts @@ -1,26 +1,31 @@ -import {Injectable} from '@angular/core'; -import {NetworkService} from '../../../model/network/network.service'; -import {SettingsService} from '../settings.service'; -import {AbstractSettingsService} from '../_abstract/abstract.settings.service'; -import {DatabaseType, ServerConfig} from '../../../../../common/config/private/PrivateConfig'; -import {ClientConfig, ClientSearchConfig} from '../../../../../common/config/public/ClientConfig'; +import { Injectable } from '@angular/core'; +import { NetworkService } from '../../../model/network/network.service'; +import { SettingsService } from '../settings.service'; +import { AbstractSettingsService } from '../_abstract/abstract.settings.service'; +import { DatabaseType } from '../../../../../common/config/private/PrivateConfig'; +import { ClientSearchConfig } from '../../../../../common/config/public/ClientConfig'; @Injectable() export class SearchSettingsService extends AbstractSettingsService { - constructor(private networkService: NetworkService, - settingsService: SettingsService) { + constructor( + private networkService: NetworkService, + settingsService: SettingsService + ) { super(settingsService); } showInSimplifiedMode(): boolean { return false; } + public isSupported(): boolean { - return this.settingsService.settings.value.Server.Database.type !== DatabaseType.memory; + return ( + this.settingsService.settings.value.Server.Database.type !== + DatabaseType.memory + ); } public updateSettings(settings: ClientSearchConfig): Promise { - return this.networkService.putJson('/settings/search', {settings}); + return this.networkService.putJson('/settings/search', { settings }); } - } diff --git a/src/frontend/app/ui/settings/settings.service.ts b/src/frontend/app/ui/settings/settings.service.ts index 7dff153a..93e1da64 100644 --- a/src/frontend/app/ui/settings/settings.service.ts +++ b/src/frontend/app/ui/settings/settings.service.ts @@ -1,9 +1,9 @@ -import {Injectable} from '@angular/core'; -import {BehaviorSubject} from 'rxjs'; -import {NetworkService} from '../../model/network/network.service'; +import { Injectable } from '@angular/core'; +import { BehaviorSubject } from 'rxjs'; +import { NetworkService } from '../../model/network/network.service'; -import {WebConfig} from '../../../../common/config/private/WebConfig'; -import {WebConfigClassBuilder} from 'typeconfig/src/decorators/builders/WebConfigClassBuilder'; +import { WebConfig } from '../../../../common/config/private/WebConfig'; +import { WebConfigClassBuilder } from 'typeconfig/src/decorators/builders/WebConfigClassBuilder'; @Injectable() export class SettingsService { @@ -22,7 +22,9 @@ export class SettingsService { this.fetchingSettings = true; try { const wcg = WebConfigClassBuilder.attachInterface(new WebConfig()); - wcg.load(await this.networkService.getJson>('/settings')); + wcg.load( + await this.networkService.getJson>('/settings') + ); this.settings.next(wcg); } catch (e) { console.error(e); diff --git a/src/frontend/app/ui/settings/share/share.settings.component.css b/src/frontend/app/ui/settings/share/share.settings.component.css index 604cf37a..15661324 100644 --- a/src/frontend/app/ui/settings/share/share.settings.component.css +++ b/src/frontend/app/ui/settings/share/share.settings.component.css @@ -2,6 +2,6 @@ text-align: center; } -.share-settings-save-buttons .btn{ +.share-settings-save-buttons .btn { margin-bottom: 10px; } diff --git a/src/frontend/app/ui/settings/share/share.settings.component.html b/src/frontend/app/ui/settings/share/share.settings.component.html index b3027a0c..ab9b18bf 100644 --- a/src/frontend/app/ui/settings/share/share.settings.component.html +++ b/src/frontend/app/ui/settings/share/share.settings.component.html @@ -8,14 +8,14 @@ class="switch" name="enabled" switch-on-color="success" - switch-inverse="true" + [switch-inverse]="true" switch-off-text="Disabled" switch-on-text="Enabled" i18n-switch-off-text i18n-switch-on-text [switch-disabled]="inProgress || !settingsService.isSupported()" - switch-handle-width="100" - switch-label-width="20" + [switch-handle-width]="100" + [switch-label-width]="20" [(ngModel)]="states.enabled.value">
@@ -31,7 +31,7 @@ i18n-description i18n-name [ngModel]="states.passwordProtected" [simplifiedMode]="simplifiedMode" - required="true"> + [required]="true"> diff --git a/src/frontend/app/ui/settings/share/share.settings.component.ts b/src/frontend/app/ui/settings/share/share.settings.component.ts index 321be10c..8e27a166 100644 --- a/src/frontend/app/ui/settings/share/share.settings.component.ts +++ b/src/frontend/app/ui/settings/share/share.settings.component.ts @@ -1,29 +1,42 @@ -import {Component, OnInit} from '@angular/core'; -import {SettingsComponentDirective} from '../_abstract/abstract.settings.component'; -import {AuthenticationService} from '../../../model/network/authentication.service'; -import {NavigationService} from '../../../model/navigation.service'; -import {NotificationService} from '../../../model/notification.service'; -import {ShareSettingsService} from './share.settings.service'; -import {ClientSharingConfig} from '../../../../../common/config/public/ClientConfig'; -import {SharingDTO} from '../../../../../common/entities/SharingDTO'; +import { Component, OnInit } from '@angular/core'; +import { SettingsComponentDirective } from '../_abstract/abstract.settings.component'; +import { AuthenticationService } from '../../../model/network/authentication.service'; +import { NavigationService } from '../../../model/navigation.service'; +import { NotificationService } from '../../../model/notification.service'; +import { ShareSettingsService } from './share.settings.service'; +import { ClientSharingConfig } from '../../../../../common/config/public/ClientConfig'; +import { SharingDTO } from '../../../../../common/entities/SharingDTO'; @Component({ selector: 'app-settings-share', templateUrl: './share.settings.component.html', - styleUrls: ['./share.settings.component.css', - '../_abstract/abstract.settings.component.css'], + styleUrls: [ + './share.settings.component.css', + '../_abstract/abstract.settings.component.css', + ], providers: [ShareSettingsService], }) -export class ShareSettingsComponent extends SettingsComponentDirective implements OnInit { - - +export class ShareSettingsComponent + extends SettingsComponentDirective + implements OnInit +{ public shares: SharingDTO[] = []; - constructor(authService: AuthenticationService, - navigation: NavigationService, - settingsService: ShareSettingsService, - notification: NotificationService) { - super($localize`Share`, 'share', authService, navigation, settingsService, notification, s => s.Client.Sharing); + constructor( + authService: AuthenticationService, + navigation: NavigationService, + settingsService: ShareSettingsService, + notification: NotificationService + ) { + super( + $localize`Share`, + 'share', + authService, + navigation, + settingsService, + notification, + (s) => s.Client.Sharing + ); } ngOnInit(): void { @@ -44,7 +57,6 @@ export class ShareSettingsComponent extends SettingsComponentDirective { - constructor(private networkService: NetworkService, - settingsService: SettingsService) { + constructor( + private networkService: NetworkService, + settingsService: SettingsService + ) { super(settingsService); - } showInSimplifiedMode(): boolean { @@ -19,15 +20,17 @@ export class ShareSettingsService extends AbstractSettingsService { - return this.networkService.putJson('/settings/share', {settings}); + return this.networkService.putJson('/settings/share', { settings }); } - public getSharingList(): Promise { if (!this.settingsService.settings.value.Client.Sharing.enabled) { return Promise.resolve([]); @@ -35,9 +38,7 @@ export class ShareSettingsService extends AbstractSettingsService { return this.networkService.deleteJson('/share/' + sharing.sharingKey); } - } diff --git a/src/frontend/app/ui/settings/thumbnail/thumbnail.settings.component.html b/src/frontend/app/ui/settings/thumbnail/thumbnail.settings.component.html index 6ce26350..19228bf3 100644 --- a/src/frontend/app/ui/settings/thumbnail/thumbnail.settings.component.html +++ b/src/frontend/app/ui/settings/thumbnail/thumbnail.settings.component.html @@ -22,7 +22,7 @@ i18n-description i18n-name [ngModel]="states.client.iconSize" [simplifiedMode]="simplifiedMode" - required="true"> + [required]="true"> @@ -32,7 +32,7 @@ placeholder="240; 480" [ngModel]="states.client.thumbnailSizes" [simplifiedMode]="simplifiedMode" - required="true"> + [required]="true"> Size of the thumbnails.
@@ -47,8 +47,6 @@ - -