147 lines
4.3 KiB
TypeScript
147 lines
4.3 KiB
TypeScript
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<string>('redmineUrlPrefix');
|
|
this.redmineUrl = this.configService.get<string>('redmineUrlPublic');
|
|
this.redmineToken = this.configService.get<string>('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<RedmineTypes.Issue | null> {
|
|
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<RedmineTypes.User | null> {
|
|
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<Record<string, any>[]> {
|
|
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;
|
|
}
|
|
}
|