From 35340b2c04f6c2b1057b1bb3994efb8a7f2dd984 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sat, 29 Jul 2023 13:18:04 +0200 Subject: [PATCH 01/22] Creating top pick job #683 --- package-lock.json | 14 ++++ package.json | 5 +- src/backend/middlewares/GalleryMWs.ts | 22 +++--- src/backend/model/database/SearchManager.ts | 56 ++++++++++++-- src/backend/model/jobs/JobRepository.ts | 2 + .../model/jobs/jobs/PreviewFillingJob.ts | 13 +--- src/backend/model/jobs/jobs/TopPickSendJob.ts | 74 +++++++++++++++++++ src/common/BackendTexts.ts | 4 + src/common/entities/job/JobDTO.ts | 5 +- 9 files changed, 162 insertions(+), 33 deletions(-) create mode 100644 src/backend/model/jobs/jobs/TopPickSendJob.ts diff --git a/package-lock.json b/package-lock.json index a71cac10..9c2bdedb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,6 +23,7 @@ "image-size": "1.0.2", "locale": "0.1.0", "node-geocoder": "4.2.0", + "nodemailer": "^6.9.4", "reflect-metadata": "0.1.13", "sharp": "0.31.3", "ts-exif-parser": "0.2.2", @@ -16598,6 +16599,14 @@ "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", "dev": true }, + "node_modules/nodemailer": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.4.tgz", + "integrity": "sha512-CXjQvrQZV4+6X5wP6ZIgdehJamI63MFoYFGGPtHudWym9qaEHDNdPzaj5bfMCvxG1vhAileSWW90q7nL0N36mA==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/non-layered-tidy-tree-layout": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz", @@ -35797,6 +35806,11 @@ "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", "dev": true }, + "nodemailer": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.4.tgz", + "integrity": "sha512-CXjQvrQZV4+6X5wP6ZIgdehJamI63MFoYFGGPtHudWym9qaEHDNdPzaj5bfMCvxG1vhAileSWW90q7nL0N36mA==" + }, "non-layered-tidy-tree-layout": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz", diff --git a/package.json b/package.json index 798fc1e4..ec080e78 100644 --- a/package.json +++ b/package.json @@ -46,13 +46,14 @@ "image-size": "1.0.2", "locale": "0.1.0", "node-geocoder": "4.2.0", + "nodemailer": "^6.9.4", "reflect-metadata": "0.1.13", "sharp": "0.31.3", "ts-exif-parser": "0.2.2", "ts-node-iptc": "1.0.11", "typeconfig": "2.1.0", - "xml2js": "0.4.23", - "typeorm": "0.3.12" + "typeorm": "0.3.12", + "xml2js": "0.4.23" }, "devDependencies": { "@angular-builders/custom-webpack": "15.0.0", diff --git a/src/backend/middlewares/GalleryMWs.ts b/src/backend/middlewares/GalleryMWs.ts index 7f1f67dc..bf831776 100644 --- a/src/backend/middlewares/GalleryMWs.ts +++ b/src/backend/middlewares/GalleryMWs.ts @@ -3,9 +3,7 @@ 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 { - ParentDirectoryDTO, -} from '../../common/entities/DirectoryDTO'; +import {ParentDirectoryDTO,} from '../../common/entities/DirectoryDTO'; import {ObjectManagers} from '../model/ObjectManagers'; import {ContentWrapper} from '../../common/entities/ConentWrapper'; import {ProjectPath} from '../ProjectPath'; @@ -14,13 +12,11 @@ import {UserDTOUtils} from '../../common/entities/UserDTO'; import {MediaDTO, MediaDTOUtils} from '../../common/entities/MediaDTO'; import {QueryParams} from '../../common/QueryParams'; import {VideoProcessing} from '../model/fileprocessing/VideoProcessing'; -import { - SearchQueryDTO, - SearchQueryTypes, -} from '../../common/entities/SearchQueryDTO'; +import {SearchQueryDTO, SearchQueryTypes,} from '../../common/entities/SearchQueryDTO'; import {LocationLookupException} from '../exceptions/LocationLookupException'; import {SupportedFormats} from '../../common/SupportedFormats'; import {ServerTime} from './ServerTimingMWs'; +import {SortingMethods} from '../../common/entities/SortingMethods'; export class GalleryMWs { @ServerTime('1.db', 'List Directory') @@ -325,16 +321,16 @@ export class GalleryMWs { req.params['searchQueryDTO'] as string ); - const photo = - await ObjectManagers.getInstance().SearchManager.getRandomPhoto(query); - if (!photo) { + const photos = + await ObjectManagers.getInstance().SearchManager.getNMedia(query, [SortingMethods.random], 1, true); + if (!photos || photos.length !== 1) { return next(new ErrorDTO(ErrorCodes.INPUT_ERROR, 'No photo found')); } req.params['mediaPath'] = path.join( - photo.directory.path, - photo.directory.name, - photo.name + photos[0].directory.path, + photos[0].directory.name, + photos[0].name ); return next(); } catch (e) { diff --git a/src/backend/model/database/SearchManager.ts b/src/backend/model/database/SearchManager.ts index 579d0004..6079940f 100644 --- a/src/backend/model/database/SearchManager.ts +++ b/src/backend/model/database/SearchManager.ts @@ -30,11 +30,11 @@ import { } 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 {Utils} from '../../../common/Utils'; import {FileEntity} from './enitites/FileEntity'; import {SQL_COLLATE} from './enitites/EntityUtils'; +import {SortingMethods} from '../../../common/entities/SortingMethods'; export class SearchManager { private DIRECTORY_SELECT = [ @@ -348,19 +348,61 @@ export class SearchManager { return result; } - public async getRandomPhoto(query: SearchQueryDTO): Promise { + + private static setSorting( + query: SelectQueryBuilder, + sortings: SortingMethods[] + ): SelectQueryBuilder { + if (!sortings || !Array.isArray(sortings)) { + return query; + } + if (sortings.includes(SortingMethods.random) && sortings.length > 0) { + throw new Error('Error during applying sorting: Can\' randomize and also sort the result. Bad input:' + sortings.map(s => SortingMethods[s]).join(', ')); + } + for (const sort of sortings) { + switch (sort) { + case SortingMethods.descDate: + query.addOrderBy('media.metadata.creationDate', 'DESC'); + break; + case SortingMethods.ascDate: + query.addOrderBy('media.metadata.creationDate', 'ASC'); + break; + case SortingMethods.descRating: + query.addOrderBy('media.metadata.rating', 'DESC'); + break; + case SortingMethods.ascRating: + query.addOrderBy('media.metadata.rating', 'ASC'); + break; + case SortingMethods.descName: + query.addOrderBy('media.name', 'DESC'); + break; + case SortingMethods.ascName: + query.addOrderBy('media.name', 'ASC'); + break; + case SortingMethods.random: + if (Config.Database.type === DatabaseType.mysql) { + query.groupBy('RAND(), media.id'); + } + query.groupBy('RANDOM()'); + break; + } + } + + return query; + } + + public async getNMedia(query: SearchQueryDTO, sortings: SortingMethods[], take: number, photoOnly = false) { const connection = await SQLConnection.getConnection(); const sqlQuery: SelectQueryBuilder = connection - .getRepository(PhotoEntity) + .getRepository(photoOnly ? PhotoEntity : MediaEntity) .createQueryBuilder('media') .select(['media', ...this.DIRECTORY_SELECT]) .innerJoin('media.directory', 'directory') .where(await this.prepareAndBuildWhereQuery(query)); + SearchManager.setSorting(sqlQuery, sortings); + + return sqlQuery.limit(take).getMany(); - if (Config.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 { diff --git a/src/backend/model/jobs/JobRepository.ts b/src/backend/model/jobs/JobRepository.ts index 2be563a6..367d6402 100644 --- a/src/backend/model/jobs/JobRepository.ts +++ b/src/backend/model/jobs/JobRepository.ts @@ -10,6 +10,7 @@ import {PreviewRestJob} from './jobs/PreviewResetJob'; import {GPXCompressionJob} from './jobs/GPXCompressionJob'; import {AlbumRestJob} from './jobs/AlbumResetJob'; import {GPXCompressionResetJob} from './jobs/GPXCompressionResetJob'; +import {TopPickSendJob} from './jobs/TopPickSendJob'; export class JobRepository { private static instance: JobRepository = null; @@ -45,3 +46,4 @@ JobRepository.Instance.register(new GPXCompressionJob()); JobRepository.Instance.register(new TempFolderCleaningJob()); JobRepository.Instance.register(new AlbumRestJob()); JobRepository.Instance.register(new GPXCompressionResetJob()); +JobRepository.Instance.register(new TopPickSendJob()); diff --git a/src/backend/model/jobs/jobs/PreviewFillingJob.ts b/src/backend/model/jobs/jobs/PreviewFillingJob.ts index 333adbcc..9eae9be0 100644 --- a/src/backend/model/jobs/jobs/PreviewFillingJob.ts +++ b/src/backend/model/jobs/jobs/PreviewFillingJob.ts @@ -1,11 +1,6 @@ -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'; export class PreviewFillingJob extends Job { public readonly Name = DefaultsJobs[DefaultsJobs['Preview Filling']]; @@ -18,7 +13,7 @@ export class PreviewFillingJob extends Job { } protected async init(): Promise { - // abstract function + this.status = 'Persons'; } protected async step(): Promise { diff --git a/src/backend/model/jobs/jobs/TopPickSendJob.ts b/src/backend/model/jobs/jobs/TopPickSendJob.ts new file mode 100644 index 00000000..1ed05111 --- /dev/null +++ b/src/backend/model/jobs/jobs/TopPickSendJob.ts @@ -0,0 +1,74 @@ +import {ConfigTemplateEntry, DefaultsJobs,} from '../../../../common/entities/job/JobDTO'; +import {Job} from './Job'; +import {backendTexts} from '../../../../common/BackendTexts'; +import {SortingMethods} from '../../../../common/entities/SortingMethods'; +import {DatePatternFrequency, DatePatternSearch, SearchQueryDTO, SearchQueryTypes} from '../../../../common/entities/SearchQueryDTO'; +import {ObjectManagers} from '../../ObjectManagers'; +import {PhotoEntity} from '../../database/enitites/PhotoEntity'; + + +export class TopPickSendJob extends Job<{ searchQuery: SearchQueryDTO, sortBy: SortingMethods[], pickAmount: number }> { + public readonly Name = DefaultsJobs[DefaultsJobs['Top Pick Sending']]; + public readonly Supported: boolean = true; + public readonly ConfigTemplate: ConfigTemplateEntry[] = [ + { + id: 'searchQuery', + type: 'SearchQuery', + name: backendTexts.searchQuery.name, + description: backendTexts.searchQuery.description, + defaultValue: { + type: SearchQueryTypes.date_pattern, + daysLength: 7, + frequency: DatePatternFrequency.every_year + } as DatePatternSearch, + }, { + id: 'sortby', + type: 'sort-array', + name: backendTexts.sortBy.name, + description: backendTexts.sortBy.description, + defaultValue: [SortingMethods.descRating], + }, { + id: 'pickAmount', + type: 'number', + name: backendTexts.pickAmount.name, + description: backendTexts.pickAmount.description, + defaultValue: 5, + }, + ]; + private status: 'Listing' | 'Sending' = 'Listing'; + private mediaList: PhotoEntity[] = []; + + + protected async init(): Promise { + this.status = 'Listing'; + this.mediaList = []; + this.Progress.Left = 2; + } + + + protected async step(): Promise { + + switch (this.status) { + case 'Listing': + if (!await this.stepListing()) { + this.status = 'Sending'; + } + return true; + case 'Sending': + await this.stepSending(); + } + return false; + } + + private async stepListing(): Promise { + this.Progress.log('Collecting Photos and videos to Send'); + this.Progress.Processed++; + this.mediaList = await ObjectManagers.getInstance().SearchManager.getNMedia(this.config.searchQuery, this.config.sortBy, this.config.pickAmount); + // console.log(this.mediaList); + return false; + } + + private async stepSending(): Promise { + return false; + } +} diff --git a/src/common/BackendTexts.ts b/src/common/BackendTexts.ts index 940d4b22..1503548c 100644 --- a/src/common/BackendTexts.ts +++ b/src/common/BackendTexts.ts @@ -3,4 +3,8 @@ export const backendTexts = { indexedFilesOnly: { name: 10, description: 12 }, sizeToGenerate: { name: 20, description: 22 }, indexChangesOnly: { name: 30, description: 32 }, + searchQuery: { name: 40, description: 42 }, + sortBy: { name: 40, description: 42 }, + pickAmount: { name: 40, description: 42 }, + }; diff --git a/src/common/entities/job/JobDTO.ts b/src/common/entities/job/JobDTO.ts index 6106e586..4d843ee7 100644 --- a/src/common/entities/job/JobDTO.ts +++ b/src/common/entities/job/JobDTO.ts @@ -1,6 +1,6 @@ import {backendText} from '../../BackendTexts'; -export type fieldType = 'string' | 'number' | 'boolean' | 'number-array'; +export type fieldType = 'string' | 'number' | 'boolean' | 'number-array' | 'SearchQuery' | 'sort-array'; export enum DefaultsJobs { Indexing = 1, @@ -13,7 +13,8 @@ export enum DefaultsJobs { 'Preview Reset' = 8, 'GPX Compression' = 9, 'Album Reset' = 10, - 'Delete Compressed GPX' = 11 + 'Delete Compressed GPX' = 11, + 'Top Pick Sending' = 12 } export interface ConfigTemplateEntry { From 763e982d2d275c6e4e82377c678c97020255c888 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 30 Jul 2023 12:21:11 +0200 Subject: [PATCH 02/22] Creating E-mail messenger job and e-mail messaging config #683 --- package-lock.json | 19 +++ package.json | 3 +- src/backend/middlewares/RenderingMWs.ts | 2 + .../model/diagnostics/ConfigDiagnostics.ts | 61 ++++++++++ src/backend/model/jobs/jobs/Job.ts | 1 + src/backend/model/jobs/jobs/TopPickSendJob.ts | 46 +++++++- .../mediamessengers/EmailMediaMessenger.ts | 43 +++++++ src/common/BackendTexts.ts | 16 ++- src/common/config/private/MessagingConfig.ts | 109 ++++++++++++++++++ src/common/config/private/PrivateConfig.ts | 19 ++- src/common/entities/job/JobDTO.ts | 2 +- src/common/entities/job/JobProgressDTO.ts | 1 + src/frontend/app/model/backendtext.service.ts | 30 ++++- .../settings/template/template.component.html | 4 + .../job-progress.settings.component.ts | 2 + .../settings/workflow/workflow.component.css | 3 + .../settings/workflow/workflow.component.html | 59 +++++++++- .../settings/workflow/workflow.component.ts | 18 +++ 18 files changed, 424 insertions(+), 14 deletions(-) create mode 100644 src/backend/model/mediamessengers/EmailMediaMessenger.ts create mode 100644 src/common/config/private/MessagingConfig.ts diff --git a/package-lock.json b/package-lock.json index 9c2bdedb..897f635a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -76,6 +76,7 @@ "@types/leaflet.markercluster": "1.5.1", "@types/node": "18.15.0", "@types/node-geocoder": "4.2.0", + "@types/nodemailer": "^6.4.9", "@types/sharp": "0.31.1", "@types/xml2js": "0.4.11", "@typescript-eslint/eslint-plugin": "5.54.1", @@ -4892,6 +4893,15 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/@types/nodemailer": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.9.tgz", + "integrity": "sha512-XYG8Gv+sHjaOtUpiuytahMy2mM3rectgroNbs6R3djZEKmPNiIJwe9KqOJBGzKKnNZNKvnuvmugBgpq3w/S0ig==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -26718,6 +26728,15 @@ } } }, + "@types/nodemailer": { + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.9.tgz", + "integrity": "sha512-XYG8Gv+sHjaOtUpiuytahMy2mM3rectgroNbs6R3djZEKmPNiIJwe9KqOJBGzKKnNZNKvnuvmugBgpq3w/S0ig==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", diff --git a/package.json b/package.json index ec080e78..f9079d42 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "image-size": "1.0.2", "locale": "0.1.0", "node-geocoder": "4.2.0", - "nodemailer": "^6.9.4", + "nodemailer": "6.9.4", "reflect-metadata": "0.1.13", "sharp": "0.31.3", "ts-exif-parser": "0.2.2", @@ -96,6 +96,7 @@ "@types/leaflet.markercluster": "1.5.1", "@types/node": "18.15.0", "@types/node-geocoder": "4.2.0", + "@types/nodemailer": "6.4.9", "@types/sharp": "0.31.1", "@types/xml2js": "0.4.11", "@typescript-eslint/eslint-plugin": "5.54.1", diff --git a/src/backend/middlewares/RenderingMWs.ts b/src/backend/middlewares/RenderingMWs.ts index b1edfea0..926d5194 100644 --- a/src/backend/middlewares/RenderingMWs.ts +++ b/src/backend/middlewares/RenderingMWs.ts @@ -9,6 +9,7 @@ import {SharingDTO} from '../../common/entities/SharingDTO'; import {Utils} from '../../common/Utils'; import {LoggerRouter} from '../routes/LoggerRouter'; import {TAGS} from '../../common/config/public/ClientConfig'; +import {ConfigDiagnostics} from '../model/diagnostics/ConfigDiagnostics'; const forcedDebug = process.env['NODE_ENV'] === 'debug'; @@ -108,6 +109,7 @@ export class RenderingMWs { res: Response ): Promise { const originalConf = await Config.original(); + await ConfigDiagnostics.checkEnvironment(originalConf); // These are sensitive information, do not send to the client side originalConf.Server.sessionSecret = null; const message = new Message( diff --git a/src/backend/model/diagnostics/ConfigDiagnostics.ts b/src/backend/model/diagnostics/ConfigDiagnostics.ts index be3079c7..334cd7f3 100644 --- a/src/backend/model/diagnostics/ConfigDiagnostics.ts +++ b/src/backend/model/diagnostics/ConfigDiagnostics.ts @@ -26,6 +26,8 @@ import { import {SearchQueryParser} from '../../../common/SearchQueryParser'; import {SearchQueryTypes, TextSearch,} from '../../../common/entities/SearchQueryDTO'; import {Utils} from '../../../common/Utils'; +import {createTransport} from 'nodemailer'; +import {EmailMessagingType, MessagingConfig} from '../../../common/config/private/MessagingConfig'; const LOG_TAG = '[ConfigDiagnostics]'; @@ -79,6 +81,14 @@ export class ConfigDiagnostics { } } + private static async testEmailMessagingConfig(Messaging: MessagingConfig, config: PrivateConfigClass): Promise { + Logger.debug(LOG_TAG, 'Testing EmailMessaging config'); + + if(Messaging.Email.type === EmailMessagingType.sendmail && !Config.Environment.sendMailAvailable){ + throw new Error('sendmail e-mail sending method is not supported as the sendmail application cannot be found in the OS.') + } + } + static testVideoConfig(videoConfig: ServerVideoConfig, config: PrivateConfigClass): Promise { Logger.debug(LOG_TAG, 'Testing video config with ffmpeg test'); @@ -285,15 +295,48 @@ export class ConfigDiagnostics { await ConfigDiagnostics.testSharingConfig(config.Sharing, config); await ConfigDiagnostics.testRandomPhotoConfig(config.Sharing, config); await ConfigDiagnostics.testMapConfig(config.Map); + await ConfigDiagnostics.testEmailMessagingConfig(config.Messaging, config); } + static async checkEnvironment(Config:PrivateConfigClass): Promise { + Logger.debug(LOG_TAG, 'Checking sendmail availability'); + const transporter = createTransport({ + sendmail: true, + }); + try { + Config.Environment.sendMailAvailable = await transporter.verify(); + } catch (e) { + Config.Environment.sendMailAvailable = false; + } + if (!Config.Environment.sendMailAvailable) { + Config.Messaging.Email.type = EmailMessagingType.SMTP; + Logger.info(LOG_TAG, 'Sendmail is not available on the OS. You will need to use an SMTP server if you wish the app to send mails.'); + } + } + static async runDiagnostics(): Promise { if (process.env['NODE_ENV'] === 'debug') { NotificationManager.warning('You are running the application with NODE_ENV=debug. This exposes a lot of debug information that can be a security vulnerability. Set NODE_ENV=production, when you finished debugging.'); } + try { + await ConfigDiagnostics.checkEnvironment(Config); + } catch (ex) { + const err: Error = ex; + NotificationManager.error( + 'Error during checking environment', + err.toString() + ); + Logger.error( + LOG_TAG, + 'Error during checking environment', + err.toString() + ); + process.exit(1); + } + try { await ConfigDiagnostics.testDatabase(Config.Database); } catch (ex) { @@ -525,5 +568,23 @@ export class ConfigDiagnostics { ); Config.Map.mapProvider = MapProviders.OpenStreetMap; } + try { + await ConfigDiagnostics.testEmailMessagingConfig(Config.Messaging, Config); + } catch (ex) { + const err: Error = ex; + NotificationManager.warning( + 'Setting to SMTP method.', + err.toString() + ); + Logger.warn( + LOG_TAG, + 'Setting to SMTP method.', + err.toString() + ); + Config.Messaging.Email.type = EmailMessagingType.SMTP; + } + + } + } diff --git a/src/backend/model/jobs/jobs/Job.ts b/src/backend/model/jobs/jobs/Job.ts index adf77084..3ed5e745 100644 --- a/src/backend/model/jobs/jobs/Job.ts +++ b/src/backend/model/jobs/jobs/Job.ts @@ -154,6 +154,7 @@ export abstract class Job = Record> i this.run(); } catch (e) { Logger.error(LOG_TAG, e); + this.Progress.State = JobProgressStates.failed; } }); } diff --git a/src/backend/model/jobs/jobs/TopPickSendJob.ts b/src/backend/model/jobs/jobs/TopPickSendJob.ts index 1ed05111..094927b5 100644 --- a/src/backend/model/jobs/jobs/TopPickSendJob.ts +++ b/src/backend/model/jobs/jobs/TopPickSendJob.ts @@ -5,9 +5,18 @@ import {SortingMethods} from '../../../../common/entities/SortingMethods'; import {DatePatternFrequency, DatePatternSearch, SearchQueryDTO, SearchQueryTypes} from '../../../../common/entities/SearchQueryDTO'; import {ObjectManagers} from '../../ObjectManagers'; import {PhotoEntity} from '../../database/enitites/PhotoEntity'; +import {EmailMediaMessenger} from '../../mediamessengers/EmailMediaMessenger'; -export class TopPickSendJob extends Job<{ searchQuery: SearchQueryDTO, sortBy: SortingMethods[], pickAmount: number }> { +export class TopPickSendJob extends Job<{ + searchQuery: SearchQueryDTO, + sortBy: SortingMethods[], + pickAmount: number, + emailTo: string, + emailFrom: string, + emailSubject: string, + emailText: string, +}> { public readonly Name = DefaultsJobs[DefaultsJobs['Top Pick Sending']]; public readonly Supported: boolean = true; public readonly ConfigTemplate: ConfigTemplateEntry[] = [ @@ -33,6 +42,30 @@ export class TopPickSendJob extends Job<{ searchQuery: SearchQueryDTO, sortBy: S name: backendTexts.pickAmount.name, description: backendTexts.pickAmount.description, defaultValue: 5, + }, { + id: 'emailTo', + type: 'email', + name: backendTexts.emailTo.name, + description: backendTexts.emailTo.description, + defaultValue: '', + }, { + id: 'emailFrom', + type: 'email', + name: backendTexts.emailFrom.name, + description: backendTexts.emailFrom.description, + defaultValue: 'norelpy@pigallery2.com', + }, { + id: 'emailSubject', + type: 'string', + name: backendTexts.emailSubject.name, + description: backendTexts.emailSubject.description, + defaultValue: 'Latest photos for you', + }, { + id: 'emailText', + type: 'string', + name: backendTexts.emailText.name, + description: backendTexts.emailText.description, + defaultValue: 'I hand picked these photos just for you:', }, ]; private status: 'Listing' | 'Sending' = 'Listing'; @@ -64,11 +97,20 @@ export class TopPickSendJob extends Job<{ searchQuery: SearchQueryDTO, sortBy: S this.Progress.log('Collecting Photos and videos to Send'); this.Progress.Processed++; this.mediaList = await ObjectManagers.getInstance().SearchManager.getNMedia(this.config.searchQuery, this.config.sortBy, this.config.pickAmount); - // console.log(this.mediaList); + // console.log(this.mediaList); return false; } private async stepSending(): Promise { + this.Progress.log('Sending emails'); + const messenger = new EmailMediaMessenger(); + await messenger.sendMedia({ + from: this.config.emailFrom, + to: this.config.emailTo, + subject: this.config.emailSubject, + text: this.config.emailText + }, this.mediaList); + this.Progress.Processed++; return false; } } diff --git a/src/backend/model/mediamessengers/EmailMediaMessenger.ts b/src/backend/model/mediamessengers/EmailMediaMessenger.ts new file mode 100644 index 00000000..7aae180b --- /dev/null +++ b/src/backend/model/mediamessengers/EmailMediaMessenger.ts @@ -0,0 +1,43 @@ +import {createTransport, Transporter} from 'nodemailer'; +import {MediaDTO} from '../../../common/entities/MediaDTO'; +import {Config} from '../../../common/config/private/Config'; +import {EmailMessagingType} from '../../../common/config/private/MessagingConfig'; + +export class EmailMediaMessenger { + transporter: Transporter; + + constructor() { + if (Config.Messaging.Email.type === EmailMessagingType.sendmail) { + this.transporter = createTransport({ + sendmail: true + }); + } else { + this.transporter = createTransport({ + host: Config.Messaging.Email.smtp.host, + port: Config.Messaging.Email.smtp.port, + secure: Config.Messaging.Email.smtp.secure, + requireTLS: Config.Messaging.Email.smtp.requireTLS, + auth: { + user: Config.Messaging.Email.smtp.user, + pass: Config.Messaging.Email.smtp.password + } + }); + } + + } + + public async sendMedia(mailSettings: { + from: string, + to: string, + subject: string, + text: string + }, media: MediaDTO[]) { + + return await this.transporter.sendMail({ + from: mailSettings.from, + to: mailSettings.to, + subject: mailSettings.subject, + text: mailSettings.text + media.map(m => m.name).join(', ') + }); + } +} diff --git a/src/common/BackendTexts.ts b/src/common/BackendTexts.ts index 1503548c..a2ce401e 100644 --- a/src/common/BackendTexts.ts +++ b/src/common/BackendTexts.ts @@ -1,10 +1,14 @@ export type backendText = number; export const backendTexts = { - indexedFilesOnly: { name: 10, description: 12 }, - sizeToGenerate: { name: 20, description: 22 }, - indexChangesOnly: { name: 30, description: 32 }, - searchQuery: { name: 40, description: 42 }, - sortBy: { name: 40, description: 42 }, - pickAmount: { name: 40, description: 42 }, + indexedFilesOnly: {name: 10, description: 12}, + sizeToGenerate: {name: 20, description: 22}, + indexChangesOnly: {name: 30, description: 32}, + searchQuery: {name: 40, description: 42}, + sortBy: {name: 100, description: 102}, + pickAmount: {name: 50, description: 52}, + emailTo: {name: 60, description: 62}, + emailFrom: {name: 70, description: 72}, + emailSubject: {name: 80, description: 82}, + emailText: {name: 90, description: 92} }; diff --git a/src/common/config/private/MessagingConfig.ts b/src/common/config/private/MessagingConfig.ts new file mode 100644 index 00000000..bf23c68d --- /dev/null +++ b/src/common/config/private/MessagingConfig.ts @@ -0,0 +1,109 @@ +/* eslint-disable @typescript-eslint/no-inferrable-types */ +import {SubConfigClass} from '../../../../node_modules/typeconfig/src/decorators/class/SubConfigClass'; +import {ConfigPriority, TAGS} from '../public/ClientConfig'; +import {ConfigProperty} from '../../../../node_modules/typeconfig/src/decorators/property/ConfigPropoerty'; +import {ServerConfig} from './PrivateConfig'; + +export enum EmailMessagingType { + sendmail = 1, + SMTP = 2, +} + +@SubConfigClass({softReadonly: true}) +export class EmailSMTPMessagingConfig { + @ConfigProperty({ + tags: { + name: $localize`Host`, + priority: ConfigPriority.advanced, + hint: 'smtp.example.com' + }, + description: $localize`SMTP host server` + }) + host: string = ''; + + @ConfigProperty({ + tags: { + name: $localize`Port`, + priority: ConfigPriority.advanced, + }, + description: $localize`SMTP server's port` + }) + port: number = 587; + + @ConfigProperty({ + tags: { + name: $localize`isSecure`, + priority: ConfigPriority.advanced, + }, + description: $localize`Is the connection secure. See https://nodemailer.com/smtp/#tls-options for more details` + }) + secure: boolean = false; + + @ConfigProperty({ + tags: { + name: $localize`TLS required`, + priority: ConfigPriority.advanced, + }, + description: $localize`if this is true and secure is false then Nodemailer (used library in the background) tries to use STARTTLS. See https://nodemailer.com/smtp/#tls-options for more details` + }) + requireTLS: boolean = true; + + + @ConfigProperty({ + tags: { + name: $localize`User`, + priority: ConfigPriority.advanced, + }, + description: $localize`User to connect to the SMTP server.` + }) + user: string = ''; + + + @ConfigProperty({ + tags: { + name: $localize`Password`, + priority: ConfigPriority.advanced, + }, + type: 'password', + description: $localize`Password to connect to the SMTP server.` + }) + password: string = ''; + +} + +@SubConfigClass({softReadonly: true}) +export class EmailMessagingConfig { + @ConfigProperty({ + type: EmailMessagingType, + tags: + { + name: $localize`Sending method`, + priority: ConfigPriority.advanced, + uiDisabled: (sc: EmailMessagingConfig, c: ServerConfig) => !c.Environment.sendMailAvailable + } as TAGS, + description: $localize`Sendmail uses the built in unix binary if available. STMP connects to any STMP server of your choice.` + }) + type: EmailMessagingType = EmailMessagingType.sendmail; + + @ConfigProperty({ + tags: + { + name: $localize`SMTP`, + relevant: (c: any) => c.type === EmailMessagingType.SMTP, + } + }) + smtp?: EmailSMTPMessagingConfig = new EmailSMTPMessagingConfig(); + +} + +@SubConfigClass({softReadonly: true}) +export class MessagingConfig { + @ConfigProperty({ + tags: + { + name: $localize`Email`, + }, + description: $localize`The app uses Nodemailer in the background for sending e-mails. Refer to https://nodemailer.com/usage/ if some options are not clear.` + }) + Email: EmailMessagingConfig = new EmailMessagingConfig(); +} diff --git a/src/common/config/private/PrivateConfig.ts b/src/common/config/private/PrivateConfig.ts index 8b924325..08ca3a71 100644 --- a/src/common/config/private/PrivateConfig.ts +++ b/src/common/config/private/PrivateConfig.ts @@ -30,6 +30,7 @@ import {DefaultsJobs} from '../../entities/job/JobDTO'; import {SearchQueryDTO, SearchQueryTypes, TextSearch,} from '../../entities/SearchQueryDTO'; import {SortingMethods} from '../../entities/SortingMethods'; import {UserRoles} from '../../entities/UserDTO'; +import {MessagingConfig} from './MessagingConfig'; declare let $localize: (s: TemplateStringsArray) => string; @@ -395,7 +396,7 @@ export class ServerMetaFileConfig extends ClientMetaFileConfig { uiJob: [{ job: DefaultsJobs[DefaultsJobs['GPX Compression']], relevant: (c) => c.MetaFile.GPXCompressing.enabled - },{ + }, { job: DefaultsJobs[DefaultsJobs['Delete Compressed GPX']], relevant: (c) => c.MetaFile.GPXCompressing.enabled }] @@ -1049,8 +1050,14 @@ export class ServerEnvironmentConfig { buildCommitHash: string | undefined; @ConfigProperty({volatile: true}) isDocker: boolean | undefined; + @ConfigProperty({ + volatile: true, + description: 'App updates on start-up if sendmail binary is available' + }) + sendMailAvailable: boolean | undefined; } + @SubConfigClass({softReadonly: true}) export class ServerConfig extends ClientConfig { @@ -1144,6 +1151,16 @@ export class ServerConfig extends ClientConfig { }) Duplicates: ServerDuplicatesConfig = new ServerDuplicatesConfig(); + @ConfigProperty({ + tags: { + name: $localize`Messaging`, + uiIcon: 'chat', + githubIssue: 683 + } as TAGS, + description: $localize`The App can send messages (like photos on the same day a year ago. aka: "Top Pick"). Here you can configure the delivery method.` + }) + Messaging: MessagingConfig = new MessagingConfig(); + @ConfigProperty({ tags: { name: $localize`Jobs`, diff --git a/src/common/entities/job/JobDTO.ts b/src/common/entities/job/JobDTO.ts index 4d843ee7..f06fe435 100644 --- a/src/common/entities/job/JobDTO.ts +++ b/src/common/entities/job/JobDTO.ts @@ -1,6 +1,6 @@ import {backendText} from '../../BackendTexts'; -export type fieldType = 'string' | 'number' | 'boolean' | 'number-array' | 'SearchQuery' | 'sort-array'; +export type fieldType = 'string' | 'email' | 'number' | 'boolean' | 'number-array' | 'SearchQuery' | 'sort-array'; export enum DefaultsJobs { Indexing = 1, diff --git a/src/common/entities/job/JobProgressDTO.ts b/src/common/entities/job/JobProgressDTO.ts index 7aecb71b..a0a1ed71 100644 --- a/src/common/entities/job/JobProgressDTO.ts +++ b/src/common/entities/job/JobProgressDTO.ts @@ -4,6 +4,7 @@ export enum JobProgressStates { interrupted = 3, canceled = 4, finished = 5, + failed = 6, } export interface JobProgressLogDTO { diff --git a/src/frontend/app/model/backendtext.service.ts b/src/frontend/app/model/backendtext.service.ts index 738d6e6f..c5af99f8 100644 --- a/src/frontend/app/model/backendtext.service.ts +++ b/src/frontend/app/model/backendtext.service.ts @@ -17,8 +17,34 @@ export class BackendtextService { return $localize`Only checks indexed files.`; case backendTexts.indexChangesOnly.name: return $localize`Index changes only`; - case backendTexts.indexChangesOnly.description: - return $localize`Only indexes a folder if it got changed.`; + case backendTexts.searchQuery.name: + return $localize`Search query`; + case backendTexts.searchQuery.description: + return $localize`Search query to list photos and videos.`; + case backendTexts.sortBy.name: + return $localize`Sorting`; + case backendTexts.sortBy.description: + return $localize`Sorts the photos and videos by this.`; + case backendTexts.pickAmount.name: + return $localize`Pick`; + case backendTexts.pickAmount.description: + return $localize`Number of photos and videos to pick.`; + case backendTexts.emailTo.name: + return $localize`E-mail to`; + case backendTexts.emailTo.description: + return $localize`E-mail address of the recipient.`; + case backendTexts.emailFrom.name: + return $localize`E-mail From`; + case backendTexts.emailFrom.description: + return $localize`E-mail sender address.`; + case backendTexts.emailSubject.name: + return $localize`Subject`; + case backendTexts.emailSubject.description: + return $localize`E-mail subject.`; + case backendTexts.emailText.name: + return $localize`Message`; + case backendTexts.emailText.description: + return $localize`E-mail text.`; default: return null; } diff --git a/src/frontend/app/ui/settings/template/template.component.html b/src/frontend/app/ui/settings/template/template.component.html index e35b8a50..08e88fa5 100644 --- a/src/frontend/app/ui/settings/template/template.component.html +++ b/src/frontend/app/ui/settings/template/template.component.html @@ -71,6 +71,10 @@ let-confPath="confPath"> diff --git a/src/frontend/app/ui/settings/workflow/progress/job-progress.settings.component.ts b/src/frontend/app/ui/settings/workflow/progress/job-progress.settings.component.ts index e8a1a52b..40f62d3d 100644 --- a/src/frontend/app/ui/settings/workflow/progress/job-progress.settings.component.ts +++ b/src/frontend/app/ui/settings/workflow/progress/job-progress.settings.component.ts @@ -115,6 +115,8 @@ export class JobProgressComponent implements OnDestroy, OnChanges { return $localize`interrupted`; case JobProgressStates.finished: return $localize`finished`; + case JobProgressStates.failed: + return $localize`failed`; default: return 'unknown state'; } diff --git a/src/frontend/app/ui/settings/workflow/workflow.component.css b/src/frontend/app/ui/settings/workflow/workflow.component.css index e69de29b..26e62ba9 100644 --- a/src/frontend/app/ui/settings/workflow/workflow.component.css +++ b/src/frontend/app/ui/settings/workflow/workflow.component.css @@ -0,0 +1,3 @@ +app-gallery-search-field { + width: 100%; +} diff --git a/src/frontend/app/ui/settings/workflow/workflow.component.html b/src/frontend/app/ui/settings/workflow/workflow.component.html index be043e5b..7983f4a9 100644 --- a/src/frontend/app/ui/settings/workflow/workflow.component.html +++ b/src/frontend/app/ui/settings/workflow/workflow.component.html @@ -1,6 +1,7 @@
-
+
@@ -175,6 +176,14 @@ [(ngModel)]="schedule.config[configEntry.id]" required> + + + + + + + + + + + +
+
+ + + +
+ +
+ +
+
+
+
+ +
+ +
+
+
diff --git a/src/frontend/app/ui/settings/workflow/workflow.component.ts b/src/frontend/app/ui/settings/workflow/workflow.component.ts index 0e5ed79f..51ae5dc5 100644 --- a/src/frontend/app/ui/settings/workflow/workflow.component.ts +++ b/src/frontend/app/ui/settings/workflow/workflow.component.ts @@ -19,6 +19,8 @@ import { ScheduledJobTriggerConfig } from '../../../../../common/config/private/PrivateConfig'; import {ControlValueAccessor, NG_VALIDATORS, NG_VALUE_ACCESSOR, ValidationErrors, Validator} from '@angular/forms'; +import {enumToTranslatedArray} from '../../EnumTranslations'; +import {SortingMethods} from '../../../../../common/entities/SortingMethods'; @Component({ selector: 'app-settings-workflow', @@ -61,6 +63,9 @@ export class WorkflowComponent implements ControlValueAccessor, Validator, OnIni allowParallelRun: false, }; + SortingMethods = enumToTranslatedArray(SortingMethods); + + error: string; constructor( @@ -278,4 +283,17 @@ export class WorkflowComponent implements ControlValueAccessor, Validator, OnIni this.onTouched = fn; } + + + AsSortArray(configElement: string | number | string[] | number[]): SortingMethods[] { + return configElement as SortingMethods[]; + } + + removeSorting(configElement: string | number | string[] | number[], j: number): void { + (configElement as SortingMethods[]).splice(j); + } + + AddNewSorting(configElement: string | number | string[] | number[]): void { + (configElement as SortingMethods[]).push(SortingMethods.ascDate) + } } From 015a0221c2ec1c90f056c7f2a88faf80fe4b5345 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 30 Jul 2023 12:27:29 +0200 Subject: [PATCH 03/22] Fix random photo tests #683 --- test/backend/unit/model/sql/SearchManager.spec.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/backend/unit/model/sql/SearchManager.spec.ts b/test/backend/unit/model/sql/SearchManager.spec.ts index 61db7c3c..a1b8f92d 100644 --- a/test/backend/unit/model/sql/SearchManager.spec.ts +++ b/test/backend/unit/model/sql/SearchManager.spec.ts @@ -35,6 +35,7 @@ import {AutoCompleteItem} from '../../../../../src/common/entities/AutoCompleteI import {Config} from '../../../../../src/common/config/private/Config'; import {SearchQueryParser} from '../../../../../src/common/SearchQueryParser'; import {FileDTO} from '../../../../../src/common/entities/FileDTO'; +import {SortingMethods} from '../../../../../src/common/entities/SortingMethods'; // eslint-disable-next-line @typescript-eslint/no-var-requires const deepEqualInAnyOrder = require('deep-equal-in-any-order'); @@ -1405,14 +1406,14 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => { } as TextSearch; // eslint-disable-next-line - expect(await sm.getRandomPhoto(query)).to.not.exist; + expect(await sm.getNMedia(query, [SortingMethods.random], 1, true)).to.not.exist; query = ({ text: 'wookiees', matchType: TextSearchQueryMatchTypes.exact_match, type: SearchQueryTypes.keyword } as TextSearch); - expect(Utils.clone(await sm.getRandomPhoto(query))).to.deep.equalInAnyOrder(searchifyMedia(pFaceLess)); + expect(Utils.clone(await sm.getNMedia(query, [SortingMethods.random], 1, true))).to.deep.equalInAnyOrder(searchifyMedia(pFaceLess)); }); }); From e1130622081331baa0beb14927fc2249ca34f5e5 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 30 Jul 2023 13:04:23 +0200 Subject: [PATCH 04/22] Fixig backendtext tests #683 --- src/common/BackendTexts.ts | 12 ++++++------ src/frontend/app/model/backendtext.service.spec.ts | 10 +++++----- src/frontend/app/model/backendtext.service.ts | 2 ++ 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/common/BackendTexts.ts b/src/common/BackendTexts.ts index a2ce401e..c29bf961 100644 --- a/src/common/BackendTexts.ts +++ b/src/common/BackendTexts.ts @@ -4,11 +4,11 @@ export const backendTexts = { sizeToGenerate: {name: 20, description: 22}, indexChangesOnly: {name: 30, description: 32}, searchQuery: {name: 40, description: 42}, - sortBy: {name: 100, description: 102}, - pickAmount: {name: 50, description: 52}, - emailTo: {name: 60, description: 62}, - emailFrom: {name: 70, description: 72}, - emailSubject: {name: 80, description: 82}, - emailText: {name: 90, description: 92} + sortBy: {name: 50, description: 52}, + pickAmount: {name: 60, description: 62}, + emailTo: {name: 70, description: 72}, + emailFrom: {name: 80, description: 82}, + emailSubject: {name: 90, description: 92}, + emailText: {name: 100, description: 102} }; diff --git a/src/frontend/app/model/backendtext.service.spec.ts b/src/frontend/app/model/backendtext.service.spec.ts index 09fd03c0..c95c1fae 100644 --- a/src/frontend/app/model/backendtext.service.spec.ts +++ b/src/frontend/app/model/backendtext.service.spec.ts @@ -1,6 +1,6 @@ -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(() => { @@ -9,7 +9,7 @@ describe('BackendTextService', () => { }); }); - it('should call UserDTO service login', inject( + it('should have valid text for all keys', inject( [BackendtextService], (backendTextService: BackendtextService) => { const getTexts = (obj: any) => { @@ -18,7 +18,7 @@ describe('BackendTextService', () => { getTexts(obj[key]); continue; } - expect(backendTextService.get(obj[key])).not.toBe(null); + expect(backendTextService.get(obj[key])).not.toEqual(null, 'Error for key: ' + obj[key] +', ' + key); } }; getTexts(backendTexts); diff --git a/src/frontend/app/model/backendtext.service.ts b/src/frontend/app/model/backendtext.service.ts index c5af99f8..fc5d66a9 100644 --- a/src/frontend/app/model/backendtext.service.ts +++ b/src/frontend/app/model/backendtext.service.ts @@ -17,6 +17,8 @@ export class BackendtextService { return $localize`Only checks indexed files.`; case backendTexts.indexChangesOnly.name: return $localize`Index changes only`; + case backendTexts.indexChangesOnly.description: + return $localize`Only indexes a folder if it got changed.`; case backendTexts.searchQuery.name: return $localize`Search query`; case backendTexts.searchQuery.description: From a408f1b24d8863d3f49d8a68f887c1c6e922c1e6 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 30 Jul 2023 13:25:31 +0200 Subject: [PATCH 05/22] Fixig setting and random photo test #683 --- src/backend/model/database/SearchManager.ts | 2 +- test/backend/integration/routers/admin/SettingsRouter.ts | 2 ++ test/backend/unit/model/sql/SearchManager.spec.ts | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/backend/model/database/SearchManager.ts b/src/backend/model/database/SearchManager.ts index 6079940f..48755562 100644 --- a/src/backend/model/database/SearchManager.ts +++ b/src/backend/model/database/SearchManager.ts @@ -356,7 +356,7 @@ export class SearchManager { if (!sortings || !Array.isArray(sortings)) { return query; } - if (sortings.includes(SortingMethods.random) && sortings.length > 0) { + if (sortings.includes(SortingMethods.random) && sortings.length > 1) { throw new Error('Error during applying sorting: Can\' randomize and also sort the result. Bad input:' + sortings.map(s => SortingMethods[s]).join(', ')); } for (const sort of sortings) { diff --git a/test/backend/integration/routers/admin/SettingsRouter.ts b/test/backend/integration/routers/admin/SettingsRouter.ts index 649e5e7d..36663cf0 100644 --- a/test/backend/integration/routers/admin/SettingsRouter.ts +++ b/test/backend/integration/routers/admin/SettingsRouter.ts @@ -46,7 +46,9 @@ describe('SettingsRouter', () => { result.body.should.be.a('object'); should.equal(result.body.error, null); (result.body.result as ServerConfig).Environment.upTime = null; + (result.body.result as ServerConfig).Environment.sendMailAvailable = null; originalSettings.Environment.upTime = null; + originalSettings.Environment.sendMailAvailable = null; result.body.result.should.deep.equal(JSON.parse(JSON.stringify(originalSettings.toJSON({ attachState: true, attachVolatile: true, diff --git a/test/backend/unit/model/sql/SearchManager.spec.ts b/test/backend/unit/model/sql/SearchManager.spec.ts index a1b8f92d..3837aaee 100644 --- a/test/backend/unit/model/sql/SearchManager.spec.ts +++ b/test/backend/unit/model/sql/SearchManager.spec.ts @@ -1406,14 +1406,14 @@ describe('SearchManager', (sqlHelper: DBTestHelper) => { } as TextSearch; // eslint-disable-next-line - expect(await sm.getNMedia(query, [SortingMethods.random], 1, true)).to.not.exist; + expect(await sm.getNMedia(query, [SortingMethods.random], 1, true)).to.deep.equalInAnyOrder([]); query = ({ text: 'wookiees', matchType: TextSearchQueryMatchTypes.exact_match, type: SearchQueryTypes.keyword } as TextSearch); - expect(Utils.clone(await sm.getNMedia(query, [SortingMethods.random], 1, true))).to.deep.equalInAnyOrder(searchifyMedia(pFaceLess)); + expect(Utils.clone(await sm.getNMedia(query, [SortingMethods.random], 1, true))).to.deep.equalInAnyOrder([searchifyMedia(pFaceLess)]); }); }); From 33ca2040a88495fc92f040ce54b77e1124f09ec7 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 30 Jul 2023 14:06:25 +0200 Subject: [PATCH 06/22] Moving sendMail checking to be only on startup #683 --- src/backend/Environment.ts | 6 ++++++ src/backend/middlewares/RenderingMWs.ts | 1 - src/backend/model/diagnostics/ConfigDiagnostics.ts | 10 ++++++---- src/common/config/private/Config.ts | 4 ++++ src/common/config/private/PrivateConfig.ts | 10 ++++++++-- .../integration/routers/admin/SettingsRouter.ts | 5 ++--- 6 files changed, 26 insertions(+), 10 deletions(-) create mode 100644 src/backend/Environment.ts diff --git a/src/backend/Environment.ts b/src/backend/Environment.ts new file mode 100644 index 00000000..85bdb7c2 --- /dev/null +++ b/src/backend/Environment.ts @@ -0,0 +1,6 @@ +/** + * Keeps the environment context + */ +export const ServerEnvironment = { + sendMailAvailable: false +}; diff --git a/src/backend/middlewares/RenderingMWs.ts b/src/backend/middlewares/RenderingMWs.ts index 926d5194..27e89cac 100644 --- a/src/backend/middlewares/RenderingMWs.ts +++ b/src/backend/middlewares/RenderingMWs.ts @@ -109,7 +109,6 @@ export class RenderingMWs { res: Response ): Promise { const originalConf = await Config.original(); - await ConfigDiagnostics.checkEnvironment(originalConf); // These are sensitive information, do not send to the client side originalConf.Server.sessionSecret = null; const message = new Message( diff --git a/src/backend/model/diagnostics/ConfigDiagnostics.ts b/src/backend/model/diagnostics/ConfigDiagnostics.ts index 334cd7f3..f803763e 100644 --- a/src/backend/model/diagnostics/ConfigDiagnostics.ts +++ b/src/backend/model/diagnostics/ConfigDiagnostics.ts @@ -28,6 +28,7 @@ import {SearchQueryTypes, TextSearch,} from '../../../common/entities/SearchQuer import {Utils} from '../../../common/Utils'; import {createTransport} from 'nodemailer'; import {EmailMessagingType, MessagingConfig} from '../../../common/config/private/MessagingConfig'; +import {ServerEnvironment} from '../../Environment'; const LOG_TAG = '[ConfigDiagnostics]'; @@ -84,8 +85,8 @@ export class ConfigDiagnostics { private static async testEmailMessagingConfig(Messaging: MessagingConfig, config: PrivateConfigClass): Promise { Logger.debug(LOG_TAG, 'Testing EmailMessaging config'); - if(Messaging.Email.type === EmailMessagingType.sendmail && !Config.Environment.sendMailAvailable){ - throw new Error('sendmail e-mail sending method is not supported as the sendmail application cannot be found in the OS.') + if (Messaging.Email.type === EmailMessagingType.sendmail && !Config.Environment.sendMailAvailable) { + throw new Error('sendmail e-mail sending method is not supported as the sendmail application cannot be found in the OS.'); } } @@ -299,7 +300,7 @@ export class ConfigDiagnostics { } - static async checkEnvironment(Config:PrivateConfigClass): Promise { + static async checkAndSetEnvironment(): Promise { Logger.debug(LOG_TAG, 'Checking sendmail availability'); const transporter = createTransport({ sendmail: true, @@ -309,6 +310,7 @@ export class ConfigDiagnostics { } catch (e) { Config.Environment.sendMailAvailable = false; } + ServerEnvironment.sendMailAvailable = Config.Environment.sendMailAvailable; if (!Config.Environment.sendMailAvailable) { Config.Messaging.Email.type = EmailMessagingType.SMTP; Logger.info(LOG_TAG, 'Sendmail is not available on the OS. You will need to use an SMTP server if you wish the app to send mails.'); @@ -322,7 +324,7 @@ export class ConfigDiagnostics { } try { - await ConfigDiagnostics.checkEnvironment(Config); + await ConfigDiagnostics.checkAndSetEnvironment(); } catch (ex) { const err: Error = ex; NotificationManager.error( diff --git a/src/common/config/private/Config.ts b/src/common/config/private/Config.ts index bf517148..b8b74d40 100644 --- a/src/common/config/private/Config.ts +++ b/src/common/config/private/Config.ts @@ -6,6 +6,9 @@ import {ConfigClass, ConfigClassBuilder} from 'typeconfig/node'; import {IConfigClass} from 'typeconfig/common'; import {PasswordHelper} from '../../../backend/model/PasswordHelper'; import {TAGS} from '../public/ClientConfig'; +import {ServerEnvironment} from '../../../backend/Environment'; +import {EmailMessagingType} from './MessagingConfig'; +import {Logger} from '../../../backend/Logger'; declare const process: any; @@ -79,6 +82,7 @@ export class PrivateConfigClass extends ServerConfig { require('../../../../package.json').buildCommitHash; this.Environment.upTime = upTime; this.Environment.isDocker = !!process.env.PI_DOCKER; + this.Environment.sendMailAvailable = ServerEnvironment.sendMailAvailable; } async original(): Promise { diff --git a/src/common/config/private/PrivateConfig.ts b/src/common/config/private/PrivateConfig.ts index 08ca3a71..e9b60a19 100644 --- a/src/common/config/private/PrivateConfig.ts +++ b/src/common/config/private/PrivateConfig.ts @@ -30,7 +30,7 @@ import {DefaultsJobs} from '../../entities/job/JobDTO'; import {SearchQueryDTO, SearchQueryTypes, TextSearch,} from '../../entities/SearchQueryDTO'; import {SortingMethods} from '../../entities/SortingMethods'; import {UserRoles} from '../../entities/UserDTO'; -import {MessagingConfig} from './MessagingConfig'; +import {EmailMessagingType, MessagingConfig} from './MessagingConfig'; declare let $localize: (s: TemplateStringsArray) => string; @@ -904,6 +904,7 @@ export class ServerPreviewConfig { @SubConfigClass({softReadonly: true}) export class ServerMediaConfig extends ClientMediaConfig { @ConfigProperty({ + tags: { name: $localize`Images folder`, priority: ConfigPriority.basic, @@ -1050,8 +1051,13 @@ export class ServerEnvironmentConfig { buildCommitHash: string | undefined; @ConfigProperty({volatile: true}) isDocker: boolean | undefined; - @ConfigProperty({ + @ConfigProperty({ volatile: true, + onNewValue: (value, config) => { + if (value === false) { + config.Messaging.Email.type = EmailMessagingType.SMTP; + } + }, description: 'App updates on start-up if sendmail binary is available' }) sendMailAvailable: boolean | undefined; diff --git a/test/backend/integration/routers/admin/SettingsRouter.ts b/test/backend/integration/routers/admin/SettingsRouter.ts index 36663cf0..6f65578f 100644 --- a/test/backend/integration/routers/admin/SettingsRouter.ts +++ b/test/backend/integration/routers/admin/SettingsRouter.ts @@ -1,12 +1,12 @@ import * as path from 'path'; import * as fs from 'fs'; import {Config} from '../../../../../src/common/config/private/Config'; -import {SQLConnection} from '../../../../../src/backend/model/database/SQLConnection'; import {Server} from '../../../../../src/backend/server'; import {DatabaseType, ServerConfig} from '../../../../../src/common/config/private/PrivateConfig'; import {ProjectPath} from '../../../../../src/backend/ProjectPath'; import {TAGS} from '../../../../../src/common/config/public/ClientConfig'; import {ObjectManagers} from '../../../../../src/backend/model/ObjectManagers'; +import {UserRoles} from '../../../../../src/common/entities/UserDTO'; process.env.NODE_ENV = 'test'; const chai: any = require('chai'); @@ -34,9 +34,8 @@ describe('SettingsRouter', () => { describe('/GET settings', () => { it('it should GET the settings', async () => { Config.Users.authenticationRequired = false; + Config.Users.unAuthenticatedUserRole = UserRoles.Admin; const originalSettings = await Config.original(); - // originalSettings.Server.sessionSecret = null; - // originalSettings.Users.enforcedUsers = null; const srv = new Server(); await srv.onStarted.wait(); const result = await chai.request(srv.App) From 8fc0512998a2063086b302bbbd0d5bb1af8bcb48 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 30 Jul 2023 14:17:15 +0200 Subject: [PATCH 07/22] Fixing getting random photo with mysql #683 --- src/backend/Environment.ts | 4 +--- src/backend/model/database/SearchManager.ts | 3 ++- src/common/config/private/Config.ts | 6 +++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/backend/Environment.ts b/src/backend/Environment.ts index 85bdb7c2..3a059b94 100644 --- a/src/backend/Environment.ts +++ b/src/backend/Environment.ts @@ -1,6 +1,4 @@ /** * Keeps the environment context */ -export const ServerEnvironment = { - sendMailAvailable: false -}; +export const ServerEnvironment: { sendMailAvailable?: boolean } = {}; diff --git a/src/backend/model/database/SearchManager.ts b/src/backend/model/database/SearchManager.ts index 48755562..5e60b33c 100644 --- a/src/backend/model/database/SearchManager.ts +++ b/src/backend/model/database/SearchManager.ts @@ -382,8 +382,9 @@ export class SearchManager { case SortingMethods.random: if (Config.Database.type === DatabaseType.mysql) { query.groupBy('RAND(), media.id'); + } else { + query.groupBy('RANDOM()'); } - query.groupBy('RANDOM()'); break; } } diff --git a/src/common/config/private/Config.ts b/src/common/config/private/Config.ts index b8b74d40..7f1f01b7 100644 --- a/src/common/config/private/Config.ts +++ b/src/common/config/private/Config.ts @@ -7,8 +7,6 @@ import {IConfigClass} from 'typeconfig/common'; import {PasswordHelper} from '../../../backend/model/PasswordHelper'; import {TAGS} from '../public/ClientConfig'; import {ServerEnvironment} from '../../../backend/Environment'; -import {EmailMessagingType} from './MessagingConfig'; -import {Logger} from '../../../backend/Logger'; declare const process: any; @@ -82,7 +80,9 @@ export class PrivateConfigClass extends ServerConfig { require('../../../../package.json').buildCommitHash; this.Environment.upTime = upTime; this.Environment.isDocker = !!process.env.PI_DOCKER; - this.Environment.sendMailAvailable = ServerEnvironment.sendMailAvailable; + if (typeof ServerEnvironment.sendMailAvailable !== 'undefined') { + this.Environment.sendMailAvailable = ServerEnvironment.sendMailAvailable; + } } async original(): Promise { From cb8553896f9cae21488a817490f621d0668034c3 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 30 Jul 2023 14:40:54 +0200 Subject: [PATCH 08/22] Fixing settings setting tests #683 --- src/backend/Environment.ts | 1 + src/common/config/private/Config.ts | 4 ++++ test/backend/unit/middlewares/admin/SettingsMWs.ts | 7 ++++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/backend/Environment.ts b/src/backend/Environment.ts index 3a059b94..9325a3a3 100644 --- a/src/backend/Environment.ts +++ b/src/backend/Environment.ts @@ -1,4 +1,5 @@ /** * Keeps the environment context + * Only use it in the Config constructor */ export const ServerEnvironment: { sendMailAvailable?: boolean } = {}; diff --git a/src/common/config/private/Config.ts b/src/common/config/private/Config.ts index 7f1f01b7..00cda39e 100644 --- a/src/common/config/private/Config.ts +++ b/src/common/config/private/Config.ts @@ -7,6 +7,7 @@ import {IConfigClass} from 'typeconfig/common'; import {PasswordHelper} from '../../../backend/model/PasswordHelper'; import {TAGS} from '../public/ClientConfig'; import {ServerEnvironment} from '../../../backend/Environment'; +import {EmailMessagingType} from './MessagingConfig'; declare const process: any; @@ -82,6 +83,9 @@ export class PrivateConfigClass extends ServerConfig { this.Environment.isDocker = !!process.env.PI_DOCKER; if (typeof ServerEnvironment.sendMailAvailable !== 'undefined') { this.Environment.sendMailAvailable = ServerEnvironment.sendMailAvailable; + if(!this.Environment.sendMailAvailable){ //onNewValue is not yet available as a callback + this.Messaging.Email.type = EmailMessagingType.SMTP; + } } } diff --git a/test/backend/unit/middlewares/admin/SettingsMWs.ts b/test/backend/unit/middlewares/admin/SettingsMWs.ts index f7bd0973..de55023f 100644 --- a/test/backend/unit/middlewares/admin/SettingsMWs.ts +++ b/test/backend/unit/middlewares/admin/SettingsMWs.ts @@ -6,6 +6,8 @@ import {SettingsMWs} from '../../../../../src/backend/middlewares/admin/Settings import {ServerUserConfig} from '../../../../../src/common/config/private/PrivateConfig'; import {Config} from '../../../../../src/common/config/private/Config'; import {UserRoles} from '../../../../../src/common/entities/UserDTO'; +import {ConfigClassBuilder} from '../../../../../node_modules/typeconfig/node'; +import {ServerEnvironment} from '../../../../../src/backend/Environment'; declare const describe: any; @@ -19,6 +21,8 @@ describe('Settings middleware', () => { }); it('should save empty enforced users settings', (done: (err?: any) => void) => { + + ServerEnvironment.sendMailAvailable = false; const req: any = { session: {}, sessionOptions: {}, @@ -26,12 +30,13 @@ describe('Settings middleware', () => { params: {}, body: { settingsPath: 'Users', - settings: new ServerUserConfig() + settings: ConfigClassBuilder.attachPrivateInterface(new ServerUserConfig()).toJSON() } }; req.body.settings.enforcedUsers = []; const next: any = (err: ErrorDTO) => { try { + expect(err).to.be.undefined; expect(Config.Users.enforcedUsers.length).to.be.equal(0); done(); } catch (err) { From 4ab60098e076fbd774cd6d282cb07fe451596b08 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 30 Jul 2023 14:49:55 +0200 Subject: [PATCH 09/22] Improving settingsTest #683 --- test/backend/unit/middlewares/admin/SettingsMWs.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/backend/unit/middlewares/admin/SettingsMWs.ts b/test/backend/unit/middlewares/admin/SettingsMWs.ts index de55023f..7a1ce7fb 100644 --- a/test/backend/unit/middlewares/admin/SettingsMWs.ts +++ b/test/backend/unit/middlewares/admin/SettingsMWs.ts @@ -40,6 +40,7 @@ describe('Settings middleware', () => { expect(Config.Users.enforcedUsers.length).to.be.equal(0); done(); } catch (err) { + console.error(err); done(err); } }; @@ -48,6 +49,8 @@ describe('Settings middleware', () => { }); it('should save enforced users settings', (done: (err?: any) => void) => { + + ServerEnvironment.sendMailAvailable = false; const req: any = { session: {}, sessionOptions: {}, @@ -76,6 +79,7 @@ describe('Settings middleware', () => { done(); }).catch(done); } catch (err) { + console.error(err); done(err); } }; From 1df147401f3a697e44034f684974c6fe02065596 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 30 Jul 2023 15:45:46 +0200 Subject: [PATCH 10/22] Improving settingsTest #683 --- test/backend/unit/middlewares/admin/SettingsMWs.ts | 2 ++ test/backend/unit/model/sql/IndexingManager.spec.ts | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/test/backend/unit/middlewares/admin/SettingsMWs.ts b/test/backend/unit/middlewares/admin/SettingsMWs.ts index 7a1ce7fb..a159def3 100644 --- a/test/backend/unit/middlewares/admin/SettingsMWs.ts +++ b/test/backend/unit/middlewares/admin/SettingsMWs.ts @@ -23,6 +23,7 @@ describe('Settings middleware', () => { it('should save empty enforced users settings', (done: (err?: any) => void) => { ServerEnvironment.sendMailAvailable = false; + Config.Environment.sendMailAvailable = false; const req: any = { session: {}, sessionOptions: {}, @@ -51,6 +52,7 @@ describe('Settings middleware', () => { it('should save enforced users settings', (done: (err?: any) => void) => { ServerEnvironment.sendMailAvailable = false; + Config.Environment.sendMailAvailable = false; const req: any = { session: {}, sessionOptions: {}, diff --git a/test/backend/unit/model/sql/IndexingManager.spec.ts b/test/backend/unit/model/sql/IndexingManager.spec.ts index 08358206..a79d3a09 100644 --- a/test/backend/unit/model/sql/IndexingManager.spec.ts +++ b/test/backend/unit/model/sql/IndexingManager.spec.ts @@ -711,7 +711,6 @@ describe('IndexingManager', (sqlHelper: DBTestHelper) => { const albums = await am.getAlbums(); expect(albums[0].preview).to.be.an('object'); delete albums[0].preview; - console.log(albums); expect(albums).to.be.deep.equal([ { id: 1, From 15c79ad68daa47693febdf8c700145d32c3f8726 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 30 Jul 2023 16:11:46 +0200 Subject: [PATCH 11/22] Improving settingsTest #683 --- test/backend/unit/middlewares/admin/SettingsMWs.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/backend/unit/middlewares/admin/SettingsMWs.ts b/test/backend/unit/middlewares/admin/SettingsMWs.ts index a159def3..8a759748 100644 --- a/test/backend/unit/middlewares/admin/SettingsMWs.ts +++ b/test/backend/unit/middlewares/admin/SettingsMWs.ts @@ -8,6 +8,7 @@ import {Config} from '../../../../../src/common/config/private/Config'; import {UserRoles} from '../../../../../src/common/entities/UserDTO'; import {ConfigClassBuilder} from '../../../../../node_modules/typeconfig/node'; import {ServerEnvironment} from '../../../../../src/backend/Environment'; +import {EmailMessagingType} from '../../../../../src/common/config/private/MessagingConfig'; declare const describe: any; @@ -24,6 +25,7 @@ describe('Settings middleware', () => { ServerEnvironment.sendMailAvailable = false; Config.Environment.sendMailAvailable = false; + Config.Messaging.Email.type = EmailMessagingType.sendmail; const req: any = { session: {}, sessionOptions: {}, @@ -52,7 +54,8 @@ describe('Settings middleware', () => { it('should save enforced users settings', (done: (err?: any) => void) => { ServerEnvironment.sendMailAvailable = false; - Config.Environment.sendMailAvailable = false; + Config.Environment.sendMailAvailable = false + Config.Messaging.Email.type = EmailMessagingType.sendmail;; const req: any = { session: {}, sessionOptions: {}, From f007037bc56530cd5696b773a1b7441e59889462 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 30 Jul 2023 17:22:47 +0200 Subject: [PATCH 12/22] Log Log Log... --- src/backend/middlewares/admin/SettingsMWs.ts | 5 ++++- src/common/config/private/Config.ts | 3 +++ src/common/config/private/PrivateConfig.ts | 2 ++ test/backend/unit/middlewares/admin/SettingsMWs.ts | 3 ++- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/backend/middlewares/admin/SettingsMWs.ts b/src/backend/middlewares/admin/SettingsMWs.ts index 2a8ef6a4..ee70b105 100644 --- a/src/backend/middlewares/admin/SettingsMWs.ts +++ b/src/backend/middlewares/admin/SettingsMWs.ts @@ -27,8 +27,9 @@ export class SettingsMWs { try { let settings = req.body.settings; // Top level settings JSON const settingsPath: string = req.body.settingsPath; // Name of the top level settings - const transformer = await Config.original(); + console.log('pre settings'); + console.log(settings); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore transformer[settingsPath] = settings; @@ -37,6 +38,8 @@ export class SettingsMWs { settings = ConfigClassBuilder.attachPrivateInterface(transformer[settingsPath]).toJSON({ skipTags: {secret: true} as TAGS }); + console.log('post settings'); + console.log(settings); const original = await Config.original(); // only updating explicitly set config (not saving config set by the diagnostics) // eslint-disable-next-line @typescript-eslint/ban-ts-comment diff --git a/src/common/config/private/Config.ts b/src/common/config/private/Config.ts index 00cda39e..b7f980d8 100644 --- a/src/common/config/private/Config.ts +++ b/src/common/config/private/Config.ts @@ -81,12 +81,15 @@ export class PrivateConfigClass extends ServerConfig { require('../../../../package.json').buildCommitHash; this.Environment.upTime = upTime; this.Environment.isDocker = !!process.env.PI_DOCKER; + console.log('CONFIG', ServerEnvironment); if (typeof ServerEnvironment.sendMailAvailable !== 'undefined') { this.Environment.sendMailAvailable = ServerEnvironment.sendMailAvailable; if(!this.Environment.sendMailAvailable){ //onNewValue is not yet available as a callback this.Messaging.Email.type = EmailMessagingType.SMTP; } } + console.log('CONFIG', this.Environment.sendMailAvailable); + console.log('CONFIG', this.Messaging.Email.type); } async original(): Promise { diff --git a/src/common/config/private/PrivateConfig.ts b/src/common/config/private/PrivateConfig.ts index e9b60a19..ffd81878 100644 --- a/src/common/config/private/PrivateConfig.ts +++ b/src/common/config/private/PrivateConfig.ts @@ -1054,9 +1054,11 @@ export class ServerEnvironmentConfig { @ConfigProperty({ volatile: true, onNewValue: (value, config) => { + console.log('onNewValue', value, config.Messaging.Email.type); if (value === false) { config.Messaging.Email.type = EmailMessagingType.SMTP; } + console.log('onNewValue after', value, config.Messaging.Email.type); }, description: 'App updates on start-up if sendmail binary is available' }) diff --git a/test/backend/unit/middlewares/admin/SettingsMWs.ts b/test/backend/unit/middlewares/admin/SettingsMWs.ts index 8a759748..0abc961a 100644 --- a/test/backend/unit/middlewares/admin/SettingsMWs.ts +++ b/test/backend/unit/middlewares/admin/SettingsMWs.ts @@ -36,6 +36,7 @@ describe('Settings middleware', () => { settings: ConfigClassBuilder.attachPrivateInterface(new ServerUserConfig()).toJSON() } }; + console.log('Settings', req); req.body.settings.enforcedUsers = []; const next: any = (err: ErrorDTO) => { try { @@ -55,7 +56,7 @@ describe('Settings middleware', () => { ServerEnvironment.sendMailAvailable = false; Config.Environment.sendMailAvailable = false - Config.Messaging.Email.type = EmailMessagingType.sendmail;; + Config.Messaging.Email.type = EmailMessagingType.sendmail; const req: any = { session: {}, sessionOptions: {}, From b8f9dbe55f5259517e54cdca27dc6cee9f8acc8c Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 30 Jul 2023 17:37:54 +0200 Subject: [PATCH 13/22] tweaking --- src/backend/model/diagnostics/ConfigDiagnostics.ts | 10 +++++----- test/backend/unit/middlewares/admin/SettingsMWs.ts | 13 +++++++++---- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/backend/model/diagnostics/ConfigDiagnostics.ts b/src/backend/model/diagnostics/ConfigDiagnostics.ts index f803763e..caa530f3 100644 --- a/src/backend/model/diagnostics/ConfigDiagnostics.ts +++ b/src/backend/model/diagnostics/ConfigDiagnostics.ts @@ -85,7 +85,7 @@ export class ConfigDiagnostics { private static async testEmailMessagingConfig(Messaging: MessagingConfig, config: PrivateConfigClass): Promise { Logger.debug(LOG_TAG, 'Testing EmailMessaging config'); - if (Messaging.Email.type === EmailMessagingType.sendmail && !Config.Environment.sendMailAvailable) { + if (Messaging.Email.type === EmailMessagingType.sendmail && ServerEnvironment.sendMailAvailable === false) { throw new Error('sendmail e-mail sending method is not supported as the sendmail application cannot be found in the OS.'); } } @@ -306,12 +306,12 @@ export class ConfigDiagnostics { sendmail: true, }); try { - Config.Environment.sendMailAvailable = await transporter.verify(); + ServerEnvironment.sendMailAvailable = await transporter.verify(); } catch (e) { - Config.Environment.sendMailAvailable = false; + ServerEnvironment.sendMailAvailable = false; } - ServerEnvironment.sendMailAvailable = Config.Environment.sendMailAvailable; - if (!Config.Environment.sendMailAvailable) { + Config.Environment.sendMailAvailable = ServerEnvironment.sendMailAvailable; + if (!ServerEnvironment.sendMailAvailable) { Config.Messaging.Email.type = EmailMessagingType.SMTP; Logger.info(LOG_TAG, 'Sendmail is not available on the OS. You will need to use an SMTP server if you wish the app to send mails.'); } diff --git a/test/backend/unit/middlewares/admin/SettingsMWs.ts b/test/backend/unit/middlewares/admin/SettingsMWs.ts index 0abc961a..d0dbc658 100644 --- a/test/backend/unit/middlewares/admin/SettingsMWs.ts +++ b/test/backend/unit/middlewares/admin/SettingsMWs.ts @@ -55,7 +55,7 @@ describe('Settings middleware', () => { it('should save enforced users settings', (done: (err?: any) => void) => { ServerEnvironment.sendMailAvailable = false; - Config.Environment.sendMailAvailable = false + Config.Environment.sendMailAvailable = false; Config.Messaging.Email.type = EmailMessagingType.sendmail; const req: any = { session: {}, @@ -80,9 +80,14 @@ describe('Settings middleware', () => { expect(Config.Users.enforcedUsers[0].name).to.be.equal('Apple'); expect(Config.Users.enforcedUsers.length).to.be.equal(1); Config.original().then((cfg) => { - expect(cfg.Users.enforcedUsers.length).to.be.equal(1); - expect(cfg.Users.enforcedUsers[0].name).to.be.equal('Apple'); - done(); + try { + expect(cfg.Users.enforcedUsers.length).to.be.equal(1); + expect(cfg.Users.enforcedUsers[0].name).to.be.equal('Apple'); + done(); + } catch (err) { + console.error(err); + done(err); + } }).catch(done); } catch (err) { console.error(err); From 7ff54759cdb4b5e47c6e8c4a08c00bd5c03431ac Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 30 Jul 2023 17:45:11 +0200 Subject: [PATCH 14/22] Fixing tests --- test/backend/unit/middlewares/admin/SettingsMWs.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/backend/unit/middlewares/admin/SettingsMWs.ts b/test/backend/unit/middlewares/admin/SettingsMWs.ts index d0dbc658..95848a41 100644 --- a/test/backend/unit/middlewares/admin/SettingsMWs.ts +++ b/test/backend/unit/middlewares/admin/SettingsMWs.ts @@ -25,7 +25,7 @@ describe('Settings middleware', () => { ServerEnvironment.sendMailAvailable = false; Config.Environment.sendMailAvailable = false; - Config.Messaging.Email.type = EmailMessagingType.sendmail; + Config.Messaging.Email.type = EmailMessagingType.SMTP; const req: any = { session: {}, sessionOptions: {}, @@ -56,7 +56,7 @@ describe('Settings middleware', () => { ServerEnvironment.sendMailAvailable = false; Config.Environment.sendMailAvailable = false; - Config.Messaging.Email.type = EmailMessagingType.sendmail; + Config.Messaging.Email.type = EmailMessagingType.SMTP; const req: any = { session: {}, sessionOptions: {}, From a73eb3a6491bfc4c2a5af0853c641d32db97a184 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 30 Jul 2023 17:51:29 +0200 Subject: [PATCH 15/22] LOG lOG --- src/backend/middlewares/admin/SettingsMWs.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/backend/middlewares/admin/SettingsMWs.ts b/src/backend/middlewares/admin/SettingsMWs.ts index ee70b105..43acc3bc 100644 --- a/src/backend/middlewares/admin/SettingsMWs.ts +++ b/src/backend/middlewares/admin/SettingsMWs.ts @@ -45,10 +45,14 @@ export class SettingsMWs { // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore original[settingsPath] = settings; + console.log('orig'); + console.log(original.Messaging); await ConfigDiagnostics.testConfig(original); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore Config[settingsPath] = settings; + console.log('Config'); + console.log(Config.Messaging); await original.save(); await ConfigDiagnostics.runDiagnostics(); Logger.info(LOG_TAG, 'new config:'); From c0cd5bb5b9dbd504a7333e308fb678a51d86cb23 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 30 Jul 2023 17:52:46 +0200 Subject: [PATCH 16/22] LOG lOG 2 --- test/backend/unit/middlewares/admin/SettingsMWs.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/backend/unit/middlewares/admin/SettingsMWs.ts b/test/backend/unit/middlewares/admin/SettingsMWs.ts index 95848a41..cece9829 100644 --- a/test/backend/unit/middlewares/admin/SettingsMWs.ts +++ b/test/backend/unit/middlewares/admin/SettingsMWs.ts @@ -22,7 +22,7 @@ describe('Settings middleware', () => { }); it('should save empty enforced users settings', (done: (err?: any) => void) => { - + console.log('sarting - should save empty enforced users settings'); ServerEnvironment.sendMailAvailable = false; Config.Environment.sendMailAvailable = false; Config.Messaging.Email.type = EmailMessagingType.SMTP; @@ -53,6 +53,7 @@ describe('Settings middleware', () => { }); it('should save enforced users settings', (done: (err?: any) => void) => { + console.log('sarting - should save enforced users settings'); ServerEnvironment.sendMailAvailable = false; Config.Environment.sendMailAvailable = false; From b80a2ae202b1f13f5ab63b93115cab02abc055f7 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 30 Jul 2023 17:58:44 +0200 Subject: [PATCH 17/22] LOG lOG 3 --- src/backend/middlewares/admin/SettingsMWs.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/backend/middlewares/admin/SettingsMWs.ts b/src/backend/middlewares/admin/SettingsMWs.ts index 43acc3bc..b7462a25 100644 --- a/src/backend/middlewares/admin/SettingsMWs.ts +++ b/src/backend/middlewares/admin/SettingsMWs.ts @@ -47,12 +47,16 @@ export class SettingsMWs { original[settingsPath] = settings; console.log('orig'); console.log(original.Messaging); + console.log(original.Messaging.Email); + console.log(original.Messaging.Email.type); await ConfigDiagnostics.testConfig(original); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore Config[settingsPath] = settings; console.log('Config'); console.log(Config.Messaging); + console.log(Config.Messaging.Email); + console.log(Config.Messaging.Email.type); await original.save(); await ConfigDiagnostics.runDiagnostics(); Logger.info(LOG_TAG, 'new config:'); From 378f719f9b0b64b79918ca046a080b84b8ad3bb8 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 30 Jul 2023 18:10:09 +0200 Subject: [PATCH 18/22] LOG lOG 4 --- src/backend/middlewares/admin/SettingsMWs.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/backend/middlewares/admin/SettingsMWs.ts b/src/backend/middlewares/admin/SettingsMWs.ts index b7462a25..651de094 100644 --- a/src/backend/middlewares/admin/SettingsMWs.ts +++ b/src/backend/middlewares/admin/SettingsMWs.ts @@ -28,7 +28,7 @@ export class SettingsMWs { let settings = req.body.settings; // Top level settings JSON const settingsPath: string = req.body.settingsPath; // Name of the top level settings const transformer = await Config.original(); - console.log('pre settings'); + console.log('pre settings' + settingsPath); console.log(settings); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -38,15 +38,19 @@ export class SettingsMWs { settings = ConfigClassBuilder.attachPrivateInterface(transformer[settingsPath]).toJSON({ skipTags: {secret: true} as TAGS }); - console.log('post settings'); + console.log('post settings'+ settingsPath); console.log(settings); const original = await Config.original(); + console.log('orig pre'); + console.log(ConfigClassBuilder.attachPrivateInterface(original.Messaging).toJSON()); + console.log(original.Messaging.Email); + console.log(original.Messaging.Email.type); // only updating explicitly set config (not saving config set by the diagnostics) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore original[settingsPath] = settings; console.log('orig'); - console.log(original.Messaging); + console.log(ConfigClassBuilder.attachPrivateInterface(original.Messaging).toJSON()); console.log(original.Messaging.Email); console.log(original.Messaging.Email.type); await ConfigDiagnostics.testConfig(original); From b8802dbf9ec6cab3485f9fd707dafc8603526a8c Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Sun, 30 Jul 2023 19:10:53 +0200 Subject: [PATCH 19/22] moving config test path --- package-lock.json | 18 +++++++++--------- package.json | 2 +- src/common/config/private/Config.ts | 5 +++-- .../unit/middlewares/admin/SettingsMWs.ts | 8 ++++++-- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 897f635a..119a4518 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,12 +23,12 @@ "image-size": "1.0.2", "locale": "0.1.0", "node-geocoder": "4.2.0", - "nodemailer": "^6.9.4", + "nodemailer": "6.9.4", "reflect-metadata": "0.1.13", "sharp": "0.31.3", "ts-exif-parser": "0.2.2", "ts-node-iptc": "1.0.11", - "typeconfig": "2.1.0", + "typeconfig": "2.1.2", "typeorm": "0.3.12", "xml2js": "0.4.23" }, @@ -76,7 +76,7 @@ "@types/leaflet.markercluster": "1.5.1", "@types/node": "18.15.0", "@types/node-geocoder": "4.2.0", - "@types/nodemailer": "^6.4.9", + "@types/nodemailer": "6.4.9", "@types/sharp": "0.31.1", "@types/xml2js": "0.4.11", "@typescript-eslint/eslint-plugin": "5.54.1", @@ -21689,9 +21689,9 @@ } }, "node_modules/typeconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/typeconfig/-/typeconfig-2.1.0.tgz", - "integrity": "sha512-2Sn2lB8nG9lOvy2jY/4U0HCkqJqc7Fpf8uF5hXaB/+YVnjexX05bfOxUpxIB0fh+Qob7TrVkHxt/2R3aacj8Cw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/typeconfig/-/typeconfig-2.1.2.tgz", + "integrity": "sha512-4Zw3q9LmJ3OeVFMxpvbRYpcrR5mG7TTZUr+bYPepXvWCuqLC4fXLD+cjS1hl8e1oSmum6Xfl4vheFx33VevlqA==", "dependencies": { "minimist": "1.2.8" } @@ -39769,9 +39769,9 @@ } }, "typeconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/typeconfig/-/typeconfig-2.1.0.tgz", - "integrity": "sha512-2Sn2lB8nG9lOvy2jY/4U0HCkqJqc7Fpf8uF5hXaB/+YVnjexX05bfOxUpxIB0fh+Qob7TrVkHxt/2R3aacj8Cw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/typeconfig/-/typeconfig-2.1.2.tgz", + "integrity": "sha512-4Zw3q9LmJ3OeVFMxpvbRYpcrR5mG7TTZUr+bYPepXvWCuqLC4fXLD+cjS1hl8e1oSmum6Xfl4vheFx33VevlqA==", "requires": { "minimist": "1.2.8" } diff --git a/package.json b/package.json index f9079d42..997e9338 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "sharp": "0.31.3", "ts-exif-parser": "0.2.2", "ts-node-iptc": "1.0.11", - "typeconfig": "2.1.0", + "typeconfig": "2.1.2", "typeorm": "0.3.12", "xml2js": "0.4.23" }, diff --git a/src/common/config/private/Config.ts b/src/common/config/private/Config.ts index b7f980d8..97c22eed 100644 --- a/src/common/config/private/Config.ts +++ b/src/common/config/private/Config.ts @@ -18,7 +18,8 @@ const isTesting = ['afterEach', 'after', 'beforeEach', 'before', 'describe', 'it .every((fn) => (global as any)[fn] instanceof Function); @ConfigClass & ServerConfig>({ - configPath: path.join(__dirname, !isTesting ? './../../../../config.json' : './../../../../test/backend/assets/config.json'), + configPath: path.join(__dirname, !isTesting ? './../../../../config.json' : './../../../../test/backend/tmp/config.json'), + crateConfigPathIfNotExists: isTesting, saveIfNotExist: true, attachDescription: true, enumsAsString: true, @@ -84,7 +85,7 @@ export class PrivateConfigClass extends ServerConfig { console.log('CONFIG', ServerEnvironment); if (typeof ServerEnvironment.sendMailAvailable !== 'undefined') { this.Environment.sendMailAvailable = ServerEnvironment.sendMailAvailable; - if(!this.Environment.sendMailAvailable){ //onNewValue is not yet available as a callback + if (!this.Environment.sendMailAvailable) { //onNewValue is not yet available as a callback this.Messaging.Email.type = EmailMessagingType.SMTP; } } diff --git a/test/backend/unit/middlewares/admin/SettingsMWs.ts b/test/backend/unit/middlewares/admin/SettingsMWs.ts index cece9829..1874987a 100644 --- a/test/backend/unit/middlewares/admin/SettingsMWs.ts +++ b/test/backend/unit/middlewares/admin/SettingsMWs.ts @@ -9,6 +9,8 @@ import {UserRoles} from '../../../../../src/common/entities/UserDTO'; import {ConfigClassBuilder} from '../../../../../node_modules/typeconfig/node'; import {ServerEnvironment} from '../../../../../src/backend/Environment'; import {EmailMessagingType} from '../../../../../src/common/config/private/MessagingConfig'; +import * as fs from 'fs'; +import * as path from 'path'; declare const describe: any; @@ -17,8 +19,10 @@ declare const beforeEach: any; describe('Settings middleware', () => { - beforeEach(() => { - ObjectManagers.reset(); + const tempDir = path.join(__dirname, '../../../tmp'); + beforeEach(async () => { + await ObjectManagers.reset(); + await fs.promises.rm(tempDir, {recursive: true, force: true}); }); it('should save empty enforced users settings', (done: (err?: any) => void) => { From 6d1b5e21fb92105a2b57e403826cf387ae5a0589 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Mon, 31 Jul 2023 01:10:00 +0200 Subject: [PATCH 20/22] Add localize shim for MessagingConfig --- src/common/config/private/MessagingConfig.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/common/config/private/MessagingConfig.ts b/src/common/config/private/MessagingConfig.ts index bf23c68d..219c5eea 100644 --- a/src/common/config/private/MessagingConfig.ts +++ b/src/common/config/private/MessagingConfig.ts @@ -3,7 +3,13 @@ import {SubConfigClass} from '../../../../node_modules/typeconfig/src/decorators import {ConfigPriority, TAGS} from '../public/ClientConfig'; import {ConfigProperty} from '../../../../node_modules/typeconfig/src/decorators/property/ConfigPropoerty'; import {ServerConfig} from './PrivateConfig'; +declare let $localize: (s: TemplateStringsArray) => string; +if (typeof $localize === 'undefined') { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + global.$localize = (s) => s; +} export enum EmailMessagingType { sendmail = 1, SMTP = 2, From 80853771f4c647725d7823965aff625ad876cb42 Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Mon, 31 Jul 2023 01:29:04 +0200 Subject: [PATCH 21/22] Removing debug logging --- src/backend/middlewares/admin/SettingsMWs.ts | 16 ---------------- src/common/config/private/Config.ts | 3 --- src/common/config/private/PrivateConfig.ts | 2 -- .../unit/middlewares/admin/SettingsMWs.ts | 3 --- 4 files changed, 24 deletions(-) diff --git a/src/backend/middlewares/admin/SettingsMWs.ts b/src/backend/middlewares/admin/SettingsMWs.ts index 651de094..a099f022 100644 --- a/src/backend/middlewares/admin/SettingsMWs.ts +++ b/src/backend/middlewares/admin/SettingsMWs.ts @@ -28,8 +28,6 @@ export class SettingsMWs { let settings = req.body.settings; // Top level settings JSON const settingsPath: string = req.body.settingsPath; // Name of the top level settings const transformer = await Config.original(); - console.log('pre settings' + settingsPath); - console.log(settings); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore transformer[settingsPath] = settings; @@ -38,29 +36,15 @@ export class SettingsMWs { settings = ConfigClassBuilder.attachPrivateInterface(transformer[settingsPath]).toJSON({ skipTags: {secret: true} as TAGS }); - console.log('post settings'+ settingsPath); - console.log(settings); const original = await Config.original(); - console.log('orig pre'); - console.log(ConfigClassBuilder.attachPrivateInterface(original.Messaging).toJSON()); - console.log(original.Messaging.Email); - console.log(original.Messaging.Email.type); // only updating explicitly set config (not saving config set by the diagnostics) // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore original[settingsPath] = settings; - console.log('orig'); - console.log(ConfigClassBuilder.attachPrivateInterface(original.Messaging).toJSON()); - console.log(original.Messaging.Email); - console.log(original.Messaging.Email.type); await ConfigDiagnostics.testConfig(original); // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore Config[settingsPath] = settings; - console.log('Config'); - console.log(Config.Messaging); - console.log(Config.Messaging.Email); - console.log(Config.Messaging.Email.type); await original.save(); await ConfigDiagnostics.runDiagnostics(); Logger.info(LOG_TAG, 'new config:'); diff --git a/src/common/config/private/Config.ts b/src/common/config/private/Config.ts index 97c22eed..4a5a02ea 100644 --- a/src/common/config/private/Config.ts +++ b/src/common/config/private/Config.ts @@ -82,15 +82,12 @@ export class PrivateConfigClass extends ServerConfig { require('../../../../package.json').buildCommitHash; this.Environment.upTime = upTime; this.Environment.isDocker = !!process.env.PI_DOCKER; - console.log('CONFIG', ServerEnvironment); if (typeof ServerEnvironment.sendMailAvailable !== 'undefined') { this.Environment.sendMailAvailable = ServerEnvironment.sendMailAvailable; if (!this.Environment.sendMailAvailable) { //onNewValue is not yet available as a callback this.Messaging.Email.type = EmailMessagingType.SMTP; } } - console.log('CONFIG', this.Environment.sendMailAvailable); - console.log('CONFIG', this.Messaging.Email.type); } async original(): Promise { diff --git a/src/common/config/private/PrivateConfig.ts b/src/common/config/private/PrivateConfig.ts index ffd81878..e9b60a19 100644 --- a/src/common/config/private/PrivateConfig.ts +++ b/src/common/config/private/PrivateConfig.ts @@ -1054,11 +1054,9 @@ export class ServerEnvironmentConfig { @ConfigProperty({ volatile: true, onNewValue: (value, config) => { - console.log('onNewValue', value, config.Messaging.Email.type); if (value === false) { config.Messaging.Email.type = EmailMessagingType.SMTP; } - console.log('onNewValue after', value, config.Messaging.Email.type); }, description: 'App updates on start-up if sendmail binary is available' }) diff --git a/test/backend/unit/middlewares/admin/SettingsMWs.ts b/test/backend/unit/middlewares/admin/SettingsMWs.ts index 1874987a..db0b689a 100644 --- a/test/backend/unit/middlewares/admin/SettingsMWs.ts +++ b/test/backend/unit/middlewares/admin/SettingsMWs.ts @@ -26,7 +26,6 @@ describe('Settings middleware', () => { }); it('should save empty enforced users settings', (done: (err?: any) => void) => { - console.log('sarting - should save empty enforced users settings'); ServerEnvironment.sendMailAvailable = false; Config.Environment.sendMailAvailable = false; Config.Messaging.Email.type = EmailMessagingType.SMTP; @@ -40,7 +39,6 @@ describe('Settings middleware', () => { settings: ConfigClassBuilder.attachPrivateInterface(new ServerUserConfig()).toJSON() } }; - console.log('Settings', req); req.body.settings.enforcedUsers = []; const next: any = (err: ErrorDTO) => { try { @@ -57,7 +55,6 @@ describe('Settings middleware', () => { }); it('should save enforced users settings', (done: (err?: any) => void) => { - console.log('sarting - should save enforced users settings'); ServerEnvironment.sendMailAvailable = false; Config.Environment.sendMailAvailable = false; From 819ca1b80d432a6f7c7745057c5ec98e6f1f023e Mon Sep 17 00:00:00 2001 From: "Patrik J. Braun" Date: Tue, 1 Aug 2023 00:05:19 +0200 Subject: [PATCH 22/22] Implementing embended image sending with email media messenger ##83 --- .../mediamessengers/EmailMediaMessenger.ts | 55 ++++++++++++++++++- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/src/backend/model/mediamessengers/EmailMediaMessenger.ts b/src/backend/model/mediamessengers/EmailMediaMessenger.ts index 7aae180b..a951750e 100644 --- a/src/backend/model/mediamessengers/EmailMediaMessenger.ts +++ b/src/backend/model/mediamessengers/EmailMediaMessenger.ts @@ -1,7 +1,13 @@ import {createTransport, Transporter} from 'nodemailer'; -import {MediaDTO} from '../../../common/entities/MediaDTO'; +import {MediaDTO, MediaDTOUtils} from '../../../common/entities/MediaDTO'; import {Config} from '../../../common/config/private/Config'; import {EmailMessagingType} from '../../../common/config/private/MessagingConfig'; +import {PhotoProcessing} from '../fileprocessing/PhotoProcessing'; +import {ThumbnailSourceType} from '../threading/PhotoWorker'; +import {ProjectPath} from '../../ProjectPath'; +import * as path from 'path'; +import {PhotoMetadata} from '../../../common/entities/PhotoDTO'; +import {Utils} from '../../../common/Utils'; export class EmailMediaMessenger { transporter: Transporter; @@ -26,6 +32,15 @@ export class EmailMediaMessenger { } + private async getThumbnail(m: MediaDTO) { + return await PhotoProcessing.generateThumbnail( + path.join(ProjectPath.ImageFolder, m.directory.path, m.directory.name, m.name), + Config.Media.Thumbnail.thumbnailSizes[0], + MediaDTOUtils.isPhoto(m) ? ThumbnailSourceType.Photo : ThumbnailSourceType.Video, + false + ); + } + public async sendMedia(mailSettings: { from: string, to: string, @@ -33,11 +48,47 @@ export class EmailMediaMessenger { text: string }, media: MediaDTO[]) { + const attachments = []; + const htmlStart = '

