From ea8115cbee17a63540c660f258e19a8f1491bcaf Mon Sep 17 00:00:00 2001 From: Pavel Gnedov Date: Tue, 7 Nov 2023 02:26:54 +0700 Subject: [PATCH] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=BB=D0=B8=D0=BD=D1=82=D0=B5=D1=80=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/calendar/calendar.controller.ts | 104 ++++++------- .../src/calendar/calendar.service.ts | 143 ++++++++++-------- 2 files changed, 128 insertions(+), 119 deletions(-) diff --git a/libs/event-emitter/src/calendar/calendar.controller.ts b/libs/event-emitter/src/calendar/calendar.controller.ts index a62d6c0..df35ced 100644 --- a/libs/event-emitter/src/calendar/calendar.controller.ts +++ b/libs/event-emitter/src/calendar/calendar.controller.ts @@ -1,58 +1,54 @@ -import { Controller, Get, Inject, Logger, Param, Query } from "@nestjs/common"; -import { CalendarService } from "./calendar.service"; +import { Controller, Get, Inject, Logger, Param, Query } from '@nestjs/common'; +import { CalendarService } from './calendar.service'; import nano from 'nano'; -import { UNLIMITED } from "../consts/consts"; +import { UNLIMITED } from '../consts/consts'; @Controller('calendar') export class CalendarController { - private logger = new Logger(CalendarController.name); - - constructor( - @Inject('CALENDAR_SERVICE') - private calendarService: CalendarService - ) {} - - @Get() - async get(@Param('filter') filter: any): Promise { - return await this.calendarService.getICalData(filter); - } - - @Get('/simple') - async simple( - @Query('project') project?: string, - @Query('category') category?: string - ): Promise { - const andSection: any[] = [ - { - "closed_on": { - "$exists": false - } - } - ]; - if (project) { - andSection.push({ - "project.name": { - "$in": [ - project - ] - } - }); - } - if (category) { - andSection.push({ - "category.name": { - "$in": [ - category - ] - } - }); - } - const query: nano.MangoQuery = { - selector: { - "$and": andSection - }, - limit: UNLIMITED - }; - return await this.calendarService.getICalData(query); - } -} \ No newline at end of file + private logger = new Logger(CalendarController.name); + + constructor( + @Inject('CALENDAR_SERVICE') + private calendarService: CalendarService, + ) {} + + @Get() + async get(@Param('filter') filter: any): Promise { + return await this.calendarService.getICalData(filter); + } + + @Get('/simple') + async simple( + @Query('project') project?: string, + @Query('category') category?: string, + ): Promise { + const andSection: any[] = [ + { + closed_on: { + $exists: false, + }, + }, + ]; + if (project) { + andSection.push({ + 'project.name': { + $in: [project], + }, + }); + } + if (category) { + andSection.push({ + 'category.name': { + $in: [category], + }, + }); + } + const query: nano.MangoQuery = { + selector: { + $and: andSection, + }, + limit: UNLIMITED, + }; + return await this.calendarService.getICalData(query); + } +} diff --git a/libs/event-emitter/src/calendar/calendar.service.ts b/libs/event-emitter/src/calendar/calendar.service.ts index 7f0833c..7011ed0 100644 --- a/libs/event-emitter/src/calendar/calendar.service.ts +++ b/libs/event-emitter/src/calendar/calendar.service.ts @@ -1,8 +1,8 @@ -import { Injectable, Logger } from "@nestjs/common"; -import { CalendarEvent } from "../models/calendar-event"; -import { RedmineTypes } from "../models/redmine-types"; +import { Injectable } from '@nestjs/common'; +import { CalendarEvent } from '../models/calendar-event'; +import { RedmineTypes } from '../models/redmine-types'; import * as Luxon from 'luxon'; -import { IssuesService } from "../issues/issues.service"; +import { IssuesService } from '../issues/issues.service'; import { randomUUID } from 'crypto'; /* @@ -36,57 +36,70 @@ END:VCALENDAR */ export type IssueAndEvent = { - issue: RedmineTypes.ExtendedIssue; - event: CalendarEvent; + issue: RedmineTypes.ExtendedIssue; + event: CalendarEvent; }; @Injectable() export class CalendarService { - private interval = 30 * 24 * 60 * 60 * 1000; // 30 days - - constructor(public calendarEventsKey: string, private issuesService: IssuesService) {} - - async getICalData(filter: any): Promise { - const issues = await this.issuesService.find(filter); - const actualEvents = this.getActualEvents(issues); - const formattedEvents = actualEvents.map((event) => { - return this.generateICalendarEvent(event.issue, event.event); - }).filter((event) => { - return !!event; - }); - const res = this.generateICalendar(formattedEvents); - return res; - } - - private getActualEvents(issues: RedmineTypes.ExtendedIssue[]): IssueAndEvent[] { - const res: IssueAndEvent[] = []; - for (let i = 0; i < issues.length; i++) { - const issue = issues[i]; - if (!issue[this.calendarEventsKey] || issue[this.calendarEventsKey].length <= 0) { - continue; - } - const events = issue[this.calendarEventsKey]; - for (let j = 0; j < events.length; j++) { - const event = events[j]; - if (this.actualEvent(event)) res.push({event: event, issue: issue}); - } - } - return res; - } - - private actualEvent(event: CalendarEvent): boolean { - const now = Luxon.DateTime.now().toMillis(); - const from = now - this.interval; - const to = now + this.interval; - return Boolean( - (from <= event.fromTimestamp && event.fromTimestamp <= to) || - (from <= event.toTimestamp && event.toTimestamp <= to) - ); - } - - private generateICalendarEvent(issue: RedmineTypes.Issue, data: CalendarEvent): string | null { - if (!data) return null; - return `BEGIN:VEVENT + private interval = 30 * 24 * 60 * 60 * 1000; // 30 days + + constructor( + public calendarEventsKey: string, + private issuesService: IssuesService, + ) {} + + async getICalData(filter: any): Promise { + const issues = await this.issuesService.find(filter); + const actualEvents = this.getActualEvents(issues); + const formattedEvents = actualEvents + .map((event) => { + return this.generateICalendarEvent(event.issue, event.event); + }) + .filter((event) => { + return !!event; + }); + const res = this.generateICalendar(formattedEvents); + return res; + } + + private getActualEvents( + issues: RedmineTypes.ExtendedIssue[], + ): IssueAndEvent[] { + const res: IssueAndEvent[] = []; + for (let i = 0; i < issues.length; i++) { + const issue = issues[i]; + if ( + !issue[this.calendarEventsKey] || + issue[this.calendarEventsKey].length <= 0 + ) { + continue; + } + const events = issue[this.calendarEventsKey]; + for (let j = 0; j < events.length; j++) { + const event = events[j]; + if (this.actualEvent(event)) res.push({ event: event, issue: issue }); + } + } + return res; + } + + private actualEvent(event: CalendarEvent): boolean { + const now = Luxon.DateTime.now().toMillis(); + const from = now - this.interval; + const to = now + this.interval; + return Boolean( + (from <= event.fromTimestamp && event.fromTimestamp <= to) || + (from <= event.toTimestamp && event.toTimestamp <= to), + ); + } + + private generateICalendarEvent( + issue: RedmineTypes.Issue, + data: CalendarEvent, + ): string | null { + if (!data) return null; + return `BEGIN:VEVENT UID:${randomUUID()}@example.com DTSTAMP:${this.formatTimestamp(data.fromTimestamp, data.fullDay)} ORGANIZER;CN=John Doe:MAILTO:john.doe@example.com @@ -94,20 +107,20 @@ DTSTART:${this.formatTimestamp(data.fromTimestamp, data.fullDay)} DTEND:${this.formatTimestamp(data.toTimestamp, data.fullDay)} SUMMARY:#${issue.id} - ${data.description} - ${issue.subject} END:VEVENT`; - } - - private formatTimestamp(timestamp: number, fullDay?: boolean): string { - let format: string = fullDay ? "yyyyMMdd" : "yyyyMMdd'T'HHmmss'Z'"; - let datetime = Luxon.DateTime.fromMillis(timestamp); - if (!fullDay) datetime = datetime.setZone('utc'); - return datetime.toFormat(format); - } - - private generateICalendar(events: string[]): string { - return `BEGIN:VCALENDAR + } + + private formatTimestamp(timestamp: number, fullDay?: boolean): string { + const format: string = fullDay ? 'yyyyMMdd' : "yyyyMMdd'T'HHmmss'Z'"; + let datetime = Luxon.DateTime.fromMillis(timestamp); + if (!fullDay) datetime = datetime.setZone('utc'); + return datetime.toFormat(format); + } + + private generateICalendar(events: string[]): string { + return `BEGIN:VCALENDAR VERSION:2.0 PRODID:-//Example Corp.//CalDAV Client//EN -${events.join("\n")} +${events.join('\n')} END:VCALENDAR`; - } -} \ No newline at end of file + } +}