import axios from 'axios'; import { Injectable, Logger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { RedmineTypes } from '../models/redmine-types'; import { EnhancerService } from '../issue-enhancers/enhancer.service'; import { parse as csvParse } from 'csv/sync'; @Injectable() export class RedmineDataLoader { urlPrefix: string; redmineUrl: string; redmineToken: string; private logger = new Logger(RedmineDataLoader.name); constructor( private configService: ConfigService, private enhancerService: EnhancerService, ) { this.urlPrefix = this.configService.get('redmineUrlPrefix'); this.redmineUrl = this.configService.get('redmineUrlPublic'); this.redmineToken = this.configService.get('redmineToken'); } async loadIssues(issues: number[]): Promise<(RedmineTypes.Issue | null)[]> { const promises = issues.map((issue) => this.loadIssue(issue)); return Promise.all(promises); } async loadIssue( issueNumber: number, skipEnhancers = false, ): Promise { if (issueNumber <= 0) { return null; } const url = this.getIssueUrl(issueNumber); let resp; try { resp = await axios.get(url); } catch (ex) { const errorMsg = ex.message || 'Unknown error'; this.logger.error(`${errorMsg} for url = ${url}`); throw ex; } if (!resp || !resp.data || !resp.data.issue) { this.logger.error( `Failed to load issue from redmine, issueNumber = ${issueNumber}`, ); return null; } this.logger.debug( `Loaded issue, issueNumber = ${issueNumber}, subject = ${resp.data.issue.subject}`, ); if (skipEnhancers) return resp.data.issue; let enhancedIssue; try { enhancedIssue = await this.enhancerService.enhanceIssue(resp.data.issue); } catch (ex) { const errorMsg = ex.message || 'Unknown error'; this.logger.error(`${errorMsg} at enhance issue #${issueNumber}`); throw ex; } return enhancedIssue; } async loadUsers(users: number[]): Promise<(RedmineTypes.User | null)[]> { const promises = users.map((user) => this.loadUser(user)); return Promise.all(promises); } async loadUser(userNumber: number): Promise { if (typeof userNumber !== 'number' || userNumber <= 0) { this.logger.warn(`Invalid userNumber = ${userNumber}`); return null; } const url = this.getUserUrl(userNumber); let resp; try { resp = await axios.get(url); } catch (ex) { const errorMsg = ex.message || 'Unknown error'; this.logger.error(`${errorMsg} at load user by url ${url}`); return null; } if (!resp || !resp.data?.user) { this.logger.error( `Failed to load user from redmine, userNumber = ${userNumber}`, ); return null; } const user: RedmineTypes.User = resp.data.user; this.logger.debug( `Loaded user, userNumber = ${userNumber}, login = ${user.login}, firstname = ${user.firstname}, lastname = ${user.lastname}`, ); return RedmineTypes.CreateUser(user); } private getIssueUrl(issueNumber: number): string { if (typeof this.urlPrefix !== 'string' || this.urlPrefix.length === 0) { throw 'REDMINE_URL_PREFIX is undefined'; } return `${this.urlPrefix}/issues/${issueNumber}.json?include=children,journals,relations`; } private getUserUrl(userNumber: number): string { return `${this.urlPrefix}/users/${userNumber}.json`; } async loadCsv( urlQuery: string, csvParserParams?: any, ): Promise[]> { if (!csvParserParams) { csvParserParams = { delimiter: ';', quote: '"', columns: true, skip_empty_lines: true, }; } let resp; try { resp = await fetch(urlQuery, { headers: { 'X-Redmine-API-Key': this.redmineToken, }, }); } catch (err) { this.logger.error( `Failed to fetch data from Redmine by url ${urlQuery}: ${err}`, ); return null; } const rawData = await resp.text(); let res; try { res = csvParse(rawData, csvParserParams); } catch (ex) { this.logger.error( `Error at loading csv from redmine, query - ${urlQuery}, ex - ${ex}`, ); return null; } return res; } }