diff --git a/configs/main-config.jsonc.dist b/configs/main-config.jsonc.dist index 9e26c4d..4d5c643 100644 --- a/configs/main-config.jsonc.dist +++ b/configs/main-config.jsonc.dist @@ -3,7 +3,8 @@ "dbs": { "changes": "", "userMetaInfo": "", - "eccmDailyReports": "" + "eccmDailyReports": "", + "eccmDailyReportsUserComments": "" } }, "telegramBotToken": "", diff --git a/src/app.module.ts b/src/app.module.ts index 34a48ae..5f24362 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -32,6 +32,9 @@ 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'; +import { DailyEccmReportsUserCommentsDatasource } from './couchdb-datasources/daily-eccm-reports-user-comments.datasource'; +import { DailyEccmUserCommentsService } from './reports/daily-eccm-user-comments.service'; +import { SetDailyEccmUserCommentBotHandlerService } from './telegram-bot/handlers/set-daily-eccm-user-comment.bot-handler.service'; @Module({ imports: [ @@ -70,6 +73,9 @@ import { DailyEccmReportTask } from './reports/daily-eccm.report.task'; ChangesService, DailyEccmReportsDatasource, DailyEccmReportTask, + DailyEccmReportsUserCommentsDatasource, + DailyEccmUserCommentsService, + SetDailyEccmUserCommentBotHandlerService, ], }) export class AppModule implements OnModuleInit { @@ -95,6 +101,7 @@ export class AppModule implements OnModuleInit { Changes.getDatasource(); UserMetaInfo.getDatasource(); DailyEccmReportsDatasource.getDatasource(); + DailyEccmReportsUserCommentsDatasource.getDatasource(); this.enhancerService.addEnhancer([ this.timestampEnhancer, diff --git a/src/consts/date-time.consts.ts b/src/consts/date-time.consts.ts new file mode 100644 index 0000000..ef91cba --- /dev/null +++ b/src/consts/date-time.consts.ts @@ -0,0 +1 @@ +export const ISO_DATE_FORMAT = 'yyyy-MM-dd'; diff --git a/src/couchdb-datasources/daily-eccm-reports-user-comments.datasource.ts b/src/couchdb-datasources/daily-eccm-reports-user-comments.datasource.ts new file mode 100644 index 0000000..57b15b5 --- /dev/null +++ b/src/couchdb-datasources/daily-eccm-reports-user-comments.datasource.ts @@ -0,0 +1,43 @@ +import { CouchDb } from '@app/event-emitter/couchdb-datasources/couchdb'; +import nano from 'nano'; +import { Injectable, Logger } from '@nestjs/common'; +import configuration from '../configs/app'; +import { DailyEccmUserComments } from 'src/reports/daily-eccm-user-comments.service'; + +const config = configuration(); + +@Injectable() +export class DailyEccmReportsUserCommentsDatasource { + private static logger = new Logger( + DailyEccmReportsUserCommentsDatasource.name, + ); + private static db = null; + private static initilized = false; + + static async getDatasource(): Promise< + nano.DocumentScope + > { + if (DailyEccmReportsUserCommentsDatasource.initilized) { + return DailyEccmReportsUserCommentsDatasource.db; + } + DailyEccmReportsUserCommentsDatasource.initilized = true; + const n = CouchDb.getCouchDb(); + const dbName = config.couchDb.dbs.eccmDailyReportsUserComments; + const dbs = await n.db.list(); + if (!dbs.includes(dbName)) { + await n.db.create(dbName); + } + DailyEccmReportsUserCommentsDatasource.db = await n.db.use(dbName); + DailyEccmReportsUserCommentsDatasource.initilized = true; + DailyEccmReportsUserCommentsDatasource.logger.log( + `Connected to eccm_daily_reports_user_comments db - ${dbName}`, + ); + return DailyEccmReportsUserCommentsDatasource.db; + } + + async getDatasource(): Promise< + nano.DocumentScope + > { + return await DailyEccmReportsUserCommentsDatasource.getDatasource(); + } +} diff --git a/src/models/app-config.model.ts b/src/models/app-config.model.ts index b1352af..141d191 100644 --- a/src/models/app-config.model.ts +++ b/src/models/app-config.model.ts @@ -14,6 +14,7 @@ export type AppConfig = { changes: string; userMetaInfo: string; eccmDailyReports: string; + eccmDailyReportsUserComments: string; }; }; telegramBotToken: string; diff --git a/src/reports/daily-eccm-user-comments.service.ts b/src/reports/daily-eccm-user-comments.service.ts new file mode 100644 index 0000000..e5d2a09 --- /dev/null +++ b/src/reports/daily-eccm-user-comments.service.ts @@ -0,0 +1,93 @@ +/* eslint-disable @typescript-eslint/no-namespace */ +import { UNLIMITED } from '@app/event-emitter/consts/consts'; +import { Timestamped } from '@app/event-emitter/models/timestamped'; +import { TimestampNowFill } from '@app/event-emitter/utils/timestamp-now-fill'; +import { Injectable } from '@nestjs/common'; +import nano from 'nano'; +import { DailyEccmReportsUserCommentsDatasource } from 'src/couchdb-datasources/daily-eccm-reports-user-comments.datasource'; + +export namespace DailyEccmUserComments { + export namespace Models { + export type Item = { + userId: number; + date: string; + comment: string; + }; + + export type CouchDbItem = Item & Timestamped & nano.DocumentGetResponse; + } +} + +@Injectable() +export class DailyEccmUserCommentsService { + constructor(private datasource: DailyEccmReportsUserCommentsDatasource) {} + + async setComment( + userId: number, + date: string, + comment: string, + ): Promise { + const key = this.getKey(userId, date); + const ds = await this.datasource.getDatasource(); + let existsItem: any; + try { + existsItem = await ds.get(key); + } catch (ex) { + existsItem = null; + } + const item: DailyEccmUserComments.Models.CouchDbItem = TimestampNowFill({ + userId: userId, + date: date, + comment: comment, + _id: key, + _rev: existsItem?._rev, + }); + await ds.insert(item); + } + + async loadComment(userId: number, date: string): Promise { + const key = this.getKey(userId, date); + const ds = await this.datasource.getDatasource(); + try { + const res: any = await ds.get(key); + return res.comment; + } catch (ex) { + return null; + } + } + + async loadComments( + userIds: number[], + date: string, + ): Promise> { + const query: nano.MangoQuery = { + limit: UNLIMITED, + selector: { + userId: { + $in: userIds, + }, + date: { + $eq: date, + }, + }, + }; + const ds = await this.datasource.getDatasource(); + const resp = await ds.find(query); + if (!resp || !resp.docs || resp.docs.length <= 0) { + return []; + } + const items = resp.docs; + const res: Record = {}; + for (const key in items) { + if (Object.prototype.hasOwnProperty.call(items, key)) { + const item = items[key]; + res[item.userId] = item.comment; + } + } + return res; + } + + private getKey(userId: number, date: string): string { + return `${date} - ${userId}`; + } +} diff --git a/src/reports/daily-eccm.report.service.ts b/src/reports/daily-eccm.report.service.ts index 45010e6..b17051e 100644 --- a/src/reports/daily-eccm.report.service.ts +++ b/src/reports/daily-eccm.report.service.ts @@ -14,6 +14,7 @@ import { DailyEccmReportsDatasource } from 'src/couchdb-datasources/daily-eccm-r import { Timestamped } from '@app/event-emitter/models/timestamped'; import { TimestampNowFill } from '@app/event-emitter/utils/timestamp-now-fill'; import { DateTime } from 'luxon'; +import { ISO_DATE_FORMAT } from 'src/consts/date-time.consts'; // eslint-disable-next-line @typescript-eslint/no-namespace export namespace DailyEccmReport { @@ -413,7 +414,7 @@ export class DailyEccmReportService { 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'); + nameValue = DateTime.now().toFormat(ISO_DATE_FORMAT); } return { from: fromDate.toISO(), diff --git a/src/telegram-bot/handlers/set-daily-eccm-user-comment.bot-handler.service.ts b/src/telegram-bot/handlers/set-daily-eccm-user-comment.bot-handler.service.ts new file mode 100644 index 0000000..ba8ab6f --- /dev/null +++ b/src/telegram-bot/handlers/set-daily-eccm-user-comment.bot-handler.service.ts @@ -0,0 +1,88 @@ +/* eslint-disable @typescript-eslint/no-namespace */ +import { TelegramBotService } from '../telegram-bot.service'; +import { TelegramBotHandlerInterface } from '../telegram.bot-handler.interface'; +import TelegramBot from 'node-telegram-bot-api'; +import { Injectable, Logger } from '@nestjs/common'; +import { UserMetaInfoService } from 'src/user-meta-info/user-meta-info.service'; +import { Subject } from 'rxjs'; +import { DateTime } from 'luxon'; +import { ISO_DATE_FORMAT } from 'src/consts/date-time.consts'; + +export namespace SetDailyEccmUserCommentBotHandler { + export namespace Models { + export type SetDailyEccmUserComment = { + userId: number; + date: string; + comment: string; + }; + } +} + +@Injectable() +export class SetDailyEccmUserCommentBotHandlerService + implements TelegramBotHandlerInterface +{ + private service: TelegramBotService; + private regexp = /\/set_daily_eccm_user_comment (.+)/; + private logger = new Logger(SetDailyEccmUserCommentBotHandlerService.name); + private dateParamRegexp = /^date=([\d\-]+) (.+)$/; + + $messages = + new Subject(); + + constructor(private userMetaInfoService: UserMetaInfoService) { + return; + } + + async init(service: TelegramBotService, bot: TelegramBot): Promise { + if (!this.service) { + this.service = service; + } + bot.onText(this.regexp, async (msg) => { + const userMetaInfo = await this.userMetaInfoService.findByTelegramId( + msg.chat.id, + ); + const redmineUserId = userMetaInfo.user_id; + const data = this.parseDate(msg.text); + this.logger.debug( + `Setting user comment for daily eccm ` + + `by redmineUserId = ${redmineUserId}, ` + + `full text - ${msg.text}, ` + + `parsed data - ${JSON.stringify(data)}`, + ); + this.$messages.next({ + userId: redmineUserId, + date: data.date, + comment: data.msg, + }); + return; + }); + } + + getHelpMsg(): string { + return ( + `/set_daily_eccm_user_comment ` + + `[дата=yyyy-MM-dd] <комментарий> ` + + `- дополнительный комментарий для дейли` + ); + } + + private parseDate(src: string): { date: string; msg: string } | null { + let msgWithoutCommand: any = src.match(this.regexp); + if (!msgWithoutCommand || !msgWithoutCommand[1]) { + return null; + } + msgWithoutCommand = msgWithoutCommand[1]; + const msgWithDate: any = msgWithoutCommand.match(this.dateParamRegexp); + let date: any = ''; + if (msgWithDate && msgWithDate[1] && msgWithDate[2]) { + date = msgWithDate[1]; + if (DateTime.fromFormat(date, ISO_DATE_FORMAT).isValid) { + msgWithoutCommand = msgWithDate[2]; + } else { + date = ''; + } + } + return { date: date, msg: msgWithoutCommand }; + } +} diff --git a/src/telegram-bot/telegram-bot.service.ts b/src/telegram-bot/telegram-bot.service.ts index 5c5dfe2..5f80b07 100644 --- a/src/telegram-bot/telegram-bot.service.ts +++ b/src/telegram-bot/telegram-bot.service.ts @@ -7,6 +7,7 @@ import axios from 'axios'; import { UserMetaInfoModel } from 'src/models/user-meta-info.model'; import { CurrentIssuesEccmBotHandlerService } from './handlers/current-issues-eccm.bot-handler.service'; import { TelegramBotHandlerInterface } from './telegram.bot-handler.interface'; +import { SetDailyEccmUserCommentBotHandlerService } from './handlers/set-daily-eccm-user-comment.bot-handler.service'; @Injectable() export class TelegramBotService { @@ -24,12 +25,14 @@ export class TelegramBotService { private usersService: UsersService, private configService: ConfigService, private currentIssuesBotHandlerService: CurrentIssuesEccmBotHandlerService, + private setDailyEccmUserCommentBotHandlerService: SetDailyEccmUserCommentBotHandlerService, ) { this.telegramBotToken = this.configService.get('telegramBotToken'); this.redminePublicUrlPrefix = this.configService.get('redmineUrlPublic'); this.initTelegramBot(); this.handlers.push(this.currentIssuesBotHandlerService); + this.handlers.push(this.setDailyEccmUserCommentBotHandlerService); } private async initTelegramBot(): Promise { @@ -47,7 +50,10 @@ export class TelegramBotService { this.bot.onText(/\/leave/, async (msg) => { await this.leave(msg); }); - this.currentIssuesBotHandlerService.init(this, this.bot); + for (let i = 0; i < this.handlers.length; i++) { + const handler = this.handlers[i]; + await handler.init(this, this.bot); + } } private async showHelpMessage(msg: TelegramBot.Message): Promise {