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, @InjectRepository(ApiEndpointInvocazioniEntity, EBITEMP_API_DATASOURCE) private readonly endpointInvocazioniRepository: Repository ) { 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(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; }); } }