' + Config.Server.applicationTitle + '

\n' + + '

' + mailSettings.text + '

\n' + + '\n' + + ' \n'; + const htmlEnd = ' \n' + + ' \n' + + '
'; + let htmlMiddle = ''; + for (let i = 0; i < media.length; ++i) { + const thPath = await this.getThumbnail(media[i]); + const linkUrl = Utils.concatUrls(Config.Server.publicUrl, '/gallery/', path.join(media[i].directory.path, media[i].directory.name)); + const location = (media[0].metadata as PhotoMetadata).positionData?.country ? + (media[0].metadata as PhotoMetadata).positionData?.country : + ((media[0].metadata as PhotoMetadata).positionData?.city ? + (media[0].metadata as PhotoMetadata).positionData?.city : ''); + const caption = (new Date(media[0].metadata.creationDate)).getFullYear() + (location ? ', ' + location : ''); + attachments.push({ + filename: media[i].name, + path: thPath, + cid: 'img' + i + }); + if (i % 2 == 0) { + htmlMiddle += ''; + } + htmlMiddle += '\n' + + ' ' + media[i].name + '\n' + + caption + + ' \n'; + + if (i % 2 == 1 || i === media.length - 1) { + htmlMiddle += ''; + } + } + return await this.transporter.sendMail({ from: mailSettings.from, to: mailSettings.to, subject: mailSettings.subject, - text: mailSettings.text + media.map(m => m.name).join(', ') + html: htmlStart + htmlMiddle + htmlEnd, + attachments: attachments }); } }