Добавлена поддержка работы с юзерами
This commit is contained in:
parent
60b0bdbcd4
commit
d20b05d760
17 changed files with 54 additions and 170 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { RedmineTypes } from 'libs/redmine-types';
|
||||
import { RedmineTypes } from '../models/redmine-types';
|
||||
import nano from 'nano';
|
||||
import { Subject } from 'rxjs';
|
||||
import { Issues } from '../couchdb-datasources/issues';
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
/// <reference types="typescript" />
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-namespace-keyword
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-namespace-keyword, @typescript-eslint/no-namespace
|
||||
export module RedmineTypes {
|
||||
export type IdAndName = {
|
||||
id: number;
|
||||
|
|
@ -51,7 +49,7 @@ export module RedmineTypes {
|
|||
journals?: Journal[];
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-namespace-keyword
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-namespace-keyword, @typescript-eslint/no-namespace
|
||||
export module Unknown {
|
||||
export const num = -1;
|
||||
export const str = '';
|
||||
|
|
@ -83,6 +81,7 @@ export module RedmineTypes {
|
|||
|
||||
export const user: User = {
|
||||
id: num,
|
||||
login: unknownName,
|
||||
firstname: unknownName,
|
||||
lastname: unknownName,
|
||||
mail: str,
|
||||
|
|
@ -96,4 +95,14 @@ export module RedmineTypes {
|
|||
lastname: string;
|
||||
mail: string;
|
||||
};
|
||||
|
||||
export function ExtractUser(obj: User): User {
|
||||
return {
|
||||
id: obj.id,
|
||||
login: obj.login,
|
||||
firstname: obj.firstname,
|
||||
lastname: obj.lastname,
|
||||
mail: obj.mail,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"name": "redmine-types",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Pavel Gnedov",
|
||||
"license": "MIT"
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { RedmineTypes } from 'libs/redmine-types';
|
||||
import { RedmineTypes } from './redmine-types';
|
||||
|
||||
// TODO: Переименовать в IssueCacheSaveResponse
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export class RedmineDataLoader {
|
|||
this.logger.debug(
|
||||
`Loaded user, userNumber = ${userNumber}, login = ${user.login}, firstname = ${user.firstname}, lastname = ${user.lastname}`,
|
||||
);
|
||||
return user;
|
||||
return RedmineTypes.ExtractUser(user);
|
||||
}
|
||||
|
||||
private getIssueUrl(issueNumber: number): string {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export class RedmineUserCacheWriterService {
|
|||
prevUser = null;
|
||||
}
|
||||
const newUser: nano.DocumentGetResponse & RedmineTypes.User & Timestamped =
|
||||
TimestampNowFill({ ...(user as any) });
|
||||
TimestampNowFill({ ...(user as any), _id: String(id) });
|
||||
if (prevUser) {
|
||||
newUser._rev = prevUser._rev;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export class UsersController {
|
|||
return await this.usersService.getUser(id);
|
||||
}
|
||||
|
||||
@Get(':id.json')
|
||||
@Get(':id/json')
|
||||
async getUserLikeRedmine(
|
||||
@Param('id') id: number,
|
||||
): Promise<{ user: RedmineTypes.User }> {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,21 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { RedmineTypes } from '../models/redmine-types';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Timestamped } from '../models/timestamped';
|
||||
import { Users } from '../couchdb-datasources/users';
|
||||
import { RedmineDataLoader } from '../redmine-data-loader/redmine-data-loader';
|
||||
import { TimestampNowFill } from '../utils/timestamp-now-fill';
|
||||
import { RedmineUserCacheWriterService } from '../user-cache-writer/user-cache-writer.service';
|
||||
import { TimestampIsTimeouted } from '../utils/timestamp-is-timeouted';
|
||||
import { RedmineTypes } from '../models/redmine-types';
|
||||
import { MemoryCache } from '../utils/memory-cache';
|
||||
|
||||
export const USER_MEMORY_CACHE_LIFETIME = 24 * 60 * 60 * 1000;
|
||||
const USER_MEMORY_CACHE_AUTOCLEAN_INTERVAL = 1000 * 60 * 5;
|
||||
|
||||
@Injectable()
|
||||
export class UsersService {
|
||||
private memoryCache: Record<number, RedmineTypes.User & Timestamped> = {};
|
||||
private logger = new Logger(UsersService.name);
|
||||
private memoryCache = new MemoryCache<number, RedmineTypes.User>(
|
||||
USER_MEMORY_CACHE_LIFETIME,
|
||||
USER_MEMORY_CACHE_AUTOCLEAN_INTERVAL,
|
||||
);
|
||||
|
||||
constructor(
|
||||
private users: Users,
|
||||
|
|
@ -26,8 +30,8 @@ export class UsersService {
|
|||
}
|
||||
const userFromCache = await this.getUserFromCache(userId);
|
||||
if (userFromCache) {
|
||||
this.memoryCache[userId] = TimestampNowFill({ ...userFromCache });
|
||||
return this.memoryCache[userId];
|
||||
this.memoryCache.set(userId, userFromCache);
|
||||
return userFromCache;
|
||||
}
|
||||
let userFromRedmine = await this.getUserFromRedmine(userId);
|
||||
if (userFromRedmine) {
|
||||
|
|
@ -35,13 +39,18 @@ export class UsersService {
|
|||
userFromRedmine,
|
||||
);
|
||||
}
|
||||
const unknownUser = TimestampNowFill({ ...RedmineTypes.Unknown.user });
|
||||
this.memoryCache[userId] = (userFromRedmine || unknownUser) as any;
|
||||
return this.memoryCache[userId];
|
||||
return this.memoryCache.set(
|
||||
userId,
|
||||
userFromRedmine || RedmineTypes.Unknown.user,
|
||||
);
|
||||
}
|
||||
|
||||
async getUserFromRedmine(userId: number): Promise<RedmineTypes.User | null> {
|
||||
return await this.redmineDataLoader.loadUser(userId);
|
||||
const user = await this.redmineDataLoader.loadUser(userId);
|
||||
this.logger.debug(
|
||||
`Get user from redmine with userId = ${userId}, login = ${user.login}`,
|
||||
);
|
||||
return user;
|
||||
}
|
||||
|
||||
async getUserFromCache(
|
||||
|
|
@ -49,22 +58,23 @@ export class UsersService {
|
|||
): Promise<(RedmineTypes.User & Timestamped) | null> {
|
||||
const usersDb = await this.users.getDatasource();
|
||||
try {
|
||||
return (await usersDb.get(String(userId))) as any;
|
||||
const user = (await usersDb.get(String(userId))) as any;
|
||||
this.logger.debug(
|
||||
`Get user from couchdb with userId = ${userId}, login = ${user.login}`,
|
||||
);
|
||||
return user;
|
||||
} catch (ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
getUserFromMemoryCache(userId: number): RedmineTypes.User | null {
|
||||
if (
|
||||
this.memoryCache[userId] &&
|
||||
!TimestampIsTimeouted(
|
||||
this.memoryCache[userId],
|
||||
USER_MEMORY_CACHE_LIFETIME,
|
||||
)
|
||||
) {
|
||||
return this.memoryCache[userId];
|
||||
const user = this.memoryCache.get(userId);
|
||||
if (user) {
|
||||
this.logger.debug(
|
||||
`Get user from memory cache with userId = ${userId}, login = ${user.login}`,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export class MemoryCache<K, T> {
|
|||
}
|
||||
}
|
||||
|
||||
get(key: K): T | null {
|
||||
get(key: K): (T & Timestamped) | null {
|
||||
const k = key as any;
|
||||
if (this.memoryCache[k]) {
|
||||
if (TimestampIsTimeouted(this.memoryCache[k], this.timeout)) {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@ export function TimestampIsTimeouted(
|
|||
obj: Timestamped,
|
||||
timeout: number,
|
||||
): boolean {
|
||||
const now = new Date().getDate();
|
||||
const now = new Date().getTime();
|
||||
return obj.timestamp__ < now - timeout;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Timestamped } from '../models/timestamped';
|
||||
|
||||
export function TimestampNowFill<T>(obj: T): T & Timestamped {
|
||||
const now = new Date().getDate();
|
||||
const now = new Date().getTime();
|
||||
return { ...obj, timestamp__: now };
|
||||
}
|
||||
|
|
|
|||
99
libs/redmine-types/index.d.ts
vendored
99
libs/redmine-types/index.d.ts
vendored
|
|
@ -1,99 +0,0 @@
|
|||
/// <reference types="typescript" />
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-namespace-keyword
|
||||
export module RedmineTypes {
|
||||
export type IdAndName = {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type CustomField = {
|
||||
id: number;
|
||||
name: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export type JournalDetail = {
|
||||
property: string;
|
||||
name: string;
|
||||
old_value?: string;
|
||||
new_value?: string;
|
||||
};
|
||||
|
||||
export type Journal = {
|
||||
id: number;
|
||||
user: IdAndName;
|
||||
notes?: string;
|
||||
created_on: string;
|
||||
details?: JournalDetail[];
|
||||
};
|
||||
|
||||
export type Issue = {
|
||||
id: number;
|
||||
project: IdAndName;
|
||||
tracker: IdAndName;
|
||||
status: IdAndName;
|
||||
priority: IdAndName;
|
||||
author: IdAndName;
|
||||
category: IdAndName;
|
||||
fixed_version: IdAndName;
|
||||
subject: string;
|
||||
description: string;
|
||||
start_date: string;
|
||||
done_ratio: number;
|
||||
spent_hours: number;
|
||||
total_spent_hours: number;
|
||||
custom_fields: CustomField[];
|
||||
created_on: string;
|
||||
updated_on?: string;
|
||||
closed_on?: string;
|
||||
relations?: Record<string, any>[];
|
||||
journals?: Journal[];
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-namespace-keyword
|
||||
export module Unknown {
|
||||
export const num = -1;
|
||||
export const str = '';
|
||||
export const idAndName: IdAndName = {
|
||||
id: -1,
|
||||
name: str,
|
||||
};
|
||||
export const unknownName = 'Unknown';
|
||||
export const subject = 'Unknown';
|
||||
export const date = '1970-01-01T00:00:00Z';
|
||||
export const issue: Issue = {
|
||||
id: num,
|
||||
project: idAndName,
|
||||
tracker: idAndName,
|
||||
status: idAndName,
|
||||
priority: idAndName,
|
||||
author: idAndName,
|
||||
category: idAndName,
|
||||
fixed_version: idAndName,
|
||||
subject: subject,
|
||||
description: str,
|
||||
start_date: date,
|
||||
done_ratio: num,
|
||||
spent_hours: num,
|
||||
total_spent_hours: num,
|
||||
custom_fields: [],
|
||||
created_on: date,
|
||||
};
|
||||
|
||||
export const user: User = {
|
||||
id: num,
|
||||
firstname: unknownName,
|
||||
lastname: unknownName,
|
||||
mail: str,
|
||||
};
|
||||
}
|
||||
|
||||
export type User = {
|
||||
id: number;
|
||||
login: string;
|
||||
firstname: string;
|
||||
lastname: string;
|
||||
mail: string;
|
||||
};
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"name": "redmine-types",
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Pavel Gnedov",
|
||||
"license": "MIT"
|
||||
}
|
||||
11
package-lock.json
generated
11
package-lock.json
generated
|
|
@ -8,9 +8,6 @@
|
|||
"name": "eltex-redmine-helper-2",
|
||||
"version": "0.0.1",
|
||||
"license": "UNLICENSED",
|
||||
"workspaces": [
|
||||
"libs/redmine-types"
|
||||
],
|
||||
"dependencies": {
|
||||
"@nestjs/common": "^8.0.0",
|
||||
"@nestjs/config": "^2.0.0",
|
||||
|
|
@ -54,6 +51,7 @@
|
|||
},
|
||||
"libs/redmine-types": {
|
||||
"version": "0.1.0",
|
||||
"extraneous": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
|
|
@ -6595,10 +6593,6 @@
|
|||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/redmine-types": {
|
||||
"resolved": "libs/redmine-types",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/reflect-metadata": {
|
||||
"version": "0.1.13",
|
||||
"license": "Apache-2.0"
|
||||
|
|
@ -12384,9 +12378,6 @@
|
|||
"resolve": "^1.1.6"
|
||||
}
|
||||
},
|
||||
"redmine-types": {
|
||||
"version": "file:libs/redmine-types"
|
||||
},
|
||||
"reflect-metadata": {
|
||||
"version": "0.1.13"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -83,8 +83,5 @@
|
|||
"moduleNameMapper": {
|
||||
"^@app/event-emitter(|/.*)$": "<rootDir>/libs/event-emitter/src/$1"
|
||||
}
|
||||
},
|
||||
"workspaces": [
|
||||
"libs/redmine-types"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue