mirror of
https://github.com/bpatrik/pigallery2.git
synced 2024-12-23 01:27:14 +02:00
Refactoring messenger to prepare extension support #753
This commit is contained in:
parent
7208a3b4fe
commit
50b8f7a81d
@ -2,18 +2,19 @@ import {NextFunction, Request, Response} from 'express';
|
||||
import {ErrorCodes, ErrorDTO} from '../../../common/entities/Error';
|
||||
import {ObjectManagers} from '../../model/ObjectManagers';
|
||||
import {StatisticDTO} from '../../../common/entities/settings/StatisticDTO';
|
||||
import {MessengerRepository} from '../../model/messenger/MessengerRepository';
|
||||
|
||||
export class AdminMWs {
|
||||
public static async loadStatistic(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
|
||||
const galleryManager = ObjectManagers.getInstance()
|
||||
.GalleryManager;
|
||||
.GalleryManager;
|
||||
const personManager = ObjectManagers.getInstance()
|
||||
.PersonManager;
|
||||
.PersonManager;
|
||||
try {
|
||||
req.resultPipe = {
|
||||
directories: await galleryManager.countDirectories(),
|
||||
@ -26,87 +27,87 @@ export class AdminMWs {
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error while getting statistic: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error while getting statistic: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error while getting statistic',
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error while getting statistic',
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static async getDuplicates(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
|
||||
try {
|
||||
req.resultPipe = await ObjectManagers.getInstance()
|
||||
.GalleryManager.getPossibleDuplicates();
|
||||
.GalleryManager.getPossibleDuplicates();
|
||||
return next();
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error while getting duplicates: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error while getting duplicates: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error while getting duplicates',
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.GENERAL_ERROR,
|
||||
'Error while getting duplicates',
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static async startJob(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): Promise<void> {
|
||||
try {
|
||||
const id = req.params['id'];
|
||||
const JobConfig: unknown = req.body.config;
|
||||
const JobConfig: Record<string, unknown> = req.body.config;
|
||||
const soloRun: boolean = req.body.soloRun;
|
||||
const allowParallelRun: boolean = req.body.allowParallelRun;
|
||||
await ObjectManagers.getInstance().JobManager.run(
|
||||
id,
|
||||
JobConfig,
|
||||
soloRun,
|
||||
allowParallelRun
|
||||
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
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + JSON.stringify(err, null, ' '),
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + JSON.stringify(err, null, ' '),
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -120,56 +121,85 @@ export class AdminMWs {
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + JSON.stringify(err, null, ' '),
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static getAvailableMessengers(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
try {
|
||||
req.resultPipe = MessengerRepository.Instance.getAll().map(msgr => msgr.Name);
|
||||
return next();
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + JSON.stringify(err, null, ' '),
|
||||
err
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Messenger error: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Messenger error: ' + JSON.stringify(err, null, ' '),
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static getAvailableJobs(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
try {
|
||||
req.resultPipe =
|
||||
ObjectManagers.getInstance().JobManager.getAvailableJobs();
|
||||
ObjectManagers.getInstance().JobManager.getAvailableJobs();
|
||||
return next();
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + JSON.stringify(err, null, ' '),
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + JSON.stringify(err, null, ' '),
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static getJobProgresses(
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
req: Request,
|
||||
res: Response,
|
||||
next: NextFunction
|
||||
): void {
|
||||
try {
|
||||
req.resultPipe = ObjectManagers.getInstance().JobManager.getProgresses();
|
||||
@ -177,19 +207,19 @@ export class AdminMWs {
|
||||
} catch (err) {
|
||||
if (err instanceof Error) {
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + err.toString(),
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
return next(
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + JSON.stringify(err, null, ' '),
|
||||
err
|
||||
)
|
||||
new ErrorDTO(
|
||||
ErrorCodes.JOB_ERROR,
|
||||
'Job error: ' + JSON.stringify(err, null, ' '),
|
||||
err
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ export class JobManager implements IJobListener, IObjectManager {
|
||||
return prg;
|
||||
}
|
||||
|
||||
public async run<T>(
|
||||
public async run<T extends Record<string, unknown>>(
|
||||
jobName: string,
|
||||
config: T,
|
||||
soloRun: boolean,
|
||||
@ -86,7 +86,7 @@ export class JobManager implements IJobListener, IObjectManager {
|
||||
};
|
||||
|
||||
onJobFinished = async (
|
||||
job: IJob<unknown>,
|
||||
job: IJob,
|
||||
state: JobProgressStates,
|
||||
soloRun: boolean
|
||||
): Promise<void> => {
|
||||
@ -121,7 +121,7 @@ export class JobManager implements IJobListener, IObjectManager {
|
||||
}
|
||||
};
|
||||
|
||||
getAvailableJobs(): IJob<unknown>[] {
|
||||
getAvailableJobs(): IJob[] {
|
||||
return JobRepository.Instance.getAvailableJobs();
|
||||
}
|
||||
|
||||
@ -144,7 +144,7 @@ export class JobManager implements IJobListener, IObjectManager {
|
||||
Config.Jobs.scheduled.forEach((s): void => this.runSchedule(s));
|
||||
}
|
||||
|
||||
protected findJob<T = unknown>(jobName: string): IJob<T> {
|
||||
protected findJob(jobName: string): IJob {
|
||||
return this.getAvailableJobs().find((t): boolean => t.Name === jobName);
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ import {AlbumCoverRestJob} from './jobs/AlbumCoverResetJob';
|
||||
|
||||
export class JobRepository {
|
||||
private static instance: JobRepository = null;
|
||||
availableJobs: { [key: string]: IJob<unknown> } = {};
|
||||
availableJobs: { [key: string]: IJob } = {};
|
||||
|
||||
public static get Instance(): JobRepository {
|
||||
if (JobRepository.instance == null) {
|
||||
@ -23,11 +23,11 @@ export class JobRepository {
|
||||
return JobRepository.instance;
|
||||
}
|
||||
|
||||
getAvailableJobs(): IJob<unknown>[] {
|
||||
getAvailableJobs(): IJob[] {
|
||||
return Object.values(this.availableJobs).filter((t) => t.Supported);
|
||||
}
|
||||
|
||||
register(job: IJob<unknown>): void {
|
||||
register(job: IJob): void {
|
||||
if (typeof this.availableJobs[job.Name] !== 'undefined') {
|
||||
throw new Error('Job already exist:' + job.Name);
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import {JobDTO} from '../../../../common/entities/job/JobDTO';
|
||||
import {JobProgress} from './JobProgress';
|
||||
import {IJobListener} from './IJobListener';
|
||||
|
||||
export interface IJob<T> extends JobDTO {
|
||||
export interface IJob<T extends Record<string, unknown> = Record<string, unknown>> extends JobDTO {
|
||||
Name: string;
|
||||
Supported: boolean;
|
||||
Progress: JobProgress;
|
||||
|
@ -4,7 +4,7 @@ import {JobProgressStates} from '../../../../common/entities/job/JobProgressDTO'
|
||||
|
||||
export interface IJobListener {
|
||||
onJobFinished(
|
||||
job: IJob<unknown>,
|
||||
job: IJob,
|
||||
state: JobProgressStates,
|
||||
soloRun: boolean
|
||||
): void;
|
||||
|
@ -35,7 +35,7 @@ export class ThumbnailGenerationJob extends FileJob<{
|
||||
): Promise<void> {
|
||||
if (!config || !config.sizes || !Array.isArray(config.sizes) || config.sizes.length === 0) {
|
||||
config = config || {};
|
||||
config.sizes = this.ConfigTemplate.find(ct => ct.id == 'sizes').defaultValue;
|
||||
config.sizes = this.ConfigTemplate.find(ct => ct.id == 'sizes').defaultValue as number[];
|
||||
}
|
||||
for (const item of config.sizes) {
|
||||
if (Config.Media.Thumbnail.thumbnailSizes.indexOf(item) === -1) {
|
||||
|
@ -1,64 +1,67 @@
|
||||
import {ConfigTemplateEntry, DefaultsJobs,} from '../../../../common/entities/job/JobDTO';
|
||||
import {DefaultMessengers, DefaultsJobs,} from '../../../../common/entities/job/JobDTO';
|
||||
import {Job} from './Job';
|
||||
import {backendTexts} from '../../../../common/BackendTexts';
|
||||
import {SortByTypes} from '../../../../common/entities/SortingMethods';
|
||||
import {DatePatternFrequency, DatePatternSearch, SearchQueryTypes} from '../../../../common/entities/SearchQueryDTO';
|
||||
import {ObjectManagers} from '../../ObjectManagers';
|
||||
import {PhotoEntity} from '../../database/enitites/PhotoEntity';
|
||||
import {EmailMediaMessenger} from '../../mediamessengers/EmailMediaMessenger';
|
||||
import {MediaPickDTO} from '../../../../common/entities/MediaPickDTO';
|
||||
import {MediaDTOUtils} from '../../../../common/entities/MediaDTO';
|
||||
import {DynamicConfig} from '../../../../common/entities/DynamicConfig';
|
||||
import {MessengerRepository} from '../../messenger/MessengerRepository';
|
||||
import {Utils} from '../../../../common/Utils';
|
||||
|
||||
|
||||
export class TopPickSendJob extends Job<{
|
||||
mediaPick: MediaPickDTO[],
|
||||
messenger: string,
|
||||
emailTo: string,
|
||||
emailFrom: string,
|
||||
emailSubject: string,
|
||||
emailText: string,
|
||||
}> {
|
||||
public readonly Name = DefaultsJobs[DefaultsJobs['Top Pick Sending']];
|
||||
public readonly Supported: boolean = true;
|
||||
public readonly ConfigTemplate: DynamicConfig[] = [
|
||||
{
|
||||
id: 'mediaPick',
|
||||
type: 'MediaPickDTO-array',
|
||||
name: backendTexts.mediaPick.name,
|
||||
description: backendTexts.mediaPick.description,
|
||||
defaultValue: [{
|
||||
searchQuery: {
|
||||
type: SearchQueryTypes.date_pattern,
|
||||
daysLength: 7,
|
||||
frequency: DatePatternFrequency.every_year
|
||||
} as DatePatternSearch,
|
||||
sortBy: [{method: SortByTypes.Rating, ascending: false},
|
||||
{method: SortByTypes.PersonCount, ascending: false}],
|
||||
pick: 5
|
||||
}] as MediaPickDTO[],
|
||||
}, {
|
||||
id: 'emailTo',
|
||||
type: 'string-array',
|
||||
name: backendTexts.emailTo.name,
|
||||
description: backendTexts.emailTo.description,
|
||||
defaultValue: [],
|
||||
}, {
|
||||
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:',
|
||||
},
|
||||
];
|
||||
public readonly ConfigTemplate: DynamicConfig[];
|
||||
private status: 'Listing' | 'Sending' = 'Listing';
|
||||
private mediaList: PhotoEntity[] = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.ConfigTemplate = [
|
||||
{
|
||||
id: 'mediaPick',
|
||||
type: 'MediaPickDTO-array',
|
||||
name: backendTexts.mediaPick.name,
|
||||
description: backendTexts.mediaPick.description,
|
||||
defaultValue: [{
|
||||
searchQuery: {
|
||||
type: SearchQueryTypes.date_pattern,
|
||||
daysLength: 7,
|
||||
frequency: DatePatternFrequency.every_year
|
||||
} as DatePatternSearch,
|
||||
sortBy: [{method: SortByTypes.Rating, ascending: false},
|
||||
{method: SortByTypes.PersonCount, ascending: false}],
|
||||
pick: 5
|
||||
}] as MediaPickDTO[],
|
||||
}, {
|
||||
id: 'messenger',
|
||||
type: 'messenger',
|
||||
name: backendTexts.messenger.name,
|
||||
description: backendTexts.messenger.description,
|
||||
defaultValue: DefaultMessengers[DefaultMessengers.Email]
|
||||
}
|
||||
];
|
||||
|
||||
// add all messenger's config to the config template
|
||||
MessengerRepository.Instance.getAll()
|
||||
.forEach(msgr => Utils.clone(msgr.ConfigTemplate)
|
||||
.forEach(ct => {
|
||||
const c = Utils.clone(ct);
|
||||
c.validIf = {configFiled: 'messenger', equalsValue: msgr.Name};
|
||||
this.ConfigTemplate.push(c);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
protected async init(): Promise<void> {
|
||||
this.status = 'Listing';
|
||||
@ -86,15 +89,15 @@ export class TopPickSendJob extends Job<{
|
||||
this.mediaList = [];
|
||||
for (let i = 0; i < this.config.mediaPick.length; ++i) {
|
||||
const media = await ObjectManagers.getInstance().SearchManager
|
||||
.getNMedia(this.config.mediaPick[i].searchQuery, this.config.mediaPick[i].sortBy, this.config.mediaPick[i].pick);
|
||||
.getNMedia(this.config.mediaPick[i].searchQuery, this.config.mediaPick[i].sortBy, this.config.mediaPick[i].pick);
|
||||
this.Progress.log('Find ' + media.length + ' photos and videos from ' + (i + 1) + '. load');
|
||||
this.mediaList = this.mediaList.concat(media);
|
||||
}
|
||||
|
||||
// make the list unique
|
||||
this.mediaList = this.mediaList
|
||||
.filter((value, index, arr) =>
|
||||
arr.findIndex(m => MediaDTOUtils.equals(m, value)) === index);
|
||||
.filter((value, index, arr) =>
|
||||
arr.findIndex(m => MediaDTOUtils.equals(m, value)) === index);
|
||||
|
||||
this.Progress.Processed++;
|
||||
// console.log(this.mediaList);
|
||||
@ -103,17 +106,16 @@ export class TopPickSendJob extends Job<{
|
||||
|
||||
private async stepSending(): Promise<boolean> {
|
||||
if (this.mediaList.length <= 0) {
|
||||
this.Progress.log('No photos found skipping e-mail sending.');
|
||||
this.Progress.log('No photos found skipping sending.');
|
||||
this.Progress.Skipped++;
|
||||
return false;
|
||||
}
|
||||
this.Progress.log('Sending emails of ' + this.mediaList.length + ' photos.');
|
||||
const messenger = new EmailMediaMessenger();
|
||||
await messenger.sendMedia({
|
||||
to: this.config.emailTo,
|
||||
subject: this.config.emailSubject,
|
||||
text: this.config.emailText
|
||||
}, this.mediaList);
|
||||
const msgr = MessengerRepository.Instance.get(this.config.messenger);
|
||||
if (!msgr) {
|
||||
throw new Error('Can\t find "' + this.config.messenger + '" messenger.');
|
||||
}
|
||||
this.Progress.log('Sending ' + this.mediaList.length + ' photos.');
|
||||
await msgr.send(this.config, this.mediaList);
|
||||
this.Progress.Processed++;
|
||||
return false;
|
||||
}
|
||||
|
@ -1,18 +1,40 @@
|
||||
import {createTransport, Transporter} from 'nodemailer';
|
||||
import {MediaDTO, MediaDTOUtils} from '../../../common/entities/MediaDTO';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
import {PhotoProcessing} from '../fileaccess/fileprocessing/PhotoProcessing';
|
||||
import {ThumbnailSourceType} from '../fileaccess/PhotoWorker';
|
||||
import {ProjectPath} from '../../ProjectPath';
|
||||
import * as path from 'path';
|
||||
import {PhotoMetadata} from '../../../common/entities/PhotoDTO';
|
||||
import {Utils} from '../../../common/Utils';
|
||||
import {QueryParams} from '../../../common/QueryParams';
|
||||
import {MediaDTOWithThPath, Messenger} from './Messenger';
|
||||
import {backendTexts} from '../../../common/BackendTexts';
|
||||
import {DynamicConfig} from '../../../common/entities/DynamicConfig';
|
||||
import {DefaultMessengers} from '../../../common/entities/job/JobDTO';
|
||||
|
||||
export class EmailMediaMessenger {
|
||||
export class EmailMessenger extends Messenger<{
|
||||
emailTo: string,
|
||||
emailSubject: string,
|
||||
emailText: string,
|
||||
}> {
|
||||
public readonly Name = DefaultMessengers[DefaultMessengers.Email];
|
||||
public readonly ConfigTemplate: DynamicConfig[] = [{
|
||||
id: 'emailTo',
|
||||
type: 'string-array',
|
||||
name: backendTexts.emailTo.name,
|
||||
description: backendTexts.emailTo.description,
|
||||
defaultValue: [],
|
||||
}, {
|
||||
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:',
|
||||
}];
|
||||
transporter: Transporter;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.transporter = createTransport({
|
||||
host: Config.Messaging.Email.smtp.host,
|
||||
port: Config.Messaging.Email.smtp.port,
|
||||
@ -25,24 +47,16 @@ 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: {
|
||||
to: string,
|
||||
subject: string,
|
||||
text: string
|
||||
}, media: MediaDTO[]) {
|
||||
protected async sendMedia(mailSettings: {
|
||||
emailTo: string,
|
||||
emailSubject: string,
|
||||
emailText: string
|
||||
}, media: MediaDTOWithThPath[]) {
|
||||
|
||||
const attachments = [];
|
||||
const htmlStart = '<h1 style="text-align: center; margin-bottom: 2em">' + Config.Server.applicationTitle + '</h1>\n' +
|
||||
'<h3>' + mailSettings.text + '</h3>\n' +
|
||||
'<h3>' + mailSettings.emailText + '</h3>\n' +
|
||||
'<table style="margin-left: auto; margin-right: auto;">\n' +
|
||||
' <tbody>\n';
|
||||
const htmlEnd = ' </tr>\n' +
|
||||
@ -51,9 +65,6 @@ export class EmailMediaMessenger {
|
||||
let htmlMiddle = '';
|
||||
const numberOfColumns = media.length >= 6 ? 3 : 2;
|
||||
for (let i = 0; i < media.length; ++i) {
|
||||
const thPath = await this.getThumbnail(media[i]);
|
||||
const linkUrl = Utils.concatUrls(Config.Server.publicUrl, '/gallery/', encodeURIComponent(path.join(media[i].directory.path, media[i].directory.name))) +
|
||||
'?' + QueryParams.gallery.photo + '=' + encodeURIComponent(media[i].name);
|
||||
const location = (media[i].metadata as PhotoMetadata).positionData?.country ?
|
||||
(media[i].metadata as PhotoMetadata).positionData?.country :
|
||||
((media[i].metadata as PhotoMetadata).positionData?.city ?
|
||||
@ -61,14 +72,14 @@ export class EmailMediaMessenger {
|
||||
const caption = (new Date(media[i].metadata.creationDate)).getFullYear() + (location ? ', ' + location : '');
|
||||
attachments.push({
|
||||
filename: media[i].name,
|
||||
path: thPath,
|
||||
path: media[i].thumbnailPath,
|
||||
cid: 'img' + i
|
||||
});
|
||||
if (i % numberOfColumns == 0) {
|
||||
htmlMiddle += '<tr>';
|
||||
}
|
||||
htmlMiddle += '<td>\n' +
|
||||
' <a style="display: block;text-align: center;" href="' + linkUrl + '"><img alt="' + media[i].name + '" style="max-width: 200px; max-height: 150px; height:auto; width:auto;" src="cid:img' + i + '"/></a>\n' +
|
||||
' <a style="display: block;text-align: center;" href="' + media[i].thumbnailUrl + '"><img alt="' + media[i].name + '" style="max-width: 200px; max-height: 150px; height:auto; width:auto;" src="cid:img' + i + '"/></a>\n' +
|
||||
caption +
|
||||
' </td>\n';
|
||||
|
||||
@ -79,8 +90,8 @@ export class EmailMediaMessenger {
|
||||
|
||||
return await this.transporter.sendMail({
|
||||
from: Config.Messaging.Email.emailFrom,
|
||||
to: mailSettings.to,
|
||||
subject: mailSettings.subject,
|
||||
to: mailSettings.emailTo,
|
||||
subject: mailSettings.emailSubject,
|
||||
html: htmlStart + htmlMiddle + htmlEnd,
|
||||
attachments: attachments
|
||||
});
|
50
src/backend/model/messenger/Messenger.ts
Normal file
50
src/backend/model/messenger/Messenger.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import {MediaDTO, MediaDTOUtils} from '../../../common/entities/MediaDTO';
|
||||
import {PhotoProcessing} from '../fileaccess/fileprocessing/PhotoProcessing';
|
||||
import {ProjectPath} from '../../ProjectPath';
|
||||
import {Config} from '../../../common/config/private/Config';
|
||||
import {ThumbnailSourceType} from '../fileaccess/PhotoWorker';
|
||||
import * as path from 'path';
|
||||
import {Utils} from '../../../common/Utils';
|
||||
import {QueryParams} from '../../../common/QueryParams';
|
||||
import {DynamicConfig} from '../../../common/entities/DynamicConfig';
|
||||
|
||||
export interface MediaDTOWithThPath extends MediaDTO {
|
||||
thumbnailPath: string;
|
||||
thumbnailUrl: string;
|
||||
}
|
||||
|
||||
export abstract class Messenger<C extends Record<string, unknown> = Record<string, unknown>> {
|
||||
|
||||
public abstract get Name(): string;
|
||||
protected config: C;
|
||||
public readonly ConfigTemplate: DynamicConfig[] = [];
|
||||
|
||||
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 send(config: C, input: string | MediaDTO[] | unknown) {
|
||||
if (Array.isArray(input) && input.length > 0
|
||||
&& (input as MediaDTO[])[0]?.name
|
||||
&& (input as MediaDTO[])[0]?.directory
|
||||
&& (input as MediaDTO[])[0]?.metadata?.creationDate) {
|
||||
const media = input as MediaDTOWithThPath[];
|
||||
for (let i = 0; i < media.length; ++i) {
|
||||
media[i].thumbnailPath = await this.getThumbnail(media[i]);
|
||||
media[i].thumbnailUrl = Utils.concatUrls(Config.Server.publicUrl, '/gallery/', encodeURIComponent(path.join(media[i].directory.path, media[i].directory.name))) +
|
||||
'?' + QueryParams.gallery.photo + '=' + encodeURIComponent(media[i].name);
|
||||
}
|
||||
return await this.sendMedia(config, media);
|
||||
}
|
||||
// TODO: implement other branches
|
||||
throw new Error('Not yet implemented');
|
||||
}
|
||||
|
||||
protected abstract sendMedia(config: C, media: MediaDTOWithThPath[]): Promise<void> ;
|
||||
}
|
34
src/backend/model/messenger/MessengerRepository.ts
Normal file
34
src/backend/model/messenger/MessengerRepository.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import {Messenger} from './Messenger';
|
||||
import {EmailMessenger} from './EmailMessenger';
|
||||
import {StdoutMessenger} from './StdoutMessenger';
|
||||
|
||||
export class MessengerRepository {
|
||||
|
||||
private static instance: MessengerRepository = null;
|
||||
messengers: { [key: string]: Messenger } = {};
|
||||
|
||||
public static get Instance(): MessengerRepository {
|
||||
if (MessengerRepository.instance == null) {
|
||||
MessengerRepository.instance = new MessengerRepository();
|
||||
}
|
||||
return MessengerRepository.instance;
|
||||
}
|
||||
|
||||
getAll(): Messenger[] {
|
||||
return Object.values(this.messengers);
|
||||
}
|
||||
|
||||
register(msgr: Messenger): void {
|
||||
if (typeof this.messengers[msgr.Name] !== 'undefined') {
|
||||
throw new Error('Messenger already exist:' + msgr.Name);
|
||||
}
|
||||
this.messengers[msgr.Name] = msgr;
|
||||
}
|
||||
|
||||
get(name: string): Messenger {
|
||||
return this.messengers[name];
|
||||
}
|
||||
}
|
||||
|
||||
MessengerRepository.Instance.register(new EmailMessenger());
|
||||
MessengerRepository.Instance.register(new StdoutMessenger());
|
17
src/backend/model/messenger/StdoutMessenger.ts
Normal file
17
src/backend/model/messenger/StdoutMessenger.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import {MediaDTOWithThPath, Messenger} from './Messenger';
|
||||
import {DynamicConfig} from '../../../common/entities/DynamicConfig';
|
||||
import {DefaultMessengers} from '../../../common/entities/job/JobDTO';
|
||||
|
||||
export class StdoutMessenger extends Messenger {
|
||||
public readonly Name = DefaultMessengers[DefaultMessengers.Stdout];
|
||||
public readonly ConfigTemplate: DynamicConfig[] = [];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
protected async sendMedia(config: never, media: MediaDTOWithThPath[]) {
|
||||
console.log(media.map(m => m.thumbnailPath));
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ export class AdminRouter {
|
||||
this.addGetStatistic(app);
|
||||
this.addGetDuplicates(app);
|
||||
this.addJobs(app);
|
||||
this.addMessengers(app);
|
||||
}
|
||||
|
||||
private static addGetStatistic(app: Express): void {
|
||||
@ -32,6 +33,15 @@ export class AdminRouter {
|
||||
);
|
||||
}
|
||||
|
||||
private static addMessengers(app: Express): void {
|
||||
app.get(
|
||||
Config.Server.apiPath + '/admin/messengers/available',
|
||||
AuthenticationMWs.authenticate,
|
||||
AuthenticationMWs.authorise(UserRoles.Admin),
|
||||
AdminMWs.getAvailableMessengers,
|
||||
RenderingMWs.renderResult
|
||||
);
|
||||
}
|
||||
private static addJobs(app: Express): void {
|
||||
app.get(
|
||||
Config.Server.apiPath + '/admin/jobs/available',
|
||||
|
@ -1,4 +1,5 @@
|
||||
export type backendText = number;
|
||||
// keep the numbering sparse to support later addition
|
||||
export const backendTexts = {
|
||||
indexedFilesOnly: {name: 10, description: 12},
|
||||
sizeToGenerate: {name: 20, description: 22},
|
||||
@ -6,6 +7,7 @@ export const backendTexts = {
|
||||
mediaPick: {name: 40, description: 42},
|
||||
emailTo: {name: 70, description: 72},
|
||||
emailSubject: {name: 90, description: 92},
|
||||
emailText: {name: 100, description: 102}
|
||||
emailText: {name: 100, description: 102},
|
||||
messenger: {name: 110,description: 112}
|
||||
|
||||
};
|
||||
|
@ -1,5 +1,8 @@
|
||||
import {backendText} from '../BackendTexts';
|
||||
import {fieldType} from './job/JobDTO';
|
||||
|
||||
|
||||
export type fieldType = 'string' | 'string-array' | 'number' | 'boolean' | 'number-array' | 'MediaPickDTO-array' | 'messenger';
|
||||
|
||||
|
||||
/**
|
||||
* Dynamic configs are not part of the typeconfig maintained config.
|
||||
@ -14,4 +17,5 @@ export interface DynamicConfig {
|
||||
description: backendText | string;
|
||||
type: fieldType;
|
||||
defaultValue: unknown;
|
||||
validIf?: { configFiled: string, equalsValue: string }; // only shows this config if this predicate is true
|
||||
}
|
||||
|
@ -1,8 +1,5 @@
|
||||
import {backendText} from '../../BackendTexts';
|
||||
import {DynamicConfig} from '../DynamicConfig';
|
||||
|
||||
export type fieldType = 'string' | 'string-array' | 'number' | 'boolean' | 'number-array' | 'MediaPickDTO-array';
|
||||
|
||||
export enum DefaultsJobs {
|
||||
Indexing = 1,
|
||||
'Gallery Reset' = 2,
|
||||
@ -19,6 +16,12 @@ export enum DefaultsJobs {
|
||||
}
|
||||
|
||||
|
||||
export enum DefaultMessengers {
|
||||
Email = 1,
|
||||
Stdout = 2
|
||||
}
|
||||
|
||||
|
||||
export interface JobDTO {
|
||||
Name: string;
|
||||
ConfigTemplate: DynamicConfig[];
|
||||
|
@ -5,7 +5,10 @@ import {DefaultsJobs} from '../../../common/entities/job/JobDTO';
|
||||
@Injectable()
|
||||
export class BackendtextService {
|
||||
|
||||
public get(id: backendText): string {
|
||||
public get(id: backendText | string): string {
|
||||
if (typeof id === 'string') {
|
||||
return id;
|
||||
}
|
||||
switch (id) {
|
||||
case backendTexts.sizeToGenerate.name:
|
||||
return $localize`Size to generate`;
|
||||
|
@ -3,9 +3,10 @@ import {BehaviorSubject} from 'rxjs';
|
||||
import {JobProgressDTO, JobProgressStates, OnTimerJobProgressDTO,} from '../../../../common/entities/job/JobProgressDTO';
|
||||
import {NetworkService} from '../../model/network/network.service';
|
||||
import {JobScheduleDTO} from '../../../../common/entities/job/JobScheduleDTO';
|
||||
import {ConfigTemplateEntry, JobDTO, JobDTOUtils} from '../../../../common/entities/job/JobDTO';
|
||||
import {JobDTO, JobDTOUtils} from '../../../../common/entities/job/JobDTO';
|
||||
import {BackendtextService} from '../../model/backendtext.service';
|
||||
import {NotificationService} from '../../model/notification.service';
|
||||
import {DynamicConfig} from '../../../../common/entities/DynamicConfig';
|
||||
|
||||
@Injectable()
|
||||
export class ScheduledJobsService {
|
||||
@ -13,6 +14,7 @@ export class ScheduledJobsService {
|
||||
public onJobFinish: EventEmitter<string> = new EventEmitter<string>();
|
||||
timer: number = null;
|
||||
public availableJobs: BehaviorSubject<JobDTO[]>;
|
||||
public availableMessengers: BehaviorSubject<string[]>;
|
||||
public jobStartingStopping: { [key: string]: boolean } = {};
|
||||
private subscribers = 0;
|
||||
|
||||
@ -23,6 +25,7 @@ export class ScheduledJobsService {
|
||||
) {
|
||||
this.progress = new BehaviorSubject({});
|
||||
this.availableJobs = new BehaviorSubject([]);
|
||||
this.availableMessengers = new BehaviorSubject([]);
|
||||
}
|
||||
|
||||
|
||||
@ -32,7 +35,13 @@ export class ScheduledJobsService {
|
||||
);
|
||||
}
|
||||
|
||||
public getConfigTemplate(JobName: string): ConfigTemplateEntry[] {
|
||||
public async getAvailableMessengers(): Promise<void> {
|
||||
this.availableMessengers.next(
|
||||
await this.networkService.getJson<string[]>('/admin/messengers/available')
|
||||
);
|
||||
}
|
||||
|
||||
public getConfigTemplate(JobName: string): DynamicConfig[] {
|
||||
const job = this.availableJobs.value.find(
|
||||
(t) => t.Name === JobName
|
||||
);
|
||||
|
@ -170,6 +170,7 @@
|
||||
<div *ngFor="let configEntry of jobsService.getConfigTemplate(schedule.jobName)">
|
||||
|
||||
<div class="mb-1 row"
|
||||
*ngIf="!configEntry.validIf || schedule.config[configEntry.validIf.configFiled] == configEntry.validIf.equalsValue"
|
||||
[class.mb-3]="settingsService.configStyle == ConfigStyle.full">
|
||||
<label class="col-md-2 control-label"
|
||||
[for]="configEntry.id+'_'+i">{{backendTextService.get(configEntry.name)}}</label>
|
||||
@ -227,6 +228,18 @@
|
||||
placeholder="Search Query">
|
||||
</app-gallery-search-field>
|
||||
|
||||
<select
|
||||
*ngSwitchCase="'messenger'"
|
||||
[id]="configEntry.id+'_'+i"
|
||||
[name]="configEntry.id+'_'+i"
|
||||
(ngModelChange)="onChange($event)"
|
||||
[(ngModel)]="schedule.config[configEntry.id]"
|
||||
class="form-select">
|
||||
<option *ngFor="let msg of jobsService.availableMessengers | async" [ngValue]="msg">{{msg}}
|
||||
</option>
|
||||
</select>
|
||||
|
||||
|
||||
<ng-container *ngSwitchCase="'MediaPickDTO-array'">
|
||||
<ng-container *ngFor="let mp of AsMediaPickDTOArray(schedule.config[configEntry.id]); let j=index">
|
||||
|
||||
|
@ -108,6 +108,7 @@ export class WorkflowComponent implements ControlValueAccessor, Validator, OnIni
|
||||
ngOnInit(): void {
|
||||
this.jobsService.subscribeToProgress();
|
||||
this.jobsService.getAvailableJobs().catch(console.error);
|
||||
this.jobsService.getAvailableMessengers().catch(console.error);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
@ -128,7 +129,7 @@ export class WorkflowComponent implements ControlValueAccessor, Validator, OnIni
|
||||
schedule.config = schedule.config || {};
|
||||
if (job.ConfigTemplate) {
|
||||
job.ConfigTemplate.forEach(
|
||||
(ct) => (schedule.config[ct.id] = ct.defaultValue)
|
||||
(ct) => (schedule.config[ct.id] = ct.defaultValue as never)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -216,7 +217,7 @@ export class WorkflowComponent implements ControlValueAccessor, Validator, OnIni
|
||||
this.newSchedule.config = this.newSchedule.config || {};
|
||||
if (job.ConfigTemplate) {
|
||||
job.ConfigTemplate.forEach(
|
||||
(ct) => (this.newSchedule.config[ct.id] = ct.defaultValue)
|
||||
(ct) => (this.newSchedule.config[ct.id] = ct.defaultValue as never)
|
||||
);
|
||||
}
|
||||
this.jobModalQL.first.show();
|
||||
|
Loading…
Reference in New Issue
Block a user