Merge branch 'dev'
This commit is contained in:
commit
62f3e04e26
18 changed files with 540 additions and 68 deletions
|
|
@ -7,5 +7,9 @@
|
||||||
"name": "",
|
"name": "",
|
||||||
"people": [""]
|
"people": [""]
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"dailyTime": {
|
||||||
|
"hour": 9,
|
||||||
|
"minute": 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3,8 +3,10 @@
|
||||||
"dbs": {
|
"dbs": {
|
||||||
"changes": "",
|
"changes": "",
|
||||||
"userMetaInfo": "",
|
"userMetaInfo": "",
|
||||||
|
"eccmDailyReports": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"telegramBotToken": "",
|
"telegramBotToken": "",
|
||||||
"personalMessageTemplate": ""
|
"personalMessageTemplate": "",
|
||||||
|
"periodValidityNotification": 43200 // 12h
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
RssListenerDefaultParams,
|
RssListenerDefaultParams,
|
||||||
RssListenerParams,
|
RssListenerParams,
|
||||||
|
|
@ -14,6 +14,8 @@ const parser = new Parser();
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RssListener implements EventsListener {
|
export class RssListener implements EventsListener {
|
||||||
|
private logger = new Logger(RssListener.name);
|
||||||
|
|
||||||
issues = new BehaviorSubject<number[]>([]);
|
issues = new BehaviorSubject<number[]>([]);
|
||||||
|
|
||||||
private updateTimeout;
|
private updateTimeout;
|
||||||
|
|
@ -56,7 +58,15 @@ export class RssListener implements EventsListener {
|
||||||
const url = subscription.url;
|
const url = subscription.url;
|
||||||
const regexp = new RegExp(subscription.issueNumberParser);
|
const regexp = new RegExp(subscription.issueNumberParser);
|
||||||
const subjectParser = CreateSubjectsParserByRegExp(regexp);
|
const subjectParser = CreateSubjectsParserByRegExp(regexp);
|
||||||
const feed = await parser.parseURL(url);
|
let feed;
|
||||||
|
try {
|
||||||
|
feed = await parser.parseURL(url);
|
||||||
|
} catch (ex) {
|
||||||
|
this.logger.error(
|
||||||
|
`Error at load data from rss by url ${url} with error message ${ex.message}`,
|
||||||
|
);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
const issueNumbers: number[] = feed.items
|
const issueNumbers: number[] = feed.items
|
||||||
.filter((item) => {
|
.filter((item) => {
|
||||||
const itemDate = new Date(item.pubDate);
|
const itemDate = new Date(item.pubDate);
|
||||||
|
|
|
||||||
103
package-lock.json
generated
103
package-lock.json
generated
|
|
@ -14,6 +14,7 @@
|
||||||
"@nestjs/core": "^8.0.0",
|
"@nestjs/core": "^8.0.0",
|
||||||
"@nestjs/platform-express": "^8.0.0",
|
"@nestjs/platform-express": "^8.0.0",
|
||||||
"@nestjs/platform-socket.io": "^8.4.4",
|
"@nestjs/platform-socket.io": "^8.4.4",
|
||||||
|
"@nestjs/schedule": "^2.1.0",
|
||||||
"@nestjs/serve-static": "^2.2.2",
|
"@nestjs/serve-static": "^2.2.2",
|
||||||
"@nestjs/websockets": "^8.4.4",
|
"@nestjs/websockets": "^8.4.4",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
|
|
@ -21,6 +22,7 @@
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"hbs": "^4.2.0",
|
"hbs": "^4.2.0",
|
||||||
"imap-simple": "^5.1.0",
|
"imap-simple": "^5.1.0",
|
||||||
|
"luxon": "^3.1.0",
|
||||||
"nano": "^10.0.0",
|
"nano": "^10.0.0",
|
||||||
"node-telegram-bot-api": "^0.59.0",
|
"node-telegram-bot-api": "^0.59.0",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
|
|
@ -34,8 +36,10 @@
|
||||||
"@nestjs/schematics": "^8.0.0",
|
"@nestjs/schematics": "^8.0.0",
|
||||||
"@nestjs/testing": "^8.0.0",
|
"@nestjs/testing": "^8.0.0",
|
||||||
"@types/cache-manager": "^4.0.1",
|
"@types/cache-manager": "^4.0.1",
|
||||||
|
"@types/cron": "^2.0.0",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/jest": "27.4.0",
|
"@types/jest": "27.4.0",
|
||||||
|
"@types/luxon": "^3.1.0",
|
||||||
"@types/node": "^16.0.0",
|
"@types/node": "^16.0.0",
|
||||||
"@types/node-telegram-bot-api": "^0.57.1",
|
"@types/node-telegram-bot-api": "^0.57.1",
|
||||||
"@types/supertest": "^2.0.11",
|
"@types/supertest": "^2.0.11",
|
||||||
|
|
@ -1628,6 +1632,20 @@
|
||||||
"rxjs": "^7.1.0"
|
"rxjs": "^7.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@nestjs/schedule": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-4Xaw56WiW3VsxEPPnj/iDtfjcO+sUZyYAeRxD0gnF5havncxjAnv52Iw7UH3DuzzUA784xPGgGje3Fq0Gu925g==",
|
||||||
|
"dependencies": {
|
||||||
|
"cron": "2.0.0",
|
||||||
|
"uuid": "8.3.2"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0",
|
||||||
|
"@nestjs/core": "^7.0.0 || ^8.0.0 || ^9.0.0",
|
||||||
|
"reflect-metadata": "^0.1.12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@nestjs/schematics": {
|
"node_modules/@nestjs/schematics": {
|
||||||
"version": "8.0.11",
|
"version": "8.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-8.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-8.0.11.tgz",
|
||||||
|
|
@ -1984,6 +2002,16 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
|
||||||
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw=="
|
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/cron": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cron/-/cron-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-xZM08fqvwIXgghtPVkSPKNgC+JoMQ2OHazEvyTKnNf7aWu1aB6/4lBbQFrb03Td2cUGG7ITzMv3mFYnMu6xRaQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/luxon": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/eslint": {
|
"node_modules/@types/eslint": {
|
||||||
"version": "8.4.5",
|
"version": "8.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz",
|
||||||
|
|
@ -2088,6 +2116,12 @@
|
||||||
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/luxon": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-gCd/HcCgjqSxfMrgtqxCgYk/22NBQfypwFUG7ZAyG/4pqs51WLTcUzVp1hqTbieDYeHS3WoVEh2Yv/2l+7B0Vg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/mime": {
|
"node_modules/@types/mime": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
|
||||||
|
|
@ -3632,6 +3666,22 @@
|
||||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/cron": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cron/-/cron-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-RPeRunBCFr/WEo7WLp8Jnm45F/ziGJiHVvVQEBSDTSGu6uHW49b2FOP2O14DcXlGJRLhwE7TIoDzHHK4KmlL6g==",
|
||||||
|
"dependencies": {
|
||||||
|
"luxon": "^1.23.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cron/node_modules/luxon": {
|
||||||
|
"version": "1.28.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz",
|
||||||
|
"integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||||
|
|
@ -7195,6 +7245,14 @@
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/luxon": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-7w6hmKC0/aoWnEsmPCu5Br54BmbmUp5GfcqBxQngRcXJ+q5fdfjEzn7dxmJh2YdDhgW8PccYtlWKSv4tQkrTQg==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/macos-release": {
|
"node_modules/macos-release": {
|
||||||
"version": "2.5.0",
|
"version": "2.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.0.tgz",
|
||||||
|
|
@ -11518,6 +11576,15 @@
|
||||||
"tslib": "2.4.0"
|
"tslib": "2.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@nestjs/schedule": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-4Xaw56WiW3VsxEPPnj/iDtfjcO+sUZyYAeRxD0gnF5havncxjAnv52Iw7UH3DuzzUA784xPGgGje3Fq0Gu925g==",
|
||||||
|
"requires": {
|
||||||
|
"cron": "2.0.0",
|
||||||
|
"uuid": "8.3.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@nestjs/schematics": {
|
"@nestjs/schematics": {
|
||||||
"version": "8.0.11",
|
"version": "8.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-8.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-8.0.11.tgz",
|
||||||
|
|
@ -11797,6 +11864,16 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz",
|
||||||
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw=="
|
"integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw=="
|
||||||
},
|
},
|
||||||
|
"@types/cron": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cron/-/cron-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-xZM08fqvwIXgghtPVkSPKNgC+JoMQ2OHazEvyTKnNf7aWu1aB6/4lBbQFrb03Td2cUGG7ITzMv3mFYnMu6xRaQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/luxon": "*",
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/eslint": {
|
"@types/eslint": {
|
||||||
"version": "8.4.5",
|
"version": "8.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz",
|
||||||
|
|
@ -11901,6 +11978,12 @@
|
||||||
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/luxon": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-gCd/HcCgjqSxfMrgtqxCgYk/22NBQfypwFUG7ZAyG/4pqs51WLTcUzVp1hqTbieDYeHS3WoVEh2Yv/2l+7B0Vg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/mime": {
|
"@types/mime": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz",
|
||||||
|
|
@ -13102,6 +13185,21 @@
|
||||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"cron": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cron/-/cron-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-RPeRunBCFr/WEo7WLp8Jnm45F/ziGJiHVvVQEBSDTSGu6uHW49b2FOP2O14DcXlGJRLhwE7TIoDzHHK4KmlL6g==",
|
||||||
|
"requires": {
|
||||||
|
"luxon": "^1.23.x"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"luxon": {
|
||||||
|
"version": "1.28.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz",
|
||||||
|
"integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"cross-spawn": {
|
"cross-spawn": {
|
||||||
"version": "7.0.3",
|
"version": "7.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||||
|
|
@ -15760,6 +15858,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.2.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.13.2.tgz",
|
||||||
"integrity": "sha512-VJL3nIpA79TodY/ctmZEfhASgqekbT574/c4j3jn4bKXbSCnTTCH/KltZyvL2GlV+tGSMtsWyem8DCX7qKTMBA=="
|
"integrity": "sha512-VJL3nIpA79TodY/ctmZEfhASgqekbT574/c4j3jn4bKXbSCnTTCH/KltZyvL2GlV+tGSMtsWyem8DCX7qKTMBA=="
|
||||||
},
|
},
|
||||||
|
"luxon": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/luxon/-/luxon-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-7w6hmKC0/aoWnEsmPCu5Br54BmbmUp5GfcqBxQngRcXJ+q5fdfjEzn7dxmJh2YdDhgW8PccYtlWKSv4tQkrTQg=="
|
||||||
|
},
|
||||||
"macos-release": {
|
"macos-release": {
|
||||||
"version": "2.5.0",
|
"version": "2.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
"@nestjs/core": "^8.0.0",
|
"@nestjs/core": "^8.0.0",
|
||||||
"@nestjs/platform-express": "^8.0.0",
|
"@nestjs/platform-express": "^8.0.0",
|
||||||
"@nestjs/platform-socket.io": "^8.4.4",
|
"@nestjs/platform-socket.io": "^8.4.4",
|
||||||
|
"@nestjs/schedule": "^2.1.0",
|
||||||
"@nestjs/serve-static": "^2.2.2",
|
"@nestjs/serve-static": "^2.2.2",
|
||||||
"@nestjs/websockets": "^8.4.4",
|
"@nestjs/websockets": "^8.4.4",
|
||||||
"axios": "^0.27.2",
|
"axios": "^0.27.2",
|
||||||
|
|
@ -33,6 +34,7 @@
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"hbs": "^4.2.0",
|
"hbs": "^4.2.0",
|
||||||
"imap-simple": "^5.1.0",
|
"imap-simple": "^5.1.0",
|
||||||
|
"luxon": "^3.1.0",
|
||||||
"nano": "^10.0.0",
|
"nano": "^10.0.0",
|
||||||
"node-telegram-bot-api": "^0.59.0",
|
"node-telegram-bot-api": "^0.59.0",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
|
|
@ -46,8 +48,10 @@
|
||||||
"@nestjs/schematics": "^8.0.0",
|
"@nestjs/schematics": "^8.0.0",
|
||||||
"@nestjs/testing": "^8.0.0",
|
"@nestjs/testing": "^8.0.0",
|
||||||
"@types/cache-manager": "^4.0.1",
|
"@types/cache-manager": "^4.0.1",
|
||||||
|
"@types/cron": "^2.0.0",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/jest": "27.4.0",
|
"@types/jest": "27.4.0",
|
||||||
|
"@types/luxon": "^3.1.0",
|
||||||
"@types/node": "^16.0.0",
|
"@types/node": "^16.0.0",
|
||||||
"@types/node-telegram-bot-api": "^0.57.1",
|
"@types/node-telegram-bot-api": "^0.57.1",
|
||||||
"@types/supertest": "^2.0.11",
|
"@types/supertest": "^2.0.11",
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { EnhancerService } from '@app/event-emitter/issue-enhancers/enhancer.ser
|
||||||
import { TimestampEnhancer } from '@app/event-emitter/issue-enhancers/timestamps-enhancer';
|
import { TimestampEnhancer } from '@app/event-emitter/issue-enhancers/timestamps-enhancer';
|
||||||
import { MainController } from '@app/event-emitter/main/main.controller';
|
import { MainController } from '@app/event-emitter/main/main.controller';
|
||||||
import { CacheModule, Logger, Module, OnModuleInit } from '@nestjs/common';
|
import { CacheModule, Logger, Module, OnModuleInit } from '@nestjs/common';
|
||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import { switchMap, tap } from 'rxjs';
|
import { switchMap, tap } from 'rxjs';
|
||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
import { AppService } from './app.service';
|
import { AppService } from './app.service';
|
||||||
|
|
@ -24,11 +24,14 @@ import { UserMetaInfo } from './couchdb-datasources/user-meta-info';
|
||||||
import { PersonalNotificationAdapterService } from './notifications/adapters/personal-notification.adapter/personal-notification.adapter.service';
|
import { PersonalNotificationAdapterService } from './notifications/adapters/personal-notification.adapter/personal-notification.adapter.service';
|
||||||
import { StatusChangeAdapterService } from './notifications/adapters/status-change.adapter.service';
|
import { StatusChangeAdapterService } from './notifications/adapters/status-change.adapter.service';
|
||||||
import { CurrentIssuesEccmReportService } from './reports/current-issues-eccm.report.service';
|
import { CurrentIssuesEccmReportService } from './reports/current-issues-eccm.report.service';
|
||||||
import { CurrentIssuesBotHandlerService } from './telegram-bot/handlers/current-issues.bot-handler.service';
|
import { CurrentIssuesEccmBotHandlerService } from './telegram-bot/handlers/current-issues-eccm.bot-handler.service';
|
||||||
import { CurrentIssuesEccmReportController } from './reports/current-issues-eccm.report.controller';
|
import { CurrentIssuesEccmReportController } from './reports/current-issues-eccm.report.controller';
|
||||||
import { DailyEccmReportController } from './reports/daily-eccm.report.controller';
|
import { DailyEccmReportController } from './reports/daily-eccm.report.controller';
|
||||||
import { DailyEccmReportService } from './reports/daily-eccm.report.service';
|
import { DailyEccmReportService } from './reports/daily-eccm.report.service';
|
||||||
import { ChangesService } from './changes/changes.service';
|
import { ChangesService } from './changes/changes.service';
|
||||||
|
import { DailyEccmReportsDatasource } from './couchdb-datasources/daily-eccm-reports.datasource';
|
||||||
|
import { ScheduleModule } from '@nestjs/schedule';
|
||||||
|
import { DailyEccmReportTask } from './reports/daily-eccm.report.task';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
|
|
@ -39,6 +42,7 @@ import { ChangesService } from './changes/changes.service';
|
||||||
CacheModule.register({
|
CacheModule.register({
|
||||||
isGlobal: true,
|
isGlobal: true,
|
||||||
}),
|
}),
|
||||||
|
ScheduleModule.forRoot(),
|
||||||
],
|
],
|
||||||
controllers: [
|
controllers: [
|
||||||
AppController,
|
AppController,
|
||||||
|
|
@ -61,9 +65,11 @@ import { ChangesService } from './changes/changes.service';
|
||||||
PersonalNotificationAdapterService,
|
PersonalNotificationAdapterService,
|
||||||
StatusChangeAdapterService,
|
StatusChangeAdapterService,
|
||||||
CurrentIssuesEccmReportService,
|
CurrentIssuesEccmReportService,
|
||||||
CurrentIssuesBotHandlerService,
|
CurrentIssuesEccmBotHandlerService,
|
||||||
DailyEccmReportService,
|
DailyEccmReportService,
|
||||||
ChangesService,
|
ChangesService,
|
||||||
|
DailyEccmReportsDatasource,
|
||||||
|
DailyEccmReportTask,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppModule implements OnModuleInit {
|
export class AppModule implements OnModuleInit {
|
||||||
|
|
@ -88,6 +94,7 @@ export class AppModule implements OnModuleInit {
|
||||||
Users.getDatasource();
|
Users.getDatasource();
|
||||||
Changes.getDatasource();
|
Changes.getDatasource();
|
||||||
UserMetaInfo.getDatasource();
|
UserMetaInfo.getDatasource();
|
||||||
|
DailyEccmReportsDatasource.getDatasource();
|
||||||
|
|
||||||
this.enhancerService.addEnhancer([
|
this.enhancerService.addEnhancer([
|
||||||
this.timestampEnhancer,
|
this.timestampEnhancer,
|
||||||
|
|
@ -104,20 +111,8 @@ export class AppModule implements OnModuleInit {
|
||||||
);
|
);
|
||||||
this.personalNotificationAdapterService.send(resp);
|
this.personalNotificationAdapterService.send(resp);
|
||||||
});
|
});
|
||||||
this.statusChangeNotificationsService.$changes.subscribe((change) => {
|
this.statusChangeNotificationsService.$batchChanges.subscribe((changes) => {
|
||||||
const messages = change.messages
|
this.statusChangeAdapterService.batchSend(changes);
|
||||||
.map((m) => m.change_message)
|
|
||||||
.filter((m) => !!m);
|
|
||||||
const notifications = change.messages
|
|
||||||
.map((m) => m.notification_message)
|
|
||||||
.filter((m) => !!m);
|
|
||||||
this.logger.log(
|
|
||||||
`Get status changes messages for ` +
|
|
||||||
`issue_id = ${change.issue_id}, ` +
|
|
||||||
`messages = ${JSON.stringify(messages)}, ` +
|
|
||||||
`notifications = ${JSON.stringify(notifications)}`,
|
|
||||||
);
|
|
||||||
this.statusChangeAdapterService.send(change);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.redmineIssuesCacheWriterService.subject
|
this.redmineIssuesCacheWriterService.subject
|
||||||
|
|
|
||||||
41
src/couchdb-datasources/daily-eccm-reports.datasource.ts
Normal file
41
src/couchdb-datasources/daily-eccm-reports.datasource.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { CouchDb } from '@app/event-emitter/couchdb-datasources/couchdb';
|
||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import nano from 'nano';
|
||||||
|
import { DailyEccmReport } from 'src/reports/daily-eccm.report.service';
|
||||||
|
import configuration from '../configs/app';
|
||||||
|
|
||||||
|
const config = configuration();
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DailyEccmReportsDatasource {
|
||||||
|
private static logger = new Logger(DailyEccmReportsDatasource.name);
|
||||||
|
private static db = null;
|
||||||
|
private static initilized = false;
|
||||||
|
|
||||||
|
static async getDatasource(): Promise<
|
||||||
|
nano.DocumentScope<DailyEccmReport.Models.Report>
|
||||||
|
> {
|
||||||
|
if (DailyEccmReportsDatasource.initilized) {
|
||||||
|
return DailyEccmReportsDatasource.db;
|
||||||
|
}
|
||||||
|
DailyEccmReportsDatasource.initilized = true;
|
||||||
|
const n = CouchDb.getCouchDb();
|
||||||
|
const dbName = config.couchDb.dbs.eccmDailyReports;
|
||||||
|
const dbs = await n.db.list();
|
||||||
|
if (!dbs.includes(dbName)) {
|
||||||
|
await n.db.create(dbName);
|
||||||
|
}
|
||||||
|
DailyEccmReportsDatasource.db = await n.db.use(dbName);
|
||||||
|
DailyEccmReportsDatasource.initilized = true;
|
||||||
|
DailyEccmReportsDatasource.logger.log(
|
||||||
|
`Connected to eccm-daily-reports db - ${dbName}`,
|
||||||
|
);
|
||||||
|
return DailyEccmReportsDatasource.db;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDatasource(): Promise<
|
||||||
|
nano.DocumentScope<DailyEccmReport.Models.Report>
|
||||||
|
> {
|
||||||
|
return await DailyEccmReportsDatasource.getDatasource();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,7 +13,9 @@ export type AppConfig = {
|
||||||
dbs: {
|
dbs: {
|
||||||
changes: string;
|
changes: string;
|
||||||
userMetaInfo: string;
|
userMetaInfo: string;
|
||||||
|
eccmDailyReports: string;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
telegramBotToken: string;
|
telegramBotToken: string;
|
||||||
|
periodValidityNotification: number;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -10,5 +10,9 @@ export namespace EccmConfig {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
currentIssuesStatuses: string[];
|
currentIssuesStatuses: string[];
|
||||||
groups: UserGroup[];
|
groups: UserGroup[];
|
||||||
|
dailyTime: {
|
||||||
|
hour: number;
|
||||||
|
minute: number;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,40 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import TelegramBot from 'node-telegram-bot-api';
|
||||||
import { Change } from 'src/models/change.model';
|
import { Change } from 'src/models/change.model';
|
||||||
import { TelegramBotService } from 'src/telegram-bot/telegram-bot.service';
|
import { TelegramBotService } from 'src/telegram-bot/telegram-bot.service';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||||
|
namespace StatusChangeAdapter {
|
||||||
|
export type MsgFromBatch = {
|
||||||
|
initiatorId: number;
|
||||||
|
recipientId: number;
|
||||||
|
issueId: number;
|
||||||
|
createdAt: number;
|
||||||
|
msg: string;
|
||||||
|
options?: TelegramBot.SendMessageOptions;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class StatusChangeAdapterService {
|
export class StatusChangeAdapterService {
|
||||||
constructor(private telegramBotService: TelegramBotService) {}
|
private periodValidityNotification: number;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private telegramBotService: TelegramBotService,
|
||||||
|
private configService: ConfigService,
|
||||||
|
) {
|
||||||
|
this.periodValidityNotification = this.configService.get<number>(
|
||||||
|
'periodValidityNotification',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Удалить устаревший метод send
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated
|
||||||
|
* @see StatusChangeAdapterService.batchSend
|
||||||
|
*/
|
||||||
async send(change: Change): Promise<boolean[]> {
|
async send(change: Change): Promise<boolean[]> {
|
||||||
const promises = change.messages.map((m) => {
|
const promises = change.messages.map((m) => {
|
||||||
if (!m || !m.recipient || !m.recipient.id || !m.notification_message) {
|
if (!m || !m.recipient || !m.recipient.id || !m.notification_message) {
|
||||||
|
|
@ -19,4 +48,64 @@ export class StatusChangeAdapterService {
|
||||||
});
|
});
|
||||||
return await Promise.all(promises);
|
return await Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async batchSend(changes: Change[]): Promise<void> {
|
||||||
|
const messages = this.getMessages(changes).map((item) => {
|
||||||
|
item.options = { parse_mode: 'HTML' };
|
||||||
|
return item;
|
||||||
|
});
|
||||||
|
for (let i = 0; i < messages.length; i++) {
|
||||||
|
const message = messages[i];
|
||||||
|
await this.telegramBotService.sendMessageByRedmineId(
|
||||||
|
message.recipientId,
|
||||||
|
message.msg,
|
||||||
|
message.options,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getMessages(changes: Change[]): StatusChangeAdapter.MsgFromBatch[] {
|
||||||
|
const res: StatusChangeAdapter.MsgFromBatch[] = [];
|
||||||
|
const store: Record<string, StatusChangeAdapter.MsgFromBatch> = {};
|
||||||
|
const nowTimestamp = new Date().getTime();
|
||||||
|
for (let i = 0; i < changes.length; i++) {
|
||||||
|
const change = changes[i];
|
||||||
|
if (
|
||||||
|
nowTimestamp - change.created_on_timestamp >
|
||||||
|
this.periodValidityNotification
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (let j = 0; j < change.messages.length; j++) {
|
||||||
|
const message = change.messages[j];
|
||||||
|
if (!message.notification_message) continue;
|
||||||
|
if (change.initiator.id == message.recipient.id) continue;
|
||||||
|
const item: StatusChangeAdapter.MsgFromBatch = {
|
||||||
|
initiatorId: change.initiator.id,
|
||||||
|
recipientId: message.recipient.id,
|
||||||
|
createdAt: change.created_on_timestamp,
|
||||||
|
issueId: change.issue_id,
|
||||||
|
msg: message.notification_message,
|
||||||
|
};
|
||||||
|
const key = this.keyForMsgFromBatch(item);
|
||||||
|
if (
|
||||||
|
!store[key] ||
|
||||||
|
(store[key] && store[key].createdAt < item.createdAt)
|
||||||
|
) {
|
||||||
|
store[key] = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const key in store) {
|
||||||
|
if (Object.prototype.hasOwnProperty.call(store, key)) {
|
||||||
|
const item = store[key];
|
||||||
|
res.push(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
private keyForMsgFromBatch(item: StatusChangeAdapter.MsgFromBatch): string {
|
||||||
|
return `${item.issueId}-${item.recipientId}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ export class StatusChangeNotificationsService {
|
||||||
private statusChanges: StatusChangesConfig.Config;
|
private statusChanges: StatusChangesConfig.Config;
|
||||||
|
|
||||||
$changes = new Subject<Change>();
|
$changes = new Subject<Change>();
|
||||||
|
$batchChanges = new Subject<Change[]>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private usersService: UsersService,
|
private usersService: UsersService,
|
||||||
|
|
@ -68,6 +69,7 @@ export class StatusChangeNotificationsService {
|
||||||
);
|
);
|
||||||
|
|
||||||
changes.forEach((c) => this.$changes.next(c));
|
changes.forEach((c) => this.$changes.next(c));
|
||||||
|
this.$batchChanges.next(changes);
|
||||||
|
|
||||||
return changes;
|
return changes;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Controller, Get, Query, Render } from '@nestjs/common';
|
import { Controller, Get, Param, Query, Render } from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
DailyEccmReport,
|
DailyEccmReport,
|
||||||
DailyEccmReportService,
|
DailyEccmReportService,
|
||||||
|
|
@ -10,28 +10,61 @@ export class DailyEccmReportController {
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Render('daily-eccm-report')
|
@Render('daily-eccm-report')
|
||||||
|
async getReportDefault(
|
||||||
|
@Query('from') from: string,
|
||||||
|
@Query('to') to: string,
|
||||||
|
): Promise<DailyEccmReport.Models.Report> {
|
||||||
|
return await this.getReport(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/delete/:name')
|
||||||
|
async deleteReport(@Param('name') name: string): Promise<boolean> {
|
||||||
|
return await this.dailyEccmReportService.deleteReport(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/load/:name')
|
||||||
|
@Render('daily-eccm-report')
|
||||||
|
async loadReport(
|
||||||
|
@Param('name') name: string,
|
||||||
|
): Promise<DailyEccmReport.Models.Report> {
|
||||||
|
return await this.dailyEccmReportService.loadReport(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/load/:name/raw')
|
||||||
|
async loadReportRawData(
|
||||||
|
@Param('name') name: string,
|
||||||
|
): Promise<DailyEccmReport.Models.Report> {
|
||||||
|
return await this.dailyEccmReportService.loadReport(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/generate')
|
||||||
|
@Render('daily-eccm-report')
|
||||||
async getReport(
|
async getReport(
|
||||||
@Query('from') from: string,
|
@Query('from') from: string,
|
||||||
@Query('to') to: string,
|
@Query('to') to: string,
|
||||||
|
@Query('name') name?: string,
|
||||||
|
@Query('overwrite') overwrite?: boolean,
|
||||||
): Promise<DailyEccmReport.Models.Report> {
|
): Promise<DailyEccmReport.Models.Report> {
|
||||||
const now = new Date().toISOString();
|
return await this.getReportRawData(from, to, name, overwrite);
|
||||||
return await this.dailyEccmReportService.generateReport({
|
|
||||||
from: from,
|
|
||||||
to: to,
|
|
||||||
reportDate: now,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('/raw')
|
@Get('/generate/raw')
|
||||||
async getReportRawData(
|
async getReportRawData(
|
||||||
@Query('from') from: string,
|
@Query('from') from: string,
|
||||||
@Query('to') to: string,
|
@Query('to') to: string,
|
||||||
|
@Query('name') name?: string,
|
||||||
|
@Query('overwrite') overwrite?: boolean,
|
||||||
): Promise<DailyEccmReport.Models.Report> {
|
): Promise<DailyEccmReport.Models.Report> {
|
||||||
const now = new Date().toISOString();
|
const params = this.dailyEccmReportService.generateParams(from, to, name);
|
||||||
return await this.dailyEccmReportService.generateReport({
|
const data = await this.dailyEccmReportService.generateReport(params);
|
||||||
from: from,
|
if (name) {
|
||||||
to: to,
|
const saveResult = await this.dailyEccmReportService.saveReport(
|
||||||
reportDate: now,
|
data,
|
||||||
});
|
overwrite,
|
||||||
|
);
|
||||||
|
return saveResult ? data : null;
|
||||||
|
} else {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,10 @@ import { TimestampConverter } from '@app/event-emitter/utils/timestamp-converter
|
||||||
import { IssuesService } from '@app/event-emitter/issues/issues.service';
|
import { IssuesService } from '@app/event-emitter/issues/issues.service';
|
||||||
import { UsersService } from '@app/event-emitter/users/users.service';
|
import { UsersService } from '@app/event-emitter/users/users.service';
|
||||||
import { GetParentsHint } from '@app/event-emitter/utils/get-parents-hint';
|
import { GetParentsHint } from '@app/event-emitter/utils/get-parents-hint';
|
||||||
|
import { DailyEccmReportsDatasource } from 'src/couchdb-datasources/daily-eccm-reports.datasource';
|
||||||
|
import { Timestamped } from '@app/event-emitter/models/timestamped';
|
||||||
|
import { TimestampNowFill } from '@app/event-emitter/utils/timestamp-now-fill';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||||
export namespace DailyEccmReport {
|
export namespace DailyEccmReport {
|
||||||
|
|
@ -96,7 +100,9 @@ export namespace DailyEccmReport {
|
||||||
export type Params = {
|
export type Params = {
|
||||||
from: string;
|
from: string;
|
||||||
to: string;
|
to: string;
|
||||||
reportDate: string;
|
name: string;
|
||||||
|
project: string;
|
||||||
|
versions: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IssueAndChanges = {
|
export type IssueAndChanges = {
|
||||||
|
|
@ -140,7 +146,8 @@ export namespace DailyEccmReport {
|
||||||
private eccmUsers: RedmineTypes.User[],
|
private eccmUsers: RedmineTypes.User[],
|
||||||
private issuesService: IssuesService,
|
private issuesService: IssuesService,
|
||||||
) {
|
) {
|
||||||
const users: Models.UserReport[] = this.eccmUsers.map((user) => {
|
const users: Models.UserReport[] = this.eccmUsers
|
||||||
|
.map((user) => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
this.logger.error(`Not found user data`);
|
this.logger.error(`Not found user data`);
|
||||||
return;
|
return;
|
||||||
|
|
@ -155,13 +162,16 @@ export namespace DailyEccmReport {
|
||||||
activities: [],
|
activities: [],
|
||||||
issuesGroupedByStatus: [],
|
issuesGroupedByStatus: [],
|
||||||
} as Models.UserReport;
|
} as Models.UserReport;
|
||||||
}).filter((user) => Boolean(user));
|
})
|
||||||
|
.filter((user) => Boolean(user));
|
||||||
this.report = {
|
this.report = {
|
||||||
byUsers: users,
|
byUsers: users,
|
||||||
params: {
|
params: {
|
||||||
from: '',
|
from: '',
|
||||||
to: '',
|
to: '',
|
||||||
reportDate: '',
|
name: '',
|
||||||
|
project: '',
|
||||||
|
versions: [],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -173,7 +183,9 @@ export namespace DailyEccmReport {
|
||||||
setParams(params: Models.Params): void {
|
setParams(params: Models.Params): void {
|
||||||
this.report.params.from = params.from;
|
this.report.params.from = params.from;
|
||||||
this.report.params.to = params.to;
|
this.report.params.to = params.to;
|
||||||
this.report.params.reportDate = params.reportDate;
|
this.report.params.name = params.name;
|
||||||
|
this.report.params.versions = [...params.versions];
|
||||||
|
this.report.params.project = params.project;
|
||||||
}
|
}
|
||||||
|
|
||||||
async setCurrentIssues(issues: RedmineTypes.Issue[]): Promise<void> {
|
async setCurrentIssues(issues: RedmineTypes.Issue[]): Promise<void> {
|
||||||
|
|
@ -355,6 +367,7 @@ export class DailyEccmReportService {
|
||||||
private changesService: ChangesService,
|
private changesService: ChangesService,
|
||||||
private issuesService: IssuesService,
|
private issuesService: IssuesService,
|
||||||
private usersService: UsersService,
|
private usersService: UsersService,
|
||||||
|
private dailyEccmReportsDatasource: DailyEccmReportsDatasource,
|
||||||
) {
|
) {
|
||||||
this.eccmConfig = this.configService.get<EccmConfig.Config>('redmineEccm');
|
this.eccmConfig = this.configService.get<EccmConfig.Config>('redmineEccm');
|
||||||
}
|
}
|
||||||
|
|
@ -389,6 +402,83 @@ export class DailyEccmReportService {
|
||||||
return report.get();
|
return report.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generateParams(
|
||||||
|
from: string,
|
||||||
|
to: string,
|
||||||
|
name?: string,
|
||||||
|
): DailyEccmReport.Models.Params {
|
||||||
|
const fromDate = DateTime.fromISO(from);
|
||||||
|
if (!fromDate.isValid) throw new Error('from is invalid date');
|
||||||
|
const toDate = DateTime.fromISO(to);
|
||||||
|
if (!toDate.isValid) throw new Error('to is invalid date');
|
||||||
|
let nameValue: string | null = name || null;
|
||||||
|
if (!nameValue) {
|
||||||
|
nameValue = DateTime.now().toFormat('yyyy-MM-dd');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
from: fromDate.toISO(),
|
||||||
|
to: toDate.toISO(),
|
||||||
|
name: nameValue,
|
||||||
|
project: this.eccmConfig.projectName,
|
||||||
|
versions: [...this.eccmConfig.currentVersions],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveReport(
|
||||||
|
report: DailyEccmReport.Models.Report,
|
||||||
|
overwrite?: boolean,
|
||||||
|
): Promise<boolean> {
|
||||||
|
const name = report.params.name;
|
||||||
|
const existsReport = await this.loadReport(name);
|
||||||
|
if (existsReport && !overwrite) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReportType = nano.DocumentGetResponse &
|
||||||
|
DailyEccmReport.Models.Report &
|
||||||
|
Timestamped;
|
||||||
|
|
||||||
|
const newReport: ReportType = TimestampNowFill({
|
||||||
|
...report,
|
||||||
|
_id: name,
|
||||||
|
_rev: existsReport?._rev,
|
||||||
|
});
|
||||||
|
|
||||||
|
const datasource = await this.dailyEccmReportsDatasource.getDatasource();
|
||||||
|
await datasource.insert(newReport);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadReport(
|
||||||
|
name: string,
|
||||||
|
): Promise<
|
||||||
|
| (DailyEccmReport.Models.Report & nano.DocumentGetResponse & Timestamped)
|
||||||
|
| null
|
||||||
|
> {
|
||||||
|
const datasource = await this.dailyEccmReportsDatasource.getDatasource();
|
||||||
|
let resp: any = null;
|
||||||
|
try {
|
||||||
|
resp = await datasource.get(name);
|
||||||
|
} catch (ex) {
|
||||||
|
this.logger.warn(
|
||||||
|
`Cannot load report ${name} with error message ${ex.message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteReport(name: string): Promise<boolean> {
|
||||||
|
const datasource = await this.dailyEccmReportsDatasource.getDatasource();
|
||||||
|
const report = await this.loadReport(name);
|
||||||
|
let res = false;
|
||||||
|
try {
|
||||||
|
await datasource.destroy(report._id, report._rev);
|
||||||
|
res = true;
|
||||||
|
} catch (ex) {}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
private async createEmptyReport(): Promise<DailyEccmReport.Report> {
|
private async createEmptyReport(): Promise<DailyEccmReport.Report> {
|
||||||
const usersList: string[] = this.eccmConfig.groups.reduce((acc, group) => {
|
const usersList: string[] = this.eccmConfig.groups.reduce((acc, group) => {
|
||||||
group.people.forEach((fullname) => {
|
group.people.forEach((fullname) => {
|
||||||
|
|
|
||||||
67
src/reports/daily-eccm.report.task.ts
Normal file
67
src/reports/daily-eccm.report.task.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
import { Cron } from '@nestjs/schedule';
|
||||||
|
import { DailyEccmReportService } from './daily-eccm.report.service';
|
||||||
|
import { DateTime, Duration } from 'luxon';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||||
|
namespace Models {
|
||||||
|
export type Time = {
|
||||||
|
hour: number;
|
||||||
|
minute: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DailyEccmReportTask {
|
||||||
|
private logger = new Logger(DailyEccmReportTask.name);
|
||||||
|
private eccmDailyTime: Models.Time;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private dailyEccmReportService: DailyEccmReportService,
|
||||||
|
private configService: ConfigService,
|
||||||
|
) {
|
||||||
|
this.eccmDailyTime = this.configService.get<Models.Time>(
|
||||||
|
'redmineEccm.dailyTime',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Cron('25 9,10 1-5 * *')
|
||||||
|
async generateReport(): Promise<void> {
|
||||||
|
this.logger.log(`Generate daily eccm report by cron task started`);
|
||||||
|
const now = DateTime.now();
|
||||||
|
const toDate = DateTime.local(
|
||||||
|
now.year,
|
||||||
|
now.month,
|
||||||
|
now.day,
|
||||||
|
this.eccmDailyTime.hour,
|
||||||
|
this.eccmDailyTime.minute,
|
||||||
|
);
|
||||||
|
let duration: Duration;
|
||||||
|
if (now.weekday == 1) {
|
||||||
|
duration = Duration.fromObject({ days: -3 });
|
||||||
|
} else {
|
||||||
|
duration = Duration.fromObject({ days: -1 });
|
||||||
|
}
|
||||||
|
const fromDate = toDate.plus(duration);
|
||||||
|
const name = now.toFormat('yyyy-MM-dd');
|
||||||
|
const params = this.dailyEccmReportService.generateParams(
|
||||||
|
fromDate.toJSDate().toISOString(),
|
||||||
|
toDate.toJSDate().toISOString(),
|
||||||
|
name,
|
||||||
|
);
|
||||||
|
|
||||||
|
const reportData = await this.dailyEccmReportService.generateReport(params);
|
||||||
|
const saveResult = await this.dailyEccmReportService.saveReport(
|
||||||
|
reportData,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
this.logger.log(
|
||||||
|
`Generate daily eccm report by cron task ` +
|
||||||
|
`finished with params = ${JSON.stringify(params)} ` +
|
||||||
|
`and result = ${saveResult}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,17 +2,23 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import TelegramBot from 'node-telegram-bot-api';
|
import TelegramBot from 'node-telegram-bot-api';
|
||||||
import { EccmConfig } from 'src/models/eccm-config.model';
|
import { EccmConfig } from 'src/models/eccm-config.model';
|
||||||
import { CurrentIssuesEccmReport, CurrentIssuesEccmReportService } from 'src/reports/current-issues-eccm.report.service';
|
import {
|
||||||
|
CurrentIssuesEccmReport,
|
||||||
|
CurrentIssuesEccmReportService,
|
||||||
|
} from 'src/reports/current-issues-eccm.report.service';
|
||||||
import { UserMetaInfoService } from 'src/user-meta-info/user-meta-info.service';
|
import { UserMetaInfoService } from 'src/user-meta-info/user-meta-info.service';
|
||||||
import { TelegramBotService } from '../telegram-bot.service';
|
import { TelegramBotService } from '../telegram-bot.service';
|
||||||
|
import { TelegramBotHandlerInterface } from '../telegram.bot-handler.interface';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CurrentIssuesBotHandlerService {
|
export class CurrentIssuesEccmBotHandlerService
|
||||||
private forName = /\/current_issues_eccm (.+) (.+)/;
|
implements TelegramBotHandlerInterface
|
||||||
|
{
|
||||||
|
private forName = /\/current_issues_eccm (.+)/;
|
||||||
private forCurrentUser = /\/current_issues_eccm/;
|
private forCurrentUser = /\/current_issues_eccm/;
|
||||||
private service: TelegramBotService;
|
private service: TelegramBotService;
|
||||||
private eccmConfig: EccmConfig.Config;
|
private eccmConfig: EccmConfig.Config;
|
||||||
private logger = new Logger(CurrentIssuesBotHandlerService.name);
|
private logger = new Logger(CurrentIssuesEccmBotHandlerService.name);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
|
|
@ -26,7 +32,7 @@ export class CurrentIssuesBotHandlerService {
|
||||||
if (!this.service) {
|
if (!this.service) {
|
||||||
this.service = service;
|
this.service = service;
|
||||||
}
|
}
|
||||||
bot.onText(/\/current_issues_eccm/, async (msg) => {
|
bot.onText(this.forCurrentUser, async (msg) => {
|
||||||
const userMetaInfo = await this.userMetaInfoService.findByTelegramId(
|
const userMetaInfo = await this.userMetaInfoService.findByTelegramId(
|
||||||
msg.chat.id,
|
msg.chat.id,
|
||||||
);
|
);
|
||||||
|
|
@ -44,4 +50,8 @@ export class CurrentIssuesBotHandlerService {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getHelpMsg(): string {
|
||||||
|
return '/current_issues_eccm - ваши текущие задачи из проекта ECCM';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5,7 +5,8 @@ import TelegramBot from 'node-telegram-bot-api';
|
||||||
import { UserMetaInfoService } from 'src/user-meta-info/user-meta-info.service';
|
import { UserMetaInfoService } from 'src/user-meta-info/user-meta-info.service';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { UserMetaInfoModel } from 'src/models/user-meta-info.model';
|
import { UserMetaInfoModel } from 'src/models/user-meta-info.model';
|
||||||
import { CurrentIssuesBotHandlerService } from './handlers/current-issues.bot-handler.service';
|
import { CurrentIssuesEccmBotHandlerService } from './handlers/current-issues-eccm.bot-handler.service';
|
||||||
|
import { TelegramBotHandlerInterface } from './telegram.bot-handler.interface';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TelegramBotService {
|
export class TelegramBotService {
|
||||||
|
|
@ -16,16 +17,19 @@ export class TelegramBotService {
|
||||||
|
|
||||||
private registerRe = /\/register (\d+) (.+)/;
|
private registerRe = /\/register (\d+) (.+)/;
|
||||||
|
|
||||||
|
private handlers: TelegramBotHandlerInterface[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private userMetaInfoService: UserMetaInfoService,
|
private userMetaInfoService: UserMetaInfoService,
|
||||||
private usersService: UsersService,
|
private usersService: UsersService,
|
||||||
private configService: ConfigService,
|
private configService: ConfigService,
|
||||||
private currentIssuesBotHandlerService: CurrentIssuesBotHandlerService,
|
private currentIssuesBotHandlerService: CurrentIssuesEccmBotHandlerService,
|
||||||
) {
|
) {
|
||||||
this.telegramBotToken = this.configService.get<string>('telegramBotToken');
|
this.telegramBotToken = this.configService.get<string>('telegramBotToken');
|
||||||
this.redminePublicUrlPrefix =
|
this.redminePublicUrlPrefix =
|
||||||
this.configService.get<string>('redmineUrlPublic');
|
this.configService.get<string>('redmineUrlPublic');
|
||||||
this.initTelegramBot();
|
this.initTelegramBot();
|
||||||
|
this.handlers.push(this.currentIssuesBotHandlerService);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initTelegramBot(): Promise<void> {
|
private async initTelegramBot(): Promise<void> {
|
||||||
|
|
@ -51,19 +55,22 @@ export class TelegramBotService {
|
||||||
msg.chat.id,
|
msg.chat.id,
|
||||||
);
|
);
|
||||||
let helpMessage: string;
|
let helpMessage: string;
|
||||||
|
/* eslint-disable */
|
||||||
if (userMetaInfo) {
|
if (userMetaInfo) {
|
||||||
// eslint-disable-next-line prettier/prettier
|
const handlersMessages = this.handlers
|
||||||
|
.map((handler) => handler.getHelpMsg())
|
||||||
|
.join('\n');
|
||||||
helpMessage = [
|
helpMessage = [
|
||||||
`/current_issues - мои текущие задачи`,
|
handlersMessages,
|
||||||
`/help`
|
`/help`
|
||||||
].join('\n');
|
].join('\n');
|
||||||
} else {
|
} else {
|
||||||
// eslint-disable-next-line prettier/prettier
|
|
||||||
helpMessage = [
|
helpMessage = [
|
||||||
`/register <redmine_id> <redmine_token>`,
|
`/register <redmine_id> <redmine_token>`,
|
||||||
`/help`
|
`/help`,
|
||||||
].join('\n');
|
].join('\n');
|
||||||
}
|
}
|
||||||
|
/* eslint-enable */
|
||||||
this.logger.debug(
|
this.logger.debug(
|
||||||
`Sent help message for telegramChatId = ${msg.chat.id}, ` +
|
`Sent help message for telegramChatId = ${msg.chat.id}, ` +
|
||||||
`message = ${helpMessage}`,
|
`message = ${helpMessage}`,
|
||||||
|
|
|
||||||
7
src/telegram-bot/telegram.bot-handler.interface.ts
Normal file
7
src/telegram-bot/telegram.bot-handler.interface.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import TelegramBot from 'node-telegram-bot-api';
|
||||||
|
import { TelegramBotService } from './telegram-bot.service';
|
||||||
|
|
||||||
|
export interface TelegramBotHandlerInterface {
|
||||||
|
init(service: TelegramBotService, bot: TelegramBot): Promise<void>;
|
||||||
|
getHelpMsg(): string;
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,9 @@
|
||||||
<ul>
|
<ul>
|
||||||
<li>От - {{this.params.from}}</li>
|
<li>От - {{this.params.from}}</li>
|
||||||
<li>До - {{this.params.to}}</li>
|
<li>До - {{this.params.to}}</li>
|
||||||
<li>Дата выгрузки - {{this.params.reportDate}}</li>
|
<li>Имя отчёта - {{this.params.name}}</li>
|
||||||
|
<li>Имя проекта - {{this.params.project}}</li>
|
||||||
|
<li>Версии - {{this.params.versions}}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<h1>Отчёт по работникам</h1>
|
<h1>Отчёт по работникам</h1>
|
||||||
|
|
@ -36,7 +38,7 @@
|
||||||
<ul>
|
<ul>
|
||||||
{{#each this.activities}}
|
{{#each this.activities}}
|
||||||
<li title="{{this.parents}}">
|
<li title="{{this.parents}}">
|
||||||
{{>redmineIssueAHref issue=this.issue}} (приоритет {{this.issue.priority.name}}; версия {{this.issue.fixed_version.name}}) - {{this.issue.subject}}
|
{{>redmineIssueAHref issue=this.issue}} (приоритет {{this.issue.priority.name}}; версия {{this.issue.fixed_version.name}}; статус {{this.issue.status.name}}) - {{this.issue.subject}}
|
||||||
<ul>
|
<ul>
|
||||||
{{#each this.changes}}
|
{{#each this.changes}}
|
||||||
<li>{{this.created_on}}: {{this.change_message}}</li>
|
<li>{{this.created_on}}: {{this.change_message}}</li>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue