feat(api): tanti moduli
This commit is contained in:
parent
aa07f3d4f9
commit
7970ccfc0e
apps/ebitemp-api/src
app
features/anagrafica
anagrafica.controller.spec.tsanagrafica.controller.tsanagrafica.module.tsanagrafica.service.spec.tsanagrafica.service.ts
main.tsmodules
auth
auth.config.tsauth.controller.tsauth.dto.tsauth.module.tsauth.service.tsauthenticated-user.decorator.ts
constants
strategies/jwt
jwt-access-token-auth.strategy.tsjwt-access-token.module.tsjwt-auth.guard.tsjwt-refresh-token-auth.strategy.tsjwt-refresh-token.module.tsjwt-refresh.guard.ts
users
cls
config
database
connections
ebitemp-api
database.config.tsdatabase.constants.tsdatabase.module.ts
entities
accounts.entity.tsapi_client.entity.tsapi_client_invocazioni.entity.tsapi_endpoint.entity.tsapi_endpoint_invocazioni.entity.tsindex.tsprofili.entity.tsruoli.entity.tstipi_jobs.entity.ts
index.tsoceano
database.config.tsdatabase.constants.tsdatabase.module.ts
entities
API_CAP_Luoghi.entity.tsAPI_Luoghi.entity.tsAPI_Luoghi_Estero.entity.tsallegati.entity.tscontratto.entity.tsente.entity.tsformeAssistenza.entity.tsgruppo.entity.tsindex.tsmissioni.entity.tssocio.entity.tssocioContratto.entity.tssocioFormeAssistenza.entity.tssportelli.entity.ts
index.tsutils
enumify
file-transactions
health
keyv
logger
request-logging
schedule
validation
tools/typeorm/src/generators/typeorm-model-generator
@ -1,6 +1,6 @@
|
||||
import { Controller, Get, VERSION_NEUTRAL } from '@nestjs/common';
|
||||
import { AppService } from './app.service';
|
||||
import { Public } from './modules/auth/strategies/jwt/jwt-auth.guard';
|
||||
import { Public } from '../modules/auth/strategies/jwt/jwt-auth.guard';
|
||||
|
||||
@Public()
|
||||
@Controller({ version: VERSION_NEUTRAL })
|
||||
|
@ -2,27 +2,34 @@ import { Module } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
import { AppAuthModule } from './modules/auth/auth.module';
|
||||
import { AppConfigModule } from './modules/config/config.module';
|
||||
import { AppDatabaseModule } from './modules/database/database.module';
|
||||
import { EnumifyModule } from './modules/enumify/enumify.module';
|
||||
import { AppFileTransactionsModule } from './modules/file-transactions/file-transactions.module';
|
||||
import { AppHealthModule } from './modules/health/health.module';
|
||||
import { AppLoggerModule } from './modules/logger/logger.module';
|
||||
import { AppScheduleModule } from './modules/schedule/schedule.module';
|
||||
import { AppValidationModule } from './modules/validation/validation.module';
|
||||
import { AppAuthModule } from '../modules/auth/auth.module';
|
||||
import { AppClsModule } from '../modules/cls/cls.module';
|
||||
import { AppConfigModule } from '../modules/config/config.module';
|
||||
import { AppDatabaseModule } from '../modules/database/database.module';
|
||||
import { EnumifyModule } from '../modules/enumify/enumify.module';
|
||||
import { AppFileTransactionsModule } from '../modules/file-transactions/file-transactions.module';
|
||||
import { AppHealthModule } from '../modules/health/health.module';
|
||||
import { AppLoggerModule } from '../modules/logger/logger.module';
|
||||
import { AppRequestLoggingModule } from '../modules/request-logging/request-logging.module';
|
||||
import { AppScheduleModule } from '../modules/schedule/schedule.module';
|
||||
import { AppValidationModule } from '../modules/validation/validation.module';
|
||||
|
||||
import { AnagraficaModule } from '../features/anagrafica/anagrafica.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
AppConfigModule,
|
||||
AppLoggerModule,
|
||||
AppDatabaseModule,
|
||||
AppClsModule,
|
||||
AppHealthModule,
|
||||
AppFileTransactionsModule,
|
||||
AppScheduleModule,
|
||||
AppValidationModule,
|
||||
AppAuthModule,
|
||||
AppRequestLoggingModule,
|
||||
EnumifyModule,
|
||||
AnagraficaModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
|
@ -1,4 +0,0 @@
|
||||
export * from './accounts.entity';
|
||||
export * from './profili.entity';
|
||||
export * from './ruoli.entity';
|
||||
export * from './tipi_jobs.entity';
|
@ -0,0 +1,20 @@
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { AnagraficaController } from './anagrafica.controller';
|
||||
import { AnagraficaService } from './anagrafica.service';
|
||||
|
||||
describe('AnagraficaController', () => {
|
||||
let controller: AnagraficaController;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
providers: [AnagraficaService],
|
||||
controllers: [AnagraficaController],
|
||||
}).compile();
|
||||
|
||||
controller = module.get(AnagraficaController);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(controller).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,28 @@
|
||||
import { Controller, Get, Param, UseInterceptors } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiOperation, ApiTags } from '@nestjs/swagger';
|
||||
import { ApiOkPaginatedResponse, ApiPaginationQuery, Paginate, PaginateQuery } from 'nestjs-paginate';
|
||||
import { SocioEntity } from '../../modules/database/connections/oceano';
|
||||
import { AnagraficaService } from './anagrafica.service';
|
||||
import { RequestLoggerInterceptor } from '../../modules/request-logging/request-logger.interceptor';
|
||||
|
||||
@UseInterceptors(RequestLoggerInterceptor)
|
||||
@ApiBearerAuth()
|
||||
@ApiTags('anagrafica')
|
||||
@Controller('anagrafiche')
|
||||
export class AnagraficaController {
|
||||
constructor(private anagraficaService: AnagraficaService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: 'Find all Anagrafiche' })
|
||||
@ApiPaginationQuery(AnagraficaService.findPaginateConfig())
|
||||
@ApiOkPaginatedResponse(SocioEntity, AnagraficaService.findPaginateConfig())
|
||||
async getAnagrafiche(@Paginate() query: PaginateQuery) {
|
||||
return this.anagraficaService.getAnagrafiche(query);
|
||||
}
|
||||
|
||||
@Get('id/:id')
|
||||
@ApiOperation({ summary: 'Get one Anagrafica by id' })
|
||||
async getOneAnagraficaById(@Param('id') id: string) {
|
||||
return this.anagraficaService.getOneAnagraficaById(id);
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { SocioEntity } from '../../modules/database/connections/oceano';
|
||||
import { OCEANO_DATASOURCE } from '../../modules/database/connections/oceano/database.constants';
|
||||
import { AnagraficaController } from './anagrafica.controller';
|
||||
import { AnagraficaService } from './anagrafica.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([SocioEntity], OCEANO_DATASOURCE)],
|
||||
controllers: [AnagraficaController],
|
||||
providers: [AnagraficaService],
|
||||
exports: [AnagraficaService],
|
||||
})
|
||||
export class AnagraficaModule {}
|
@ -0,0 +1,18 @@
|
||||
import { Test } from '@nestjs/testing';
|
||||
import { AnagraficaService } from './anagrafica.service';
|
||||
|
||||
describe('AnagraficaService', () => {
|
||||
let service: AnagraficaService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module = await Test.createTestingModule({
|
||||
providers: [AnagraficaService],
|
||||
}).compile();
|
||||
|
||||
service = module.get(AnagraficaService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,42 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { paginate, PaginateConfig, PaginateQuery } from 'nestjs-paginate';
|
||||
import { Repository } from 'typeorm';
|
||||
import { SocioEntity } from '../../modules/database/connections/oceano';
|
||||
import { OCEANO_DATASOURCE } from '../../modules/database/connections/oceano/database.constants';
|
||||
|
||||
@Injectable()
|
||||
export class AnagraficaService {
|
||||
private readonly logger = new Logger(AnagraficaService.name);
|
||||
|
||||
static readonly findPaginateConfig = (): PaginateConfig<SocioEntity> => ({
|
||||
relations: {},
|
||||
sortableColumns: ['codiceSocio', 'idNucleo', 'idPersona'],
|
||||
searchableColumns: [],
|
||||
defaultSortBy: [['codiceSocio', 'ASC']],
|
||||
defaultLimit: 50,
|
||||
maxLimit: -1,
|
||||
filterableColumns: {},
|
||||
relativePath: true,
|
||||
where: {
|
||||
codiceGruppo: '6',
|
||||
},
|
||||
});
|
||||
|
||||
constructor(
|
||||
@InjectRepository(SocioEntity, OCEANO_DATASOURCE) private readonly sociRepository: Repository<SocioEntity>
|
||||
) {}
|
||||
|
||||
async getAnagrafiche(query: PaginateQuery) {
|
||||
const config = AnagraficaService.findPaginateConfig();
|
||||
query.limit ??= -1;
|
||||
|
||||
return await paginate(query, this.sociRepository, config);
|
||||
}
|
||||
|
||||
async getOneAnagraficaById(idAnagrafica: string) {
|
||||
return await this.sociRepository.findOne({
|
||||
where: { ...AnagraficaService.findPaginateConfig().where, codiceSocio: idAnagrafica },
|
||||
});
|
||||
}
|
||||
}
|
@ -6,10 +6,10 @@ import { Logger as PinoLogger } from 'nestjs-pino';
|
||||
import { DocumentBuilder, SwaggerDocumentOptions, SwaggerModule } from '@nestjs/swagger';
|
||||
import { flow } from 'lodash';
|
||||
import { AppModule } from './app/app.module';
|
||||
import { patchPublicDecoratorSupport } from './app/modules/auth/strategies/jwt/jwt-auth.guard';
|
||||
import { LocalConfig, localConfig } from './app/modules/config/local.config';
|
||||
import { patchTypeOrm } from './app/modules/database/utils/typeorm-patch';
|
||||
import { patchNestjsSwagger } from './app/modules/validation/utils/nestjs-swagger-patches';
|
||||
import { patchPublicDecoratorSupport } from './modules/auth/strategies/jwt/jwt-auth.guard';
|
||||
import { LocalConfig, localConfig } from './modules/config/local.config';
|
||||
import { patchTypeOrm } from './modules/database/utils/typeorm-patch';
|
||||
import { patchNestjsSwagger } from './modules/validation/utils/nestjs-swagger-patches';
|
||||
|
||||
async function bootstrap() {
|
||||
await patchTypeOrm();
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { Body, Controller, HttpCode, HttpStatus, Post, UnauthorizedException, UseGuards } from '@nestjs/common';
|
||||
import { Body, Controller, HttpCode, HttpStatus, Post, UseGuards } from '@nestjs/common';
|
||||
import { ApiBearerAuth } from '@nestjs/swagger';
|
||||
import { AccountsEntity } from '../database/connections/ebitemp-api/entities';
|
||||
import { LoginDto, LoginResDto } from './auth.dto';
|
||||
import { AuthService } from './auth.service';
|
||||
import { AuthenticatedUser } from './authenticated-user.decorator';
|
||||
import { Public } from './strategies/jwt/jwt-auth.guard';
|
||||
import { JwtRefreshGuard } from './strategies/jwt/jwt-refresh.guard';
|
||||
|
||||
@ApiBearerAuth()
|
||||
@Controller('auth')
|
||||
@ -17,21 +18,14 @@ export class AuthController {
|
||||
async logIn(@Body() body: LoginDto): Promise<LoginResDto> {
|
||||
const { username, password } = body;
|
||||
const user = await this.authService.getAuthenticatedUser(username, password);
|
||||
|
||||
const { accessToken, refreshToken } = await this.authService.signJwts(user);
|
||||
return {
|
||||
accessToken: accessToken,
|
||||
refreshToken: refreshToken,
|
||||
};
|
||||
return await this.authService.signJwts(user);
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@UseGuards(JwtRefreshGuard)
|
||||
@Public(true)
|
||||
@Post('refresh')
|
||||
async refresh(@AuthenticatedUser() user: AccountsEntity): Promise<LoginResDto> {
|
||||
const { accessToken, refreshToken } = await this.authService.signJwts(user);
|
||||
return {
|
||||
accessToken: accessToken,
|
||||
refreshToken: refreshToken,
|
||||
};
|
||||
return await this.authService.signJwts(user);
|
||||
}
|
||||
}
|
@ -11,7 +11,9 @@ export class LoginDto extends createZodDto(loginSchema) {}
|
||||
|
||||
export const loginResSchema = z.object({
|
||||
accessToken: z.string().jwt(),
|
||||
accessTokenExp: z.string().datetime(),
|
||||
refreshToken: z.string().jwt(),
|
||||
refreshTokenExp: z.string().datetime()
|
||||
})
|
||||
export type LoginRes = z.infer<typeof loginResSchema>;
|
||||
export class LoginResDto extends createZodDto(loginResSchema) {}
|
@ -11,10 +11,11 @@ import { JwtAuthGuard } from './strategies/jwt/jwt-auth.guard';
|
||||
import { JwtRefreshTokenAuthStrategy } from './strategies/jwt/jwt-refresh-token-auth.strategy';
|
||||
import { JwtRefreshTokenModule } from './strategies/jwt/jwt-refresh-token.module';
|
||||
import { UsersAuthModule } from './users/users.module';
|
||||
import { EBITEMP_API_DATASOURCE } from '../database/connections/ebitemp-api/database.constants';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([AccountsEntity]),
|
||||
TypeOrmModule.forFeature([AccountsEntity], EBITEMP_API_DATASOURCE),
|
||||
JwtAccessTokenModule,
|
||||
JwtRefreshTokenModule,
|
||||
PassportModule,
|
@ -1,8 +1,10 @@
|
||||
import { Inject, Injectable, Logger, UnauthorizedException } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { JwtService, JwtSignOptions } from '@nestjs/jwt';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
import dayjs from 'dayjs';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { AccountsEntity } from '../database/connections/ebitemp-api/entities';
|
||||
import { TokenPayload } from './constants/token-payload.interface';
|
||||
import { ACCESS_TOKEN_JWT_SERVICE } from './strategies/jwt/jwt-access-token.module';
|
||||
import { REFRESH_TOKEN_JWT_SERVICE } from './strategies/jwt/jwt-refresh-token.module';
|
||||
import { UsersAuthService } from './users/users-auth.service';
|
||||
@ -17,15 +19,31 @@ export class AuthService {
|
||||
@Inject(REFRESH_TOKEN_JWT_SERVICE) private readonly refreshTokenJwtService: JwtService
|
||||
) {}
|
||||
|
||||
public async signJwts(user: AccountsEntity) {
|
||||
const payload = { username: user.username, sub: user.id };
|
||||
public async signJwts(account: AccountsEntity) {
|
||||
const { token: accessToken, exp: accessTokenExp } = await this.getAccessToken(account);
|
||||
const { token: refreshToken, exp: refreshTokenExp } = await this.getRefreshToken(account);
|
||||
|
||||
const accessToken = await this.accessTokenJwtService.signAsync(payload);
|
||||
return {
|
||||
accessToken: accessToken,
|
||||
accessTokenExp: dayjs.unix(accessTokenExp).toJSON(),
|
||||
refreshToken: refreshToken,
|
||||
refreshTokenExp: dayjs.unix(refreshTokenExp).toJSON(),
|
||||
};
|
||||
}
|
||||
|
||||
const refreshToken = await this.refreshTokenJwtService.signAsync(payload);
|
||||
await this.usersAuthService.setCurrentRefreshTokenHash(user.id, refreshToken);
|
||||
public async getAccessToken(account: AccountsEntity, options?: JwtSignOptions) {
|
||||
const payload: TokenPayload = { username: account.username, sub: account.id };
|
||||
const token = await this.accessTokenJwtService.signAsync(payload, options);
|
||||
const exp = this.accessTokenJwtService.decode(token).exp;
|
||||
return { token, exp };
|
||||
}
|
||||
|
||||
return { accessToken, refreshToken };
|
||||
public async getRefreshToken(account: AccountsEntity, options?: JwtSignOptions) {
|
||||
const payload: TokenPayload = { username: account.username, sub: account.id };
|
||||
const token = await this.refreshTokenJwtService.signAsync(payload, options);
|
||||
await this.usersAuthService.setCurrentRefreshTokenHash(account.id, token);
|
||||
const exp = this.refreshTokenJwtService.decode(token).exp;
|
||||
return { token, exp };
|
||||
}
|
||||
|
||||
public async getAuthenticatedUser(username: string, password: string): Promise<AccountsEntity> {
|
@ -1,4 +1,5 @@
|
||||
export interface TokenPayload {
|
||||
username: string;
|
||||
sub: number;
|
||||
iat?: number;
|
||||
exp?: number;
|
@ -1,14 +1,20 @@
|
||||
import { Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { FastifyRequest } from 'fastify';
|
||||
import { ClsService } from 'nestjs-cls';
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
import { AppClsStore } from '../../../cls/cls.interface';
|
||||
import { AuthConfig, authConfig } from '../../auth.config';
|
||||
import { TokenPayload } from '../../constants/token-payload.interface';
|
||||
import { UsersAuthService } from '../../users/users-auth.service';
|
||||
import { FastifyRequest } from 'fastify';
|
||||
|
||||
@Injectable()
|
||||
export class JwtAccessTokenAuthStrategy extends PassportStrategy(Strategy, 'jwt-access-token') {
|
||||
constructor(@Inject(authConfig.KEY) authConfig: AuthConfig, private readonly userAuthService: UsersAuthService) {
|
||||
constructor(
|
||||
@Inject(authConfig.KEY) authConfig: AuthConfig,
|
||||
private readonly cls: ClsService<AppClsStore>,
|
||||
private readonly userAuthService: UsersAuthService
|
||||
) {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
secretOrKey: authConfig.accessToken.secret,
|
||||
@ -19,6 +25,7 @@ export class JwtAccessTokenAuthStrategy extends PassportStrategy(Strategy, 'jwt-
|
||||
async validate(request: FastifyRequest, payload: TokenPayload) {
|
||||
const account = await this.userAuthService.getUserById(payload.sub);
|
||||
if (!account) throw new UnauthorizedException('Access Token Guard');
|
||||
this.cls.set('account', account);
|
||||
return account;
|
||||
}
|
||||
}
|
@ -4,8 +4,11 @@ import { AuthGuard } from '@nestjs/passport';
|
||||
import { OpenAPIObject } from '@nestjs/swagger';
|
||||
|
||||
export const IS_PUBLIC_KEY = 'isPublic';
|
||||
export const Public = () =>
|
||||
applyDecorators(SetMetadata(IS_PUBLIC_KEY, true), SetMetadata('swagger/apiSecurity', ['public']));
|
||||
export const Public = (keepSecurity: boolean = false) => {
|
||||
const decorators = [SetMetadata(IS_PUBLIC_KEY, true)];
|
||||
if (!keepSecurity) decorators.push(SetMetadata('swagger/apiSecurity', ['public']));
|
||||
return applyDecorators(...decorators);
|
||||
};
|
||||
|
||||
export const patchPublicDecoratorSupport = (document: OpenAPIObject) => {
|
||||
Object.values(document.paths).forEach((path: any) => {
|
||||
@ -18,7 +21,7 @@ export const patchPublicDecoratorSupport = (document: OpenAPIObject) => {
|
||||
return document;
|
||||
};
|
||||
|
||||
export class JwtAuthGuard extends AuthGuard(['jwt-access-token', 'jwt-refresh-token']) {
|
||||
export class JwtAuthGuard extends AuthGuard('jwt-access-token') {
|
||||
constructor(private reflector: Reflector) {
|
||||
super();
|
||||
}
|
@ -1,14 +1,20 @@
|
||||
import { Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
||||
import { PassportStrategy } from '@nestjs/passport';
|
||||
import { FastifyRequest } from 'fastify';
|
||||
import { ClsService } from 'nestjs-cls';
|
||||
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||
import { AppClsStore } from '../../../cls/cls.interface';
|
||||
import { authConfig, AuthConfig } from '../../auth.config';
|
||||
import { TokenPayload } from '../../constants/token-payload.interface';
|
||||
import { UsersAuthService } from '../../users/users-auth.service';
|
||||
|
||||
@Injectable()
|
||||
export class JwtRefreshTokenAuthStrategy extends PassportStrategy(Strategy, 'jwt-refresh-token') {
|
||||
constructor(@Inject(authConfig.KEY) authConfig: AuthConfig, private readonly usersAuthService: UsersAuthService) {
|
||||
constructor(
|
||||
@Inject(authConfig.KEY) authConfig: AuthConfig,
|
||||
private readonly cls: ClsService<AppClsStore>,
|
||||
private readonly usersAuthService: UsersAuthService
|
||||
) {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
secretOrKey: authConfig.refreshToken.secret,
|
||||
@ -17,9 +23,10 @@ export class JwtRefreshTokenAuthStrategy extends PassportStrategy(Strategy, 'jwt
|
||||
}
|
||||
|
||||
async validate(request: FastifyRequest, payload: TokenPayload) {
|
||||
const refreshToken = (request.headers?.authorization as string| undefined)?.replace('Bearer', '')?.trim() ?? '';
|
||||
const refreshToken = (request.headers?.authorization as string | undefined)?.replace('Bearer', '')?.trim() ?? '';
|
||||
const account = await this.usersAuthService.getUserByIdAndRefreshTokenPair(payload.sub, refreshToken);
|
||||
if (!account) throw new UnauthorizedException('Refresh Token Guard');
|
||||
this.cls.set('account', account);
|
||||
return account;
|
||||
}
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
|
||||
export class JwtRefreshGuard extends AuthGuard('jwt-refresh-token') {}
|
@ -1,13 +1,14 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import bcrypt from 'bcrypt';
|
||||
import * as argon2 from 'argon2';
|
||||
import { Repository } from 'typeorm';
|
||||
import { AccountsEntity } from '../../database/connections/ebitemp-api/entities';
|
||||
import { EBITEMP_API_DATASOURCE } from '../../database/connections/ebitemp-api/database.constants';
|
||||
|
||||
@Injectable()
|
||||
export class UsersAuthService {
|
||||
constructor(
|
||||
@InjectRepository(AccountsEntity)
|
||||
@InjectRepository(AccountsEntity, EBITEMP_API_DATASOURCE)
|
||||
private readonly accountsRepository: Repository<AccountsEntity>
|
||||
) {}
|
||||
|
||||
@ -29,12 +30,12 @@ export class UsersAuthService {
|
||||
const accountById = await this.getUserById(accountId);
|
||||
if (!accountById?.ultimoHashRefreshToken) return null;
|
||||
|
||||
const isRefreshTokenMatching = await bcrypt.compare(refreshToken, accountById.ultimoHashRefreshToken);
|
||||
const isRefreshTokenMatching = await argon2.verify(accountById.ultimoHashRefreshToken, refreshToken);
|
||||
return isRefreshTokenMatching ? accountById : null;
|
||||
}
|
||||
|
||||
async setCurrentRefreshTokenHash(accountId: number, refreshToken: string | null) {
|
||||
const hash = refreshToken ? await bcrypt.hash(refreshToken, 10) : null;
|
||||
const hash = refreshToken ? await argon2.hash(refreshToken) : null;
|
||||
await this.accountsRepository.update(accountId, {
|
||||
ultimoHashRefreshToken: hash,
|
||||
});
|
@ -3,9 +3,10 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { UsersAuthService } from './users-auth.service';
|
||||
import { AccountsEntity } from '../../database/connections/ebitemp-api/entities';
|
||||
import { EBITEMP_API_DATASOURCE } from '../../database/connections/ebitemp-api/database.constants';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([AccountsEntity])],
|
||||
imports: [TypeOrmModule.forFeature([AccountsEntity], EBITEMP_API_DATASOURCE)],
|
||||
controllers: [],
|
||||
providers: [UsersAuthService],
|
||||
exports: [UsersAuthService],
|
6
apps/ebitemp-api/src/modules/cls/cls.interface.ts
Normal file
6
apps/ebitemp-api/src/modules/cls/cls.interface.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { ClsStore } from "nestjs-cls";
|
||||
import { AccountsEntity } from "../database/connections/ebitemp-api";
|
||||
|
||||
export interface AppClsStore extends ClsStore {
|
||||
account: AccountsEntity|null;
|
||||
}
|
16
apps/ebitemp-api/src/modules/cls/cls.module.ts
Normal file
16
apps/ebitemp-api/src/modules/cls/cls.module.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ClsModule } from 'nestjs-cls';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ClsModule.forRoot({
|
||||
global: true,
|
||||
middleware: {
|
||||
mount: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
providers: [],
|
||||
exports: [ClsModule],
|
||||
})
|
||||
export class AppClsModule {}
|
@ -0,0 +1 @@
|
||||
export const EBITEMP_API_DATASOURCE = "EbitempApi";
|
@ -6,17 +6,20 @@ import { typeormTransactionalDataSourceFactory } from '../../utils/typeorm-data-
|
||||
import { typeormEntitiesFromImport } from '../../utils/typeorm-import-entities';
|
||||
import { typeormModuleOptionsFactory } from '../../utils/typeorm-module-options-factory';
|
||||
import { databaseConfig } from './database.config';
|
||||
import { EBITEMP_API_DATASOURCE } from './database.constants';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forFeature(databaseConfig),
|
||||
TypeOrmModule.forRootAsync({
|
||||
imports: databaseConfig.asProvider().imports,
|
||||
dataSourceFactory: typeormTransactionalDataSourceFactory(),
|
||||
name: EBITEMP_API_DATASOURCE,
|
||||
dataSourceFactory: typeormTransactionalDataSourceFactory(EBITEMP_API_DATASOURCE),
|
||||
useFactory: async (dbConfig: DatabaseConfig) => {
|
||||
const config = await typeormModuleOptionsFactory(
|
||||
dbConfig,
|
||||
await typeormEntitiesFromImport(await import('./index'))
|
||||
await typeormEntitiesFromImport(await import('./index')),
|
||||
EBITEMP_API_DATASOURCE
|
||||
);
|
||||
return config;
|
||||
},
|
@ -0,0 +1,25 @@
|
||||
import { Column, Entity, Index, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { ApiClientInvocazioniEntity } from './api_client_invocazioni.entity';
|
||||
|
||||
@Index('pk_api_client', ['id'], { unique: true })
|
||||
@Entity('api_client')
|
||||
export class ApiClientEntity {
|
||||
@PrimaryGeneratedColumn({ type: 'int', name: 'id' })
|
||||
id!: number;
|
||||
|
||||
@Column('varchar', { name: 'nome', nullable: true, length: 255 })
|
||||
nome!: string | null;
|
||||
|
||||
@Column('varchar', { name: 'descrizione', nullable: true, length: 255 })
|
||||
descrizione!: string | null;
|
||||
|
||||
@Column('varchar', { name: 'base_url', length: 2000 })
|
||||
baseUrl!: string;
|
||||
|
||||
@OneToMany(() => ApiClientInvocazioniEntity, (apiClientInvocazioniEntity) => apiClientInvocazioniEntity.client)
|
||||
invocazioni!: ApiClientInvocazioniEntity[];
|
||||
|
||||
constructor(init?: Partial<ApiClientEntity>) {
|
||||
Object.assign(this, init);
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { ApiClientEntity } from './api_client.entity';
|
||||
|
||||
@Index('pk_api_client_invocazioni', ['id'], { unique: true })
|
||||
@Entity('api_client_invocazioni')
|
||||
export class ApiClientInvocazioniEntity {
|
||||
@PrimaryGeneratedColumn({ type: 'int', name: 'id' })
|
||||
id!: number;
|
||||
|
||||
@Column('int', { name: 'id_client' })
|
||||
idClient!: number;
|
||||
|
||||
@Column('varchar', { name: 'percorso', length: 2000 })
|
||||
percorso!: string;
|
||||
|
||||
@Column('varchar', { name: 'metodo', length: 7 })
|
||||
metodo!: string;
|
||||
|
||||
@Column('datetime', { name: 'timestamp' })
|
||||
timestamp!: Date;
|
||||
|
||||
@Column('varchar', { name: 'richiesta', nullable: true })
|
||||
richiesta!: string | null;
|
||||
|
||||
@Column('varchar', { name: 'risposta' })
|
||||
risposta!: string;
|
||||
|
||||
@Column('varchar', { name: 'errori', nullable: true })
|
||||
errori!: string | null;
|
||||
|
||||
@Column('bit', { name: 'flag_errore' })
|
||||
flagErrore!: boolean;
|
||||
|
||||
@ManyToOne(() => ApiClientEntity, (apiClientEntity) => apiClientEntity.invocazioni)
|
||||
@JoinColumn([{ name: 'id_client', referencedColumnName: 'id' }])
|
||||
client!: ApiClientEntity;
|
||||
|
||||
constructor(init?: Partial<ApiClientInvocazioniEntity>) {
|
||||
Object.assign(this, init);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { Column, Entity, Index, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { ApiEndpointInvocazioniEntity } from './api_endpoint_invocazioni.entity';
|
||||
|
||||
@Index('pk_api_endpoint', ['id'], { unique: true })
|
||||
@Entity('api_endpoint')
|
||||
export class ApiEndpointEntity {
|
||||
@PrimaryGeneratedColumn({ type: 'int', name: 'id' })
|
||||
id!: number;
|
||||
|
||||
@Column('varchar', { name: 'percorso', length: 2000 })
|
||||
percorso!: string;
|
||||
|
||||
@Column('varchar', { name: 'metodo', length: 7 })
|
||||
metodo!: string;
|
||||
|
||||
@OneToMany(
|
||||
() => ApiEndpointInvocazioniEntity,
|
||||
(apiEndpointInvocazioniEntity) => apiEndpointInvocazioniEntity.endpoint
|
||||
)
|
||||
invocazioni!: ApiEndpointInvocazioniEntity[];
|
||||
|
||||
constructor(init?: Partial<ApiEndpointEntity>) {
|
||||
Object.assign(this, init);
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import { AccountsEntity } from './accounts.entity';
|
||||
import { ApiEndpointEntity } from './api_endpoint.entity';
|
||||
|
||||
@Index('pk_api_endpoint_invocazioni', ['id'], { unique: true })
|
||||
@Entity('api_endpoint_invocazioni')
|
||||
export class ApiEndpointInvocazioniEntity {
|
||||
@PrimaryGeneratedColumn({ type: 'int', name: 'id' })
|
||||
id!: number;
|
||||
|
||||
@Column('int', { name: 'id_account' })
|
||||
idAccount!: number;
|
||||
|
||||
@Column('int', { name: 'id_endpoint' })
|
||||
idEndpoint!: number;
|
||||
|
||||
@Column('datetime', { name: 'timestamp' })
|
||||
timestamp!: Date;
|
||||
|
||||
@Column('varchar', { name: 'richiesta' })
|
||||
richiesta!: string;
|
||||
|
||||
@Column('varchar', { name: 'risposta' })
|
||||
risposta!: string;
|
||||
|
||||
@Column('bit', { name: 'flag_errore' })
|
||||
flagErrore!: boolean;
|
||||
|
||||
@ManyToOne(() => ApiEndpointEntity, (apiEndpointEntity) => apiEndpointEntity.invocazioni)
|
||||
@JoinColumn([{ name: 'id_endpoint', referencedColumnName: 'id' }])
|
||||
endpoint!: ApiEndpointEntity;
|
||||
|
||||
@ManyToOne(() => AccountsEntity)
|
||||
@JoinColumn([{ name: 'id_account', referencedColumnName: 'id' }])
|
||||
account!: AccountsEntity;
|
||||
|
||||
constructor(init?: Partial<ApiEndpointInvocazioniEntity>) {
|
||||
Object.assign(this, init);
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
export * from './accounts.entity';
|
||||
export * from './api_client.entity';
|
||||
export * from './api_client_invocazioni.entity';
|
||||
export * from './api_endpoint.entity';
|
||||
export * from './api_endpoint_invocazioni.entity';
|
||||
export * from './profili.entity';
|
||||
export * from './ruoli.entity';
|
||||
export * from './tipi_jobs.entity';
|
@ -1,5 +1,5 @@
|
||||
import { Column, Entity, Index } from 'typeorm';
|
||||
import { Enumify } from '../../../../enumify/enumify';
|
||||
import { Enum, Enumify } from '../../../../enumify/enumify';
|
||||
|
||||
export class InviaMailSeErrori {
|
||||
@Column('bit', { name: 'flag', default: () => '(0)' })
|
@ -0,0 +1 @@
|
||||
export const OCEANO_DATASOURCE = "Oceano";
|
@ -6,17 +6,20 @@ import { typeormTransactionalDataSourceFactory } from '../../utils/typeorm-data-
|
||||
import { typeormEntitiesFromImport } from '../../utils/typeorm-import-entities';
|
||||
import { typeormModuleOptionsFactory } from '../../utils/typeorm-module-options-factory';
|
||||
import { databaseConfig } from './database.config';
|
||||
import { OCEANO_DATASOURCE } from './database.constants';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forFeature(databaseConfig),
|
||||
TypeOrmModule.forRootAsync({
|
||||
imports: databaseConfig.asProvider().imports,
|
||||
dataSourceFactory: typeormTransactionalDataSourceFactory(),
|
||||
name: OCEANO_DATASOURCE,
|
||||
dataSourceFactory: typeormTransactionalDataSourceFactory(OCEANO_DATASOURCE),
|
||||
useFactory: async (dbConfig: DatabaseConfig) => {
|
||||
const config = await typeormModuleOptionsFactory(
|
||||
dbConfig,
|
||||
await typeormEntitiesFromImport(await import('./index'))
|
||||
await typeormEntitiesFromImport(await import('./index')),
|
||||
OCEANO_DATASOURCE
|
||||
);
|
||||
return config;
|
||||
},
|
@ -1,8 +1,8 @@
|
||||
import { Column, Entity } from 'typeorm';
|
||||
import { Column, Entity, PrimaryColumn } from 'typeorm';
|
||||
|
||||
@Entity('API_Luoghi_Estero')
|
||||
export class ApiLuoghiEsteroEntity {
|
||||
@Column('varchar', { name: 'codice', length: 5 })
|
||||
@PrimaryColumn('varchar', { name: 'codice', length: 5 })
|
||||
codice!: string;
|
||||
|
||||
@Column('nvarchar', { name: 'codice_stato', length: 5 })
|
@ -1,8 +1,11 @@
|
||||
import { Column, Entity, Index, OneToMany } from 'typeorm';
|
||||
import { EnteEntity } from './ente.entity';
|
||||
|
||||
@Index('PK_gruppo', ['codiceGruppo'], { unique: true })
|
||||
@Entity('gruppo')
|
||||
export class GruppoEntity {
|
||||
|
||||
|
||||
@Column('bigint', { primary: true, name: 'codiceGruppo' })
|
||||
codiceGruppo!: string;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { APP_DATASOURCES } from './database.constants';
|
||||
import { EbitempApiDatabaseModule } from './connections/ebitemp-api/database.module';
|
||||
import { OceanoDatabaseModule } from './connections/oceano/database.module';
|
||||
import { APP_DATASOURCES } from './database.constants';
|
||||
import { dataSources } from './utils/typeorm-data-source-factory';
|
||||
|
||||
const dataSourcesProvider = {
|
||||
@ -8,7 +9,7 @@ const dataSourcesProvider = {
|
||||
useValue: dataSources,
|
||||
};
|
||||
|
||||
const databaseModules = [EbitempApiDatabaseModule];
|
||||
const databaseModules = [EbitempApiDatabaseModule, OceanoDatabaseModule];
|
||||
|
||||
@Global()
|
||||
@Module({
|
@ -0,0 +1,17 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ApiClientEntity, ApiClientInvocazioniEntity } from '../../database/connections/ebitemp-api';
|
||||
import { EBITEMP_API_DATASOURCE } from '../../database/connections/ebitemp-api/database.constants';
|
||||
import { KeyvModule } from '../../keyv/keyv.module';
|
||||
import { ClientsService } from './clients.service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [
|
||||
KeyvModule,
|
||||
TypeOrmModule.forFeature([ApiClientEntity, ApiClientInvocazioniEntity], EBITEMP_API_DATASOURCE),
|
||||
],
|
||||
providers: [ClientsService],
|
||||
exports: [ClientsService],
|
||||
})
|
||||
export class ClientsModule {}
|
@ -0,0 +1,86 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { isError } from '@stdlib/assert';
|
||||
import AsyncLock from 'async-lock';
|
||||
import { Keyv } from 'cacheable';
|
||||
import { isObject } from 'lodash';
|
||||
import { Repository } from 'typeorm';
|
||||
import { runInTransaction } from 'typeorm-transactional';
|
||||
import { ApiClientEntity, ApiClientInvocazioniEntity } from '../../database/connections/ebitemp-api';
|
||||
import { EBITEMP_API_DATASOURCE } from '../../database/connections/ebitemp-api/database.constants';
|
||||
import { KeyvService } from '../../keyv/keyv.service';
|
||||
|
||||
@Injectable()
|
||||
export class ClientsService {
|
||||
private readonly logger = new Logger(ClientsService.name);
|
||||
private readonly lock = new AsyncLock();
|
||||
|
||||
private readonly keyv: Keyv;
|
||||
|
||||
constructor(
|
||||
keyvService: KeyvService,
|
||||
@InjectRepository(ApiClientEntity, EBITEMP_API_DATASOURCE)
|
||||
private readonly clientRepository: Repository<ApiClientEntity>,
|
||||
@InjectRepository(ApiClientInvocazioniEntity, EBITEMP_API_DATASOURCE)
|
||||
private readonly clientInvocazioniRepository: Repository<ApiClientInvocazioniEntity>
|
||||
) {
|
||||
this.keyv = keyvService.create({
|
||||
namespace: ClientsService.name,
|
||||
});
|
||||
}
|
||||
|
||||
async insertOne(
|
||||
baseUrl: string,
|
||||
path: string,
|
||||
method: string,
|
||||
requestBody: any,
|
||||
responseBody: any,
|
||||
hasFailed: boolean,
|
||||
errors: string | null
|
||||
) {
|
||||
try {
|
||||
return await runInTransaction(
|
||||
async () => {
|
||||
const client = await this.getClient(baseUrl);
|
||||
if (!client) return;
|
||||
|
||||
const payload = JSON.stringify(requestBody);
|
||||
const response = isObject(responseBody) ? JSON.stringify(responseBody) : responseBody;
|
||||
|
||||
const invocazione = this.clientInvocazioniRepository.create({
|
||||
idClient: client.id,
|
||||
percorso: path,
|
||||
metodo: method,
|
||||
timestamp: new Date(),
|
||||
richiesta: payload,
|
||||
risposta: response,
|
||||
errori: errors,
|
||||
flagErrore: hasFailed,
|
||||
});
|
||||
|
||||
await this.clientInvocazioniRepository.insert(invocazione);
|
||||
return invocazione;
|
||||
},
|
||||
{ connectionName: EBITEMP_API_DATASOURCE }
|
||||
);
|
||||
} catch (err) {
|
||||
if (!isError(err)) throw err;
|
||||
this.logger.error(`Unexpected error: ${err.message}`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private async getClient(baseUrl: string) {
|
||||
const key = `client:baseUrl#${baseUrl}`;
|
||||
|
||||
return await this.lock.acquire(key, async () => {
|
||||
let client = await this.keyv.get<ApiClientEntity>(key);
|
||||
const isInCache = client != null;
|
||||
if (!isInCache) {
|
||||
client ??= (await this.clientRepository.findOne({ where: { baseUrl: baseUrl } })) ?? undefined;
|
||||
if (client) this.keyv.set(key, client);
|
||||
}
|
||||
return client;
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ApiEndpointEntity, ApiEndpointInvocazioniEntity } from '../../database/connections/ebitemp-api';
|
||||
import { EBITEMP_API_DATASOURCE } from '../../database/connections/ebitemp-api/database.constants';
|
||||
import { KeyvModule } from '../../keyv/keyv.module';
|
||||
import { EndpointsService } from './endpoints.service';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [
|
||||
KeyvModule,
|
||||
TypeOrmModule.forFeature([ApiEndpointEntity, ApiEndpointInvocazioniEntity], EBITEMP_API_DATASOURCE),
|
||||
],
|
||||
providers: [EndpointsService],
|
||||
exports: [EndpointsService],
|
||||
})
|
||||
export class EndpointsModule {}
|
@ -0,0 +1,84 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import AsyncLock from 'async-lock';
|
||||
import { Keyv } from 'cacheable';
|
||||
import { Repository } from 'typeorm';
|
||||
import { runInTransaction } from 'typeorm-transactional';
|
||||
import { AccountsEntity, ApiEndpointEntity, ApiEndpointInvocazioniEntity } from '../../database/connections/ebitemp-api';
|
||||
import { EBITEMP_API_DATASOURCE } from '../../database/connections/ebitemp-api/database.constants';
|
||||
import { KeyvService } from '../../keyv/keyv.service';
|
||||
import { RequestLog } from '../request-logger.interceptor';
|
||||
import { isError } from '@stdlib/assert';
|
||||
|
||||
@Injectable()
|
||||
export class EndpointsService {
|
||||
private readonly logger = new Logger(EndpointsService.name);
|
||||
private readonly lock = new AsyncLock();
|
||||
|
||||
private readonly keyv: Keyv;
|
||||
|
||||
constructor(
|
||||
keyvService: KeyvService,
|
||||
@InjectRepository(ApiEndpointEntity, EBITEMP_API_DATASOURCE)
|
||||
private readonly endpointRepository: Repository<ApiEndpointEntity>,
|
||||
@InjectRepository(ApiEndpointInvocazioniEntity, EBITEMP_API_DATASOURCE)
|
||||
private readonly endpointInvocazioniRepository: Repository<ApiEndpointInvocazioniEntity>
|
||||
) {
|
||||
this.keyv = keyvService.create({
|
||||
namespace: EndpointsService.name,
|
||||
});
|
||||
}
|
||||
|
||||
async insertOne(
|
||||
user: AccountsEntity,
|
||||
route: string,
|
||||
method: string,
|
||||
requestLog: RequestLog,
|
||||
responseLog: any,
|
||||
hasFailed: boolean
|
||||
) {
|
||||
try {
|
||||
return await runInTransaction(
|
||||
async () => {
|
||||
const endpoint = await this.getEndpoint(route, method);
|
||||
if (!endpoint) return;
|
||||
|
||||
const payload = JSON.stringify(requestLog);
|
||||
const response = JSON.stringify(responseLog);
|
||||
|
||||
const invocazione = this.endpointInvocazioniRepository.create({
|
||||
idAccount: user.id,
|
||||
idEndpoint: endpoint.id,
|
||||
timestamp: new Date(),
|
||||
richiesta: payload,
|
||||
risposta: response,
|
||||
flagErrore: hasFailed,
|
||||
});
|
||||
|
||||
await this.endpointInvocazioniRepository.insert(invocazione);
|
||||
return invocazione;
|
||||
},
|
||||
{ connectionName: EBITEMP_API_DATASOURCE }
|
||||
);
|
||||
} catch (err) {
|
||||
if (!isError(err)) throw err;
|
||||
this.logger.error(`Unexpected error: ${err.message}`);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
private async getEndpoint(route: string, method: string) {
|
||||
const key = `endpoint:route,method#${route},${method}`;
|
||||
|
||||
return await this.lock.acquire(key, async () => {
|
||||
let endpoint = await this.keyv.get<ApiEndpointEntity>(key);
|
||||
const isInCache = endpoint != null;
|
||||
if (!isInCache) {
|
||||
endpoint ??=
|
||||
(await this.endpointRepository.findOne({ where: { percorso: route, metodo: method } })) ?? undefined;
|
||||
if (endpoint) this.keyv.set(key, endpoint);
|
||||
}
|
||||
return endpoint;
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common';
|
||||
import { omit } from 'lodash';
|
||||
import { Observable } from 'rxjs';
|
||||
import { catchError, concatMap } from 'rxjs/operators';
|
||||
import { EndpointsService } from './endpoints/endpoints.service';
|
||||
import { RequestWithUser } from '../auth/constants/request-with-user';
|
||||
|
||||
export type RequestLog = {
|
||||
url: string;
|
||||
idAccount: string;
|
||||
params: object;
|
||||
query: object;
|
||||
body: object;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class RequestLoggerInterceptor implements NestInterceptor {
|
||||
private readonly logger = new Logger(RequestLoggerInterceptor.name);
|
||||
|
||||
constructor(private readonly endpointsService: EndpointsService) {}
|
||||
|
||||
async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
|
||||
const request: RequestWithUser = context.switchToHttp().getRequest();
|
||||
|
||||
const user = request.user;
|
||||
const route = request.routeOptions.url ?? '/';
|
||||
const method = request.method as string;
|
||||
|
||||
request.params = { ...(request.params as object ?? {}), route, method };
|
||||
|
||||
const requestLog = {
|
||||
url: request.url,
|
||||
idAccount: `${user.id}`,
|
||||
params: omit((request.params as object), ['user', 'route', 'method']),
|
||||
query: omit((request.query as object), ['__context']),
|
||||
body: request.body as object,
|
||||
};
|
||||
|
||||
return next.handle().pipe(
|
||||
concatMap(async (res: any) => {
|
||||
await this.endpointsService.insertOne(user, route, method, requestLog, res, false);
|
||||
return res;
|
||||
}),
|
||||
catchError(async (err: any) => {
|
||||
const getExceptionResponse = (exception: any): any => {
|
||||
if (typeof exception.response === 'object') {
|
||||
const { error, message, ...rest } = exception.response;
|
||||
return {
|
||||
error,
|
||||
message,
|
||||
...rest,
|
||||
};
|
||||
}
|
||||
return {
|
||||
error: exception.name,
|
||||
message: exception.message,
|
||||
};
|
||||
};
|
||||
|
||||
const res = getExceptionResponse(err);
|
||||
|
||||
await this.endpointsService.insertOne(user, route, method, requestLog, res, true);
|
||||
throw err;
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import { Global, Module } from '@nestjs/common';
|
||||
import { APP_INTERCEPTOR } from '@nestjs/core';
|
||||
import { ClientsModule } from './clients/clients.module';
|
||||
import { EndpointsModule } from './endpoints/endpoints.module';
|
||||
import { RequestLoggerInterceptor } from './request-logger.interceptor';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [EndpointsModule, ClientsModule],
|
||||
providers: [],
|
||||
exports: [EndpointsModule, ClientsModule],
|
||||
})
|
||||
export class AppRequestLoggingModule {}
|
@ -3,9 +3,10 @@ import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { TipiJobsEntity } from '../database/connections/ebitemp-api/entities';
|
||||
import { SchedulerService } from './schedule.service';
|
||||
import { EBITEMP_API_DATASOURCE } from '../database/connections/ebitemp-api/database.constants';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([TipiJobsEntity]), ScheduleModule.forRoot()],
|
||||
imports: [TypeOrmModule.forFeature([TipiJobsEntity], EBITEMP_API_DATASOURCE), ScheduleModule.forRoot()],
|
||||
providers: [SchedulerService],
|
||||
exports: [],
|
||||
})
|
@ -1,10 +1,11 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { SchedulerRegistry } from '@nestjs/schedule';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { isError } from '@stdlib/assert';
|
||||
import { CronJob, CronTime } from 'cron';
|
||||
import { Repository } from 'typeorm';
|
||||
import { EBITEMP_API_DATASOURCE } from '../database/connections/ebitemp-api/database.constants';
|
||||
import { TipiJobsEntity } from '../database/connections/ebitemp-api/entities/tipi_jobs.entity';
|
||||
import { isError } from '@stdlib/assert';
|
||||
|
||||
@Injectable()
|
||||
export class SchedulerService {
|
||||
@ -14,7 +15,8 @@ export class SchedulerService {
|
||||
|
||||
constructor(
|
||||
private readonly schedulerRegistry: SchedulerRegistry,
|
||||
@InjectRepository(TipiJobsEntity) private readonly tipiJobsRepository: Repository<TipiJobsEntity>
|
||||
@InjectRepository(TipiJobsEntity, EBITEMP_API_DATASOURCE)
|
||||
private readonly tipiJobsRepository: Repository<TipiJobsEntity>
|
||||
) {}
|
||||
|
||||
async onApplicationBootstrap() {
|
@ -41,6 +41,7 @@ export class ZodSerializerInterceptor implements NestInterceptor {
|
||||
map((res: object | object[]) => {
|
||||
if (!responseSchema) return res;
|
||||
if (typeof res !== 'object' || res instanceof StreamableFile) return res;
|
||||
if (!('safeParse' in responseSchema)) return res;
|
||||
|
||||
return Array.isArray(res)
|
||||
? res.map(
|
@ -30,6 +30,8 @@
|
||||
"@nestjs/typeorm": "^11.0.0",
|
||||
"@nx/devkit": "20.6.0",
|
||||
"@stdlib/assert": "^0.3.3",
|
||||
"argon2": "^0.41.1",
|
||||
"async-lock": "^1.4.1",
|
||||
"axios": "^1.7.9",
|
||||
"bcrypt": "^5.1.1",
|
||||
"cacheable": "^1.8.8",
|
||||
@ -39,6 +41,8 @@
|
||||
"fastify": "^5.2.1",
|
||||
"flatted": "^3.3.2",
|
||||
"lodash": "^4.17.21",
|
||||
"nestjs-cls": "^5.4.1",
|
||||
"nestjs-paginate": "^11.1.1",
|
||||
"nestjs-pino": "^4.3.0",
|
||||
"openapi3-ts": "^4.4.0",
|
||||
"passport-jwt": "^4.0.1",
|
||||
@ -73,6 +77,7 @@
|
||||
"@swc/cli": "~0.3.12",
|
||||
"@swc/core": "~1.10.15",
|
||||
"@swc/helpers": "~0.5.11",
|
||||
"@types/async-lock": "^1.4.2",
|
||||
"@types/bcrypt": "^5.0.2",
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/lodash": "^4.17.15",
|
||||
@ -83,6 +88,7 @@
|
||||
"execa": "5.1.1",
|
||||
"jest": "^29.7.0",
|
||||
"jest-environment-node": "^29.7.0",
|
||||
"jsonc-eslint-parser": "^2.1.0",
|
||||
"mssql": "^11.0.1",
|
||||
"nx": "20.6.0",
|
||||
"prettier": "^2.6.2",
|
||||
|
90
pnpm-lock.yaml
generated
90
pnpm-lock.yaml
generated
@ -65,6 +65,12 @@ importers:
|
||||
'@stdlib/assert':
|
||||
specifier: ^0.3.3
|
||||
version: 0.3.3
|
||||
argon2:
|
||||
specifier: ^0.41.1
|
||||
version: 0.41.1
|
||||
async-lock:
|
||||
specifier: ^1.4.1
|
||||
version: 1.4.1
|
||||
axios:
|
||||
specifier: ^1.7.9
|
||||
version: 1.7.9
|
||||
@ -92,6 +98,12 @@ importers:
|
||||
lodash:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
nestjs-cls:
|
||||
specifier: ^5.4.1
|
||||
version: 5.4.1(@nestjs/common@11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.8)(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
||||
nestjs-paginate:
|
||||
specifier: ^11.1.1
|
||||
version: 11.1.1(@nestjs/common@11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/swagger@11.0.3(@fastify/static@8.1.0)(@nestjs/common@11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.8)(reflect-metadata@0.2.2))(express@5.0.1)(fastify@5.2.1)(typeorm@0.3.20(mssql@11.0.1)(ts-node@10.9.2(@swc/core@1.10.15(@swc/helpers@0.5.15))(@types/node@18.16.20)(typescript@5.7.3)))
|
||||
nestjs-pino:
|
||||
specifier: ^4.3.0
|
||||
version: 4.3.0(@nestjs/common@11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1))(pino-http@10.4.0)
|
||||
@ -189,6 +201,9 @@ importers:
|
||||
'@swc/helpers':
|
||||
specifier: ~0.5.11
|
||||
version: 0.5.15
|
||||
'@types/async-lock':
|
||||
specifier: ^1.4.2
|
||||
version: 1.4.2
|
||||
'@types/bcrypt':
|
||||
specifier: ^5.0.2
|
||||
version: 5.0.2
|
||||
@ -219,6 +234,9 @@ importers:
|
||||
jest-environment-node:
|
||||
specifier: ^29.7.0
|
||||
version: 29.7.0
|
||||
jsonc-eslint-parser:
|
||||
specifier: ^2.1.0
|
||||
version: 2.4.0
|
||||
mssql:
|
||||
specifier: ^11.0.1
|
||||
version: 11.0.1
|
||||
@ -1798,6 +1816,10 @@ packages:
|
||||
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
|
||||
engines: {node: '>= 10.0.0'}
|
||||
|
||||
'@phc/format@1.0.0':
|
||||
resolution: {integrity: sha512-m7X9U6BG2+J+R1lSOdCiITLLrxm+cWlNI3HUFA92oLO77ObGNzaKdh8pMLqdZcshtkKuV84olNNXDfMc4FezBQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
'@phenomnomnominal/tsquery@5.0.1':
|
||||
resolution: {integrity: sha512-3nVv+e2FQwsW8Aw6qTU6f+1rfcJ3hrcnvH/mu9i8YhxO+9sqbOfpL8m6PbET5+xKOlz/VSbp0RoYWYCtIsnmuA==}
|
||||
peerDependencies:
|
||||
@ -2154,6 +2176,9 @@ packages:
|
||||
'@tybys/wasm-util@0.9.0':
|
||||
resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==}
|
||||
|
||||
'@types/async-lock@1.4.2':
|
||||
resolution: {integrity: sha512-HlZ6Dcr205BmNhwkdXqrg2vkFMN2PluI7Lgr8In3B3wE5PiQHhjRqtW/lGdVU9gw+sM0JcIDx2AN+cW8oSWIcw==}
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
||||
|
||||
@ -2590,6 +2615,10 @@ packages:
|
||||
arg@4.1.3:
|
||||
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
|
||||
|
||||
argon2@0.41.1:
|
||||
resolution: {integrity: sha512-dqCW8kJXke8Ik+McUcMDltrbuAWETPyU6iq+4AhxqKphWi7pChB/Zgd/Tp/o8xRLbg8ksMj46F/vph9wnxpTzQ==}
|
||||
engines: {node: '>=16.17.0'}
|
||||
|
||||
argparse@1.0.10:
|
||||
resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==}
|
||||
|
||||
@ -2613,6 +2642,9 @@ packages:
|
||||
resolution: {integrity: sha512-gFaHkFfSxTjvoxDMYqDuGHlcRyUuamF8s+ZTtJdDzqjws4mCt7v0vuV79/E2Wr2/riMQgtG4/yUtXWs1gZ7JMg==}
|
||||
engines: {node: ^4.7 || >=6.9 || >=7.3}
|
||||
|
||||
async-lock@1.4.1:
|
||||
resolution: {integrity: sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==}
|
||||
|
||||
async@2.6.4:
|
||||
resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==}
|
||||
|
||||
@ -4885,6 +4917,24 @@ packages:
|
||||
neo-async@2.6.2:
|
||||
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
||||
|
||||
nestjs-cls@5.4.1:
|
||||
resolution: {integrity: sha512-hm0CQE6hRjr07yqSTcj50eQwNuZhlOHocX1W0CHpsYo9aDqsEysGkshegvAlC/4NVedTeAU6J0dVjAvNPTMrjQ==}
|
||||
engines: {node: '>=18'}
|
||||
peerDependencies:
|
||||
'@nestjs/common': '>= 10 < 12'
|
||||
'@nestjs/core': '>= 10 < 12'
|
||||
reflect-metadata: '*'
|
||||
rxjs: '>= 7'
|
||||
|
||||
nestjs-paginate@11.1.1:
|
||||
resolution: {integrity: sha512-PYNTxnQ3ZHGjXfOgqMNtEmMlad1o9M6RoiXPSQtCl3n8MDH+t2igXn9Z9Id6B5SfBnsrvRD9YEi2Zb1EU5g6Fg==}
|
||||
peerDependencies:
|
||||
'@nestjs/common': ^10.0.0 || ^11.0.0
|
||||
'@nestjs/swagger': ^8.0.0 || ^11.0.0
|
||||
express: ^4.21.2 || ^5.0.0
|
||||
fastify: ^4.0.0 || ^5.0.0
|
||||
typeorm: ^0.3.17
|
||||
|
||||
nestjs-pino@4.3.0:
|
||||
resolution: {integrity: sha512-u/FRi+eRH+ER6cccyr9BBNaHg71qxLgWwDT7a0eGbSVZrSUzQ04O5zTNqydgzihNNHfuam6TBTz1wWdapOWYnQ==}
|
||||
engines: {node: '>= 14'}
|
||||
@ -4904,6 +4954,10 @@ packages:
|
||||
node-addon-api@7.1.1:
|
||||
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
||||
|
||||
node-addon-api@8.3.1:
|
||||
resolution: {integrity: sha512-lytcDEdxKjGJPTLEfW4mYMigRezMlyJY8W4wxJK8zE533Jlb8L8dRuObJFWg2P+AuOIxoCgKF+2Oq4d4Zd0OUA==}
|
||||
engines: {node: ^18 || ^20 || >= 21}
|
||||
|
||||
node-fetch@2.7.0:
|
||||
resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==}
|
||||
engines: {node: 4.x || >=6.0.0}
|
||||
@ -4917,6 +4971,10 @@ packages:
|
||||
resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
|
||||
engines: {node: '>= 6.13.0'}
|
||||
|
||||
node-gyp-build@4.8.4:
|
||||
resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==}
|
||||
hasBin: true
|
||||
|
||||
node-int64@0.4.0:
|
||||
resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==}
|
||||
|
||||
@ -8996,6 +9054,8 @@ snapshots:
|
||||
'@parcel/watcher-win32-x64': 2.5.1
|
||||
optional: true
|
||||
|
||||
'@phc/format@1.0.0': {}
|
||||
|
||||
'@phenomnomnominal/tsquery@5.0.1(typescript@5.7.3)':
|
||||
dependencies:
|
||||
esquery: 1.6.0
|
||||
@ -9600,6 +9660,8 @@ snapshots:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@types/async-lock@1.4.2': {}
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
dependencies:
|
||||
'@babel/parser': 7.26.7
|
||||
@ -10116,6 +10178,12 @@ snapshots:
|
||||
|
||||
arg@4.1.3: {}
|
||||
|
||||
argon2@0.41.1:
|
||||
dependencies:
|
||||
'@phc/format': 1.0.0
|
||||
node-addon-api: 8.3.1
|
||||
node-gyp-build: 4.8.4
|
||||
|
||||
argparse@1.0.10:
|
||||
dependencies:
|
||||
sprintf-js: 1.0.3
|
||||
@ -10134,6 +10202,8 @@ snapshots:
|
||||
dependencies:
|
||||
stack-chain: 1.3.7
|
||||
|
||||
async-lock@1.4.1: {}
|
||||
|
||||
async@2.6.4:
|
||||
dependencies:
|
||||
lodash: 4.17.21
|
||||
@ -12776,6 +12846,22 @@ snapshots:
|
||||
|
||||
neo-async@2.6.2: {}
|
||||
|
||||
nestjs-cls@5.4.1(@nestjs/common@11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.8)(reflect-metadata@0.2.2)(rxjs@7.8.1):
|
||||
dependencies:
|
||||
'@nestjs/common': 11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
||||
'@nestjs/core': 11.0.8(@nestjs/common@11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@11.0.8)(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
||||
reflect-metadata: 0.2.2
|
||||
rxjs: 7.8.1
|
||||
|
||||
nestjs-paginate@11.1.1(@nestjs/common@11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/swagger@11.0.3(@fastify/static@8.1.0)(@nestjs/common@11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.8)(reflect-metadata@0.2.2))(express@5.0.1)(fastify@5.2.1)(typeorm@0.3.20(mssql@11.0.1)(ts-node@10.9.2(@swc/core@1.10.15(@swc/helpers@0.5.15))(@types/node@18.16.20)(typescript@5.7.3))):
|
||||
dependencies:
|
||||
'@nestjs/common': 11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
||||
'@nestjs/swagger': 11.0.3(@fastify/static@8.1.0)(@nestjs/common@11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@11.0.8)(reflect-metadata@0.2.2)
|
||||
express: 5.0.1
|
||||
fastify: 5.2.1
|
||||
lodash: 4.17.21
|
||||
typeorm: 0.3.20(mssql@11.0.1)(ts-node@10.9.2(@swc/core@1.10.15(@swc/helpers@0.5.15))(@types/node@18.16.20)(typescript@5.7.3))
|
||||
|
||||
nestjs-pino@4.3.0(@nestjs/common@11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1))(pino-http@10.4.0):
|
||||
dependencies:
|
||||
'@nestjs/common': 11.0.8(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
||||
@ -12793,12 +12879,16 @@ snapshots:
|
||||
node-addon-api@7.1.1:
|
||||
optional: true
|
||||
|
||||
node-addon-api@8.3.1: {}
|
||||
|
||||
node-fetch@2.7.0:
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
|
||||
node-forge@1.3.1: {}
|
||||
|
||||
node-gyp-build@4.8.4: {}
|
||||
|
||||
node-int64@0.4.0: {}
|
||||
|
||||
node-machine-id@1.1.12: {}
|
||||
|
@ -24,7 +24,7 @@
|
||||
"alias": "dir",
|
||||
"x-prompt": "Which directory do you want to create the entities in?",
|
||||
"default": "typeorm-model-generator-output",
|
||||
"x-priority": "important"
|
||||
"x-priority": "important"
|
||||
},
|
||||
"driver": {
|
||||
"description": "The database driver that should be used by the generator.",
|
||||
|
Reference in New Issue
Block a user