diff --git a/apps/ebitemp-api/src/app/app.controller.ts b/apps/ebitemp-api/src/app/app.controller.ts index dfd1296..394aaec 100644 --- a/apps/ebitemp-api/src/app/app.controller.ts +++ b/apps/ebitemp-api/src/app/app.controller.ts @@ -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 }) diff --git a/apps/ebitemp-api/src/app/app.module.ts b/apps/ebitemp-api/src/app/app.module.ts index a7d324e..a25df2d 100644 --- a/apps/ebitemp-api/src/app/app.module.ts +++ b/apps/ebitemp-api/src/app/app.module.ts @@ -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], diff --git a/apps/ebitemp-api/src/app/modules/database/connections/ebitemp-api/entities/index.ts b/apps/ebitemp-api/src/app/modules/database/connections/ebitemp-api/entities/index.ts deleted file mode 100644 index 11fabeb..0000000 --- a/apps/ebitemp-api/src/app/modules/database/connections/ebitemp-api/entities/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './accounts.entity'; -export * from './profili.entity'; -export * from './ruoli.entity'; -export * from './tipi_jobs.entity'; diff --git a/apps/ebitemp-api/src/features/anagrafica/anagrafica.controller.spec.ts b/apps/ebitemp-api/src/features/anagrafica/anagrafica.controller.spec.ts new file mode 100644 index 0000000..c97cbd1 --- /dev/null +++ b/apps/ebitemp-api/src/features/anagrafica/anagrafica.controller.spec.ts @@ -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(); + }); +}); diff --git a/apps/ebitemp-api/src/features/anagrafica/anagrafica.controller.ts b/apps/ebitemp-api/src/features/anagrafica/anagrafica.controller.ts new file mode 100644 index 0000000..74f9f6f --- /dev/null +++ b/apps/ebitemp-api/src/features/anagrafica/anagrafica.controller.ts @@ -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); + } +} diff --git a/apps/ebitemp-api/src/features/anagrafica/anagrafica.module.ts b/apps/ebitemp-api/src/features/anagrafica/anagrafica.module.ts new file mode 100644 index 0000000..58bd7bc --- /dev/null +++ b/apps/ebitemp-api/src/features/anagrafica/anagrafica.module.ts @@ -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 {} diff --git a/apps/ebitemp-api/src/features/anagrafica/anagrafica.service.spec.ts b/apps/ebitemp-api/src/features/anagrafica/anagrafica.service.spec.ts new file mode 100644 index 0000000..bc42cd0 --- /dev/null +++ b/apps/ebitemp-api/src/features/anagrafica/anagrafica.service.spec.ts @@ -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(); + }); +}); diff --git a/apps/ebitemp-api/src/features/anagrafica/anagrafica.service.ts b/apps/ebitemp-api/src/features/anagrafica/anagrafica.service.ts new file mode 100644 index 0000000..32dd51b --- /dev/null +++ b/apps/ebitemp-api/src/features/anagrafica/anagrafica.service.ts @@ -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 => ({ + 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 + ) {} + + 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 }, + }); + } +} diff --git a/apps/ebitemp-api/src/main.ts b/apps/ebitemp-api/src/main.ts index 1b3cb32..fb0ae16 100644 --- a/apps/ebitemp-api/src/main.ts +++ b/apps/ebitemp-api/src/main.ts @@ -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(); diff --git a/apps/ebitemp-api/src/app/modules/auth/auth.config.ts b/apps/ebitemp-api/src/modules/auth/auth.config.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/auth/auth.config.ts rename to apps/ebitemp-api/src/modules/auth/auth.config.ts diff --git a/apps/ebitemp-api/src/app/modules/auth/auth.controller.ts b/apps/ebitemp-api/src/modules/auth/auth.controller.ts similarity index 65% rename from apps/ebitemp-api/src/app/modules/auth/auth.controller.ts rename to apps/ebitemp-api/src/modules/auth/auth.controller.ts index 42b31e0..17d2956 100644 --- a/apps/ebitemp-api/src/app/modules/auth/auth.controller.ts +++ b/apps/ebitemp-api/src/modules/auth/auth.controller.ts @@ -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 { 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 { - const { accessToken, refreshToken } = await this.authService.signJwts(user); - return { - accessToken: accessToken, - refreshToken: refreshToken, - }; + return await this.authService.signJwts(user); } } diff --git a/apps/ebitemp-api/src/app/modules/auth/auth.dto.ts b/apps/ebitemp-api/src/modules/auth/auth.dto.ts similarity index 88% rename from apps/ebitemp-api/src/app/modules/auth/auth.dto.ts rename to apps/ebitemp-api/src/modules/auth/auth.dto.ts index f353e3b..cfb1420 100644 --- a/apps/ebitemp-api/src/app/modules/auth/auth.dto.ts +++ b/apps/ebitemp-api/src/modules/auth/auth.dto.ts @@ -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; export class LoginResDto extends createZodDto(loginResSchema) {} diff --git a/apps/ebitemp-api/src/app/modules/auth/auth.module.ts b/apps/ebitemp-api/src/modules/auth/auth.module.ts similarity index 88% rename from apps/ebitemp-api/src/app/modules/auth/auth.module.ts rename to apps/ebitemp-api/src/modules/auth/auth.module.ts index 8d14ba3..9781318 100644 --- a/apps/ebitemp-api/src/app/modules/auth/auth.module.ts +++ b/apps/ebitemp-api/src/modules/auth/auth.module.ts @@ -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, diff --git a/apps/ebitemp-api/src/app/modules/auth/auth.service.ts b/apps/ebitemp-api/src/modules/auth/auth.service.ts similarity index 54% rename from apps/ebitemp-api/src/app/modules/auth/auth.service.ts rename to apps/ebitemp-api/src/modules/auth/auth.service.ts index 655d60a..1e77bf8 100644 --- a/apps/ebitemp-api/src/app/modules/auth/auth.service.ts +++ b/apps/ebitemp-api/src/modules/auth/auth.service.ts @@ -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 { diff --git a/apps/ebitemp-api/src/app/modules/auth/authenticated-user.decorator.ts b/apps/ebitemp-api/src/modules/auth/authenticated-user.decorator.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/auth/authenticated-user.decorator.ts rename to apps/ebitemp-api/src/modules/auth/authenticated-user.decorator.ts diff --git a/apps/ebitemp-api/src/app/modules/auth/constants/request-with-user.ts b/apps/ebitemp-api/src/modules/auth/constants/request-with-user.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/auth/constants/request-with-user.ts rename to apps/ebitemp-api/src/modules/auth/constants/request-with-user.ts diff --git a/apps/ebitemp-api/src/app/modules/auth/constants/token-payload.interface.ts b/apps/ebitemp-api/src/modules/auth/constants/token-payload.interface.ts similarity index 80% rename from apps/ebitemp-api/src/app/modules/auth/constants/token-payload.interface.ts rename to apps/ebitemp-api/src/modules/auth/constants/token-payload.interface.ts index d250bb5..1e0843c 100644 --- a/apps/ebitemp-api/src/app/modules/auth/constants/token-payload.interface.ts +++ b/apps/ebitemp-api/src/modules/auth/constants/token-payload.interface.ts @@ -1,4 +1,5 @@ export interface TokenPayload { + username: string; sub: number; iat?: number; exp?: number; diff --git a/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-access-token-auth.strategy.ts b/apps/ebitemp-api/src/modules/auth/strategies/jwt/jwt-access-token-auth.strategy.ts similarity index 74% rename from apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-access-token-auth.strategy.ts rename to apps/ebitemp-api/src/modules/auth/strategies/jwt/jwt-access-token-auth.strategy.ts index c3c0930..9331730 100644 --- a/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-access-token-auth.strategy.ts +++ b/apps/ebitemp-api/src/modules/auth/strategies/jwt/jwt-access-token-auth.strategy.ts @@ -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, + 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; } } diff --git a/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-access-token.module.ts b/apps/ebitemp-api/src/modules/auth/strategies/jwt/jwt-access-token.module.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-access-token.module.ts rename to apps/ebitemp-api/src/modules/auth/strategies/jwt/jwt-access-token.module.ts diff --git a/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-auth.guard.ts b/apps/ebitemp-api/src/modules/auth/strategies/jwt/jwt-auth.guard.ts similarity index 75% rename from apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-auth.guard.ts rename to apps/ebitemp-api/src/modules/auth/strategies/jwt/jwt-auth.guard.ts index 0c0484b..c4b064d 100644 --- a/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-auth.guard.ts +++ b/apps/ebitemp-api/src/modules/auth/strategies/jwt/jwt-auth.guard.ts @@ -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(); } diff --git a/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-refresh-token-auth.strategy.ts b/apps/ebitemp-api/src/modules/auth/strategies/jwt/jwt-refresh-token-auth.strategy.ts similarity index 73% rename from apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-refresh-token-auth.strategy.ts rename to apps/ebitemp-api/src/modules/auth/strategies/jwt/jwt-refresh-token-auth.strategy.ts index e69fd64..f13d4ce 100644 --- a/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-refresh-token-auth.strategy.ts +++ b/apps/ebitemp-api/src/modules/auth/strategies/jwt/jwt-refresh-token-auth.strategy.ts @@ -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, + 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; } } diff --git a/apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-refresh-token.module.ts b/apps/ebitemp-api/src/modules/auth/strategies/jwt/jwt-refresh-token.module.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/auth/strategies/jwt/jwt-refresh-token.module.ts rename to apps/ebitemp-api/src/modules/auth/strategies/jwt/jwt-refresh-token.module.ts diff --git a/apps/ebitemp-api/src/modules/auth/strategies/jwt/jwt-refresh.guard.ts b/apps/ebitemp-api/src/modules/auth/strategies/jwt/jwt-refresh.guard.ts new file mode 100644 index 0000000..9ea7bce --- /dev/null +++ b/apps/ebitemp-api/src/modules/auth/strategies/jwt/jwt-refresh.guard.ts @@ -0,0 +1,3 @@ +import { AuthGuard } from '@nestjs/passport'; + +export class JwtRefreshGuard extends AuthGuard('jwt-refresh-token') {} diff --git a/apps/ebitemp-api/src/app/modules/auth/users/users-auth.service.ts b/apps/ebitemp-api/src/modules/auth/users/users-auth.service.ts similarity index 75% rename from apps/ebitemp-api/src/app/modules/auth/users/users-auth.service.ts rename to apps/ebitemp-api/src/modules/auth/users/users-auth.service.ts index 55a68fc..9bc420e 100644 --- a/apps/ebitemp-api/src/app/modules/auth/users/users-auth.service.ts +++ b/apps/ebitemp-api/src/modules/auth/users/users-auth.service.ts @@ -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 ) {} @@ -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, }); diff --git a/apps/ebitemp-api/src/app/modules/auth/users/users.module.ts b/apps/ebitemp-api/src/modules/auth/users/users.module.ts similarity index 66% rename from apps/ebitemp-api/src/app/modules/auth/users/users.module.ts rename to apps/ebitemp-api/src/modules/auth/users/users.module.ts index e584aad..8b51800 100644 --- a/apps/ebitemp-api/src/app/modules/auth/users/users.module.ts +++ b/apps/ebitemp-api/src/modules/auth/users/users.module.ts @@ -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], diff --git a/apps/ebitemp-api/src/modules/cls/cls.interface.ts b/apps/ebitemp-api/src/modules/cls/cls.interface.ts new file mode 100644 index 0000000..cfe8737 --- /dev/null +++ b/apps/ebitemp-api/src/modules/cls/cls.interface.ts @@ -0,0 +1,6 @@ +import { ClsStore } from "nestjs-cls"; +import { AccountsEntity } from "../database/connections/ebitemp-api"; + +export interface AppClsStore extends ClsStore { + account: AccountsEntity|null; +} diff --git a/apps/ebitemp-api/src/modules/cls/cls.module.ts b/apps/ebitemp-api/src/modules/cls/cls.module.ts new file mode 100644 index 0000000..d4139bc --- /dev/null +++ b/apps/ebitemp-api/src/modules/cls/cls.module.ts @@ -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 {} diff --git a/apps/ebitemp-api/src/app/modules/config/config.module.ts b/apps/ebitemp-api/src/modules/config/config.module.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/config/config.module.ts rename to apps/ebitemp-api/src/modules/config/config.module.ts diff --git a/apps/ebitemp-api/src/app/modules/config/local.config.ts b/apps/ebitemp-api/src/modules/config/local.config.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/config/local.config.ts rename to apps/ebitemp-api/src/modules/config/local.config.ts diff --git a/apps/ebitemp-api/src/app/modules/config/utils/coerce-record-types.ts b/apps/ebitemp-api/src/modules/config/utils/coerce-record-types.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/config/utils/coerce-record-types.ts rename to apps/ebitemp-api/src/modules/config/utils/coerce-record-types.ts diff --git a/apps/ebitemp-api/src/app/modules/database/connections/ebitemp-api/database.config.ts b/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/database.config.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/connections/ebitemp-api/database.config.ts rename to apps/ebitemp-api/src/modules/database/connections/ebitemp-api/database.config.ts diff --git a/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/database.constants.ts b/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/database.constants.ts new file mode 100644 index 0000000..b908c66 --- /dev/null +++ b/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/database.constants.ts @@ -0,0 +1 @@ +export const EBITEMP_API_DATASOURCE = "EbitempApi"; diff --git a/apps/ebitemp-api/src/app/modules/database/connections/ebitemp-api/database.module.ts b/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/database.module.ts similarity index 82% rename from apps/ebitemp-api/src/app/modules/database/connections/ebitemp-api/database.module.ts rename to apps/ebitemp-api/src/modules/database/connections/ebitemp-api/database.module.ts index deb505f..31d0a65 100644 --- a/apps/ebitemp-api/src/app/modules/database/connections/ebitemp-api/database.module.ts +++ b/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/database.module.ts @@ -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; }, diff --git a/apps/ebitemp-api/src/app/modules/database/connections/ebitemp-api/entities/accounts.entity.ts b/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/accounts.entity.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/connections/ebitemp-api/entities/accounts.entity.ts rename to apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/accounts.entity.ts diff --git a/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/api_client.entity.ts b/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/api_client.entity.ts new file mode 100644 index 0000000..cdf7f09 --- /dev/null +++ b/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/api_client.entity.ts @@ -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) { + Object.assign(this, init); + } +} diff --git a/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/api_client_invocazioni.entity.ts b/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/api_client_invocazioni.entity.ts new file mode 100644 index 0000000..90285c0 --- /dev/null +++ b/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/api_client_invocazioni.entity.ts @@ -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) { + Object.assign(this, init); + } +} diff --git a/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/api_endpoint.entity.ts b/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/api_endpoint.entity.ts new file mode 100644 index 0000000..e5a98a6 --- /dev/null +++ b/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/api_endpoint.entity.ts @@ -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) { + Object.assign(this, init); + } +} diff --git a/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/api_endpoint_invocazioni.entity.ts b/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/api_endpoint_invocazioni.entity.ts new file mode 100644 index 0000000..11606a8 --- /dev/null +++ b/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/api_endpoint_invocazioni.entity.ts @@ -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) { + Object.assign(this, init); + } +} diff --git a/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/index.ts b/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/index.ts new file mode 100644 index 0000000..95b7a6f --- /dev/null +++ b/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/index.ts @@ -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'; diff --git a/apps/ebitemp-api/src/app/modules/database/connections/ebitemp-api/entities/profili.entity.ts b/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/profili.entity.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/connections/ebitemp-api/entities/profili.entity.ts rename to apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/profili.entity.ts diff --git a/apps/ebitemp-api/src/app/modules/database/connections/ebitemp-api/entities/ruoli.entity.ts b/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/ruoli.entity.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/connections/ebitemp-api/entities/ruoli.entity.ts rename to apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/ruoli.entity.ts diff --git a/apps/ebitemp-api/src/app/modules/database/connections/ebitemp-api/entities/tipi_jobs.entity.ts b/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/tipi_jobs.entity.ts similarity index 95% rename from apps/ebitemp-api/src/app/modules/database/connections/ebitemp-api/entities/tipi_jobs.entity.ts rename to apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/tipi_jobs.entity.ts index 51cf023..959080b 100644 --- a/apps/ebitemp-api/src/app/modules/database/connections/ebitemp-api/entities/tipi_jobs.entity.ts +++ b/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/entities/tipi_jobs.entity.ts @@ -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)' }) diff --git a/apps/ebitemp-api/src/app/modules/database/connections/ebitemp-api/index.ts b/apps/ebitemp-api/src/modules/database/connections/ebitemp-api/index.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/connections/ebitemp-api/index.ts rename to apps/ebitemp-api/src/modules/database/connections/ebitemp-api/index.ts diff --git a/apps/ebitemp-api/src/app/modules/database/connections/oceano/database.config.ts b/apps/ebitemp-api/src/modules/database/connections/oceano/database.config.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/connections/oceano/database.config.ts rename to apps/ebitemp-api/src/modules/database/connections/oceano/database.config.ts diff --git a/apps/ebitemp-api/src/modules/database/connections/oceano/database.constants.ts b/apps/ebitemp-api/src/modules/database/connections/oceano/database.constants.ts new file mode 100644 index 0000000..cb968c1 --- /dev/null +++ b/apps/ebitemp-api/src/modules/database/connections/oceano/database.constants.ts @@ -0,0 +1 @@ +export const OCEANO_DATASOURCE = "Oceano"; diff --git a/apps/ebitemp-api/src/app/modules/database/connections/oceano/database.module.ts b/apps/ebitemp-api/src/modules/database/connections/oceano/database.module.ts similarity index 83% rename from apps/ebitemp-api/src/app/modules/database/connections/oceano/database.module.ts rename to apps/ebitemp-api/src/modules/database/connections/oceano/database.module.ts index 63431cc..b70c5d3 100644 --- a/apps/ebitemp-api/src/app/modules/database/connections/oceano/database.module.ts +++ b/apps/ebitemp-api/src/modules/database/connections/oceano/database.module.ts @@ -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; }, diff --git a/apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/API_CAP_Luoghi.entity.ts b/apps/ebitemp-api/src/modules/database/connections/oceano/entities/API_CAP_Luoghi.entity.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/API_CAP_Luoghi.entity.ts rename to apps/ebitemp-api/src/modules/database/connections/oceano/entities/API_CAP_Luoghi.entity.ts diff --git a/apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/API_Luoghi.entity.ts b/apps/ebitemp-api/src/modules/database/connections/oceano/entities/API_Luoghi.entity.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/API_Luoghi.entity.ts rename to apps/ebitemp-api/src/modules/database/connections/oceano/entities/API_Luoghi.entity.ts diff --git a/apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/API_Luoghi_Estero.entity.ts b/apps/ebitemp-api/src/modules/database/connections/oceano/entities/API_Luoghi_Estero.entity.ts similarity index 69% rename from apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/API_Luoghi_Estero.entity.ts rename to apps/ebitemp-api/src/modules/database/connections/oceano/entities/API_Luoghi_Estero.entity.ts index f366b8c..1afe9ae 100644 --- a/apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/API_Luoghi_Estero.entity.ts +++ b/apps/ebitemp-api/src/modules/database/connections/oceano/entities/API_Luoghi_Estero.entity.ts @@ -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 }) diff --git a/apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/allegati.entity.ts b/apps/ebitemp-api/src/modules/database/connections/oceano/entities/allegati.entity.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/allegati.entity.ts rename to apps/ebitemp-api/src/modules/database/connections/oceano/entities/allegati.entity.ts diff --git a/apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/contratto.entity.ts b/apps/ebitemp-api/src/modules/database/connections/oceano/entities/contratto.entity.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/contratto.entity.ts rename to apps/ebitemp-api/src/modules/database/connections/oceano/entities/contratto.entity.ts diff --git a/apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/ente.entity.ts b/apps/ebitemp-api/src/modules/database/connections/oceano/entities/ente.entity.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/ente.entity.ts rename to apps/ebitemp-api/src/modules/database/connections/oceano/entities/ente.entity.ts diff --git a/apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/formeAssistenza.entity.ts b/apps/ebitemp-api/src/modules/database/connections/oceano/entities/formeAssistenza.entity.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/formeAssistenza.entity.ts rename to apps/ebitemp-api/src/modules/database/connections/oceano/entities/formeAssistenza.entity.ts diff --git a/apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/gruppo.entity.ts b/apps/ebitemp-api/src/modules/database/connections/oceano/entities/gruppo.entity.ts similarity index 99% rename from apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/gruppo.entity.ts rename to apps/ebitemp-api/src/modules/database/connections/oceano/entities/gruppo.entity.ts index 13a6e81..5b0790e 100644 --- a/apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/gruppo.entity.ts +++ b/apps/ebitemp-api/src/modules/database/connections/oceano/entities/gruppo.entity.ts @@ -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; diff --git a/apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/index.ts b/apps/ebitemp-api/src/modules/database/connections/oceano/entities/index.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/index.ts rename to apps/ebitemp-api/src/modules/database/connections/oceano/entities/index.ts diff --git a/apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/missioni.entity.ts b/apps/ebitemp-api/src/modules/database/connections/oceano/entities/missioni.entity.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/missioni.entity.ts rename to apps/ebitemp-api/src/modules/database/connections/oceano/entities/missioni.entity.ts diff --git a/apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/socio.entity.ts b/apps/ebitemp-api/src/modules/database/connections/oceano/entities/socio.entity.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/socio.entity.ts rename to apps/ebitemp-api/src/modules/database/connections/oceano/entities/socio.entity.ts diff --git a/apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/socioContratto.entity.ts b/apps/ebitemp-api/src/modules/database/connections/oceano/entities/socioContratto.entity.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/socioContratto.entity.ts rename to apps/ebitemp-api/src/modules/database/connections/oceano/entities/socioContratto.entity.ts diff --git a/apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/socioFormeAssistenza.entity.ts b/apps/ebitemp-api/src/modules/database/connections/oceano/entities/socioFormeAssistenza.entity.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/socioFormeAssistenza.entity.ts rename to apps/ebitemp-api/src/modules/database/connections/oceano/entities/socioFormeAssistenza.entity.ts diff --git a/apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/sportelli.entity.ts b/apps/ebitemp-api/src/modules/database/connections/oceano/entities/sportelli.entity.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/connections/oceano/entities/sportelli.entity.ts rename to apps/ebitemp-api/src/modules/database/connections/oceano/entities/sportelli.entity.ts diff --git a/apps/ebitemp-api/src/app/modules/database/connections/oceano/index.ts b/apps/ebitemp-api/src/modules/database/connections/oceano/index.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/connections/oceano/index.ts rename to apps/ebitemp-api/src/modules/database/connections/oceano/index.ts diff --git a/apps/ebitemp-api/src/app/modules/database/database.constants.ts b/apps/ebitemp-api/src/modules/database/database.constants.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/database.constants.ts rename to apps/ebitemp-api/src/modules/database/database.constants.ts diff --git a/apps/ebitemp-api/src/app/modules/database/database.module.ts b/apps/ebitemp-api/src/modules/database/database.module.ts similarity index 77% rename from apps/ebitemp-api/src/app/modules/database/database.module.ts rename to apps/ebitemp-api/src/modules/database/database.module.ts index a395761..54dc272 100644 --- a/apps/ebitemp-api/src/app/modules/database/database.module.ts +++ b/apps/ebitemp-api/src/modules/database/database.module.ts @@ -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({ diff --git a/apps/ebitemp-api/src/app/modules/database/utils/database-config.ts b/apps/ebitemp-api/src/modules/database/utils/database-config.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/utils/database-config.ts rename to apps/ebitemp-api/src/modules/database/utils/database-config.ts diff --git a/apps/ebitemp-api/src/app/modules/database/utils/typeorm-data-source-factory.ts b/apps/ebitemp-api/src/modules/database/utils/typeorm-data-source-factory.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/utils/typeorm-data-source-factory.ts rename to apps/ebitemp-api/src/modules/database/utils/typeorm-data-source-factory.ts diff --git a/apps/ebitemp-api/src/app/modules/database/utils/typeorm-import-entities.ts b/apps/ebitemp-api/src/modules/database/utils/typeorm-import-entities.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/utils/typeorm-import-entities.ts rename to apps/ebitemp-api/src/modules/database/utils/typeorm-import-entities.ts diff --git a/apps/ebitemp-api/src/app/modules/database/utils/typeorm-module-options-factory.ts b/apps/ebitemp-api/src/modules/database/utils/typeorm-module-options-factory.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/utils/typeorm-module-options-factory.ts rename to apps/ebitemp-api/src/modules/database/utils/typeorm-module-options-factory.ts diff --git a/apps/ebitemp-api/src/app/modules/database/utils/typeorm-patch.ts b/apps/ebitemp-api/src/modules/database/utils/typeorm-patch.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/database/utils/typeorm-patch.ts rename to apps/ebitemp-api/src/modules/database/utils/typeorm-patch.ts diff --git a/apps/ebitemp-api/src/app/modules/enumify/enumify-update-from-database.service.ts b/apps/ebitemp-api/src/modules/enumify/enumify-update-from-database.service.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/enumify/enumify-update-from-database.service.ts rename to apps/ebitemp-api/src/modules/enumify/enumify-update-from-database.service.ts diff --git a/apps/ebitemp-api/src/app/modules/enumify/enumify.config.ts b/apps/ebitemp-api/src/modules/enumify/enumify.config.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/enumify/enumify.config.ts rename to apps/ebitemp-api/src/modules/enumify/enumify.config.ts diff --git a/apps/ebitemp-api/src/app/modules/enumify/enumify.module.ts b/apps/ebitemp-api/src/modules/enumify/enumify.module.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/enumify/enumify.module.ts rename to apps/ebitemp-api/src/modules/enumify/enumify.module.ts diff --git a/apps/ebitemp-api/src/app/modules/enumify/enumify.ts b/apps/ebitemp-api/src/modules/enumify/enumify.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/enumify/enumify.ts rename to apps/ebitemp-api/src/modules/enumify/enumify.ts diff --git a/apps/ebitemp-api/src/app/modules/file-transactions/file-transaction.ts b/apps/ebitemp-api/src/modules/file-transactions/file-transaction.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/file-transactions/file-transaction.ts rename to apps/ebitemp-api/src/modules/file-transactions/file-transaction.ts diff --git a/apps/ebitemp-api/src/app/modules/file-transactions/file-transactions.module.ts b/apps/ebitemp-api/src/modules/file-transactions/file-transactions.module.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/file-transactions/file-transactions.module.ts rename to apps/ebitemp-api/src/modules/file-transactions/file-transactions.module.ts diff --git a/apps/ebitemp-api/src/app/modules/file-transactions/file-transactions.service.ts b/apps/ebitemp-api/src/modules/file-transactions/file-transactions.service.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/file-transactions/file-transactions.service.ts rename to apps/ebitemp-api/src/modules/file-transactions/file-transactions.service.ts diff --git a/apps/ebitemp-api/src/app/modules/file-transactions/file-utils.ts b/apps/ebitemp-api/src/modules/file-transactions/file-utils.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/file-transactions/file-utils.ts rename to apps/ebitemp-api/src/modules/file-transactions/file-utils.ts diff --git a/apps/ebitemp-api/src/app/modules/health/health.controller.ts b/apps/ebitemp-api/src/modules/health/health.controller.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/health/health.controller.ts rename to apps/ebitemp-api/src/modules/health/health.controller.ts diff --git a/apps/ebitemp-api/src/app/modules/health/health.module.ts b/apps/ebitemp-api/src/modules/health/health.module.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/health/health.module.ts rename to apps/ebitemp-api/src/modules/health/health.module.ts diff --git a/apps/ebitemp-api/src/app/modules/keyv/keyv.config.ts b/apps/ebitemp-api/src/modules/keyv/keyv.config.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/keyv/keyv.config.ts rename to apps/ebitemp-api/src/modules/keyv/keyv.config.ts diff --git a/apps/ebitemp-api/src/app/modules/keyv/keyv.module.ts b/apps/ebitemp-api/src/modules/keyv/keyv.module.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/keyv/keyv.module.ts rename to apps/ebitemp-api/src/modules/keyv/keyv.module.ts diff --git a/apps/ebitemp-api/src/app/modules/keyv/keyv.service.ts b/apps/ebitemp-api/src/modules/keyv/keyv.service.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/keyv/keyv.service.ts rename to apps/ebitemp-api/src/modules/keyv/keyv.service.ts diff --git a/apps/ebitemp-api/src/app/modules/logger/logger.config.ts b/apps/ebitemp-api/src/modules/logger/logger.config.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/logger/logger.config.ts rename to apps/ebitemp-api/src/modules/logger/logger.config.ts diff --git a/apps/ebitemp-api/src/app/modules/logger/logger.module.ts b/apps/ebitemp-api/src/modules/logger/logger.module.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/logger/logger.module.ts rename to apps/ebitemp-api/src/modules/logger/logger.module.ts diff --git a/apps/ebitemp-api/src/modules/request-logging/clients/clients.module.ts b/apps/ebitemp-api/src/modules/request-logging/clients/clients.module.ts new file mode 100644 index 0000000..1a6e715 --- /dev/null +++ b/apps/ebitemp-api/src/modules/request-logging/clients/clients.module.ts @@ -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 {} diff --git a/apps/ebitemp-api/src/modules/request-logging/clients/clients.service.ts b/apps/ebitemp-api/src/modules/request-logging/clients/clients.service.ts new file mode 100644 index 0000000..9cc9819 --- /dev/null +++ b/apps/ebitemp-api/src/modules/request-logging/clients/clients.service.ts @@ -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, + @InjectRepository(ApiClientInvocazioniEntity, EBITEMP_API_DATASOURCE) + private readonly clientInvocazioniRepository: Repository + ) { + 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(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; + }); + } +} diff --git a/apps/ebitemp-api/src/modules/request-logging/endpoints/endpoints.module.ts b/apps/ebitemp-api/src/modules/request-logging/endpoints/endpoints.module.ts new file mode 100644 index 0000000..be04e62 --- /dev/null +++ b/apps/ebitemp-api/src/modules/request-logging/endpoints/endpoints.module.ts @@ -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 {} diff --git a/apps/ebitemp-api/src/modules/request-logging/endpoints/endpoints.service.ts b/apps/ebitemp-api/src/modules/request-logging/endpoints/endpoints.service.ts new file mode 100644 index 0000000..69e32cc --- /dev/null +++ b/apps/ebitemp-api/src/modules/request-logging/endpoints/endpoints.service.ts @@ -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, + @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; + }); + } +} diff --git a/apps/ebitemp-api/src/modules/request-logging/request-logger.interceptor.ts b/apps/ebitemp-api/src/modules/request-logging/request-logger.interceptor.ts new file mode 100644 index 0000000..883ed38 --- /dev/null +++ b/apps/ebitemp-api/src/modules/request-logging/request-logger.interceptor.ts @@ -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> { + 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; + }) + ); + } +} diff --git a/apps/ebitemp-api/src/modules/request-logging/request-logging.module.ts b/apps/ebitemp-api/src/modules/request-logging/request-logging.module.ts new file mode 100644 index 0000000..f3fa1ec --- /dev/null +++ b/apps/ebitemp-api/src/modules/request-logging/request-logging.module.ts @@ -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 {} diff --git a/apps/ebitemp-api/src/app/modules/schedule/schedule.module.ts b/apps/ebitemp-api/src/modules/schedule/schedule.module.ts similarity index 64% rename from apps/ebitemp-api/src/app/modules/schedule/schedule.module.ts rename to apps/ebitemp-api/src/modules/schedule/schedule.module.ts index 4e4b6d7..6cd0bd2 100644 --- a/apps/ebitemp-api/src/app/modules/schedule/schedule.module.ts +++ b/apps/ebitemp-api/src/modules/schedule/schedule.module.ts @@ -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: [], }) diff --git a/apps/ebitemp-api/src/app/modules/schedule/schedule.service.ts b/apps/ebitemp-api/src/modules/schedule/schedule.service.ts similarity index 88% rename from apps/ebitemp-api/src/app/modules/schedule/schedule.service.ts rename to apps/ebitemp-api/src/modules/schedule/schedule.service.ts index c014727..f5a4432 100644 --- a/apps/ebitemp-api/src/app/modules/schedule/schedule.service.ts +++ b/apps/ebitemp-api/src/modules/schedule/schedule.service.ts @@ -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 + @InjectRepository(TipiJobsEntity, EBITEMP_API_DATASOURCE) + private readonly tipiJobsRepository: Repository ) {} async onApplicationBootstrap() { diff --git a/apps/ebitemp-api/src/app/modules/validation/utils/nestjs-swagger-patches.ts b/apps/ebitemp-api/src/modules/validation/utils/nestjs-swagger-patches.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/validation/utils/nestjs-swagger-patches.ts rename to apps/ebitemp-api/src/modules/validation/utils/nestjs-swagger-patches.ts diff --git a/apps/ebitemp-api/src/app/modules/validation/validation.module.ts b/apps/ebitemp-api/src/modules/validation/validation.module.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/validation/validation.module.ts rename to apps/ebitemp-api/src/modules/validation/validation.module.ts diff --git a/apps/ebitemp-api/src/app/modules/validation/zod.pipe.ts b/apps/ebitemp-api/src/modules/validation/zod.pipe.ts similarity index 100% rename from apps/ebitemp-api/src/app/modules/validation/zod.pipe.ts rename to apps/ebitemp-api/src/modules/validation/zod.pipe.ts diff --git a/apps/ebitemp-api/src/app/modules/validation/zod.serializer.ts b/apps/ebitemp-api/src/modules/validation/zod.serializer.ts similarity index 98% rename from apps/ebitemp-api/src/app/modules/validation/zod.serializer.ts rename to apps/ebitemp-api/src/modules/validation/zod.serializer.ts index c2902e6..bc8b916 100644 --- a/apps/ebitemp-api/src/app/modules/validation/zod.serializer.ts +++ b/apps/ebitemp-api/src/modules/validation/zod.serializer.ts @@ -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( diff --git a/package.json b/package.json index 62b904d..72c7c30 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 64d8d6c..c7f0ba8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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: {} diff --git a/tools/typeorm/src/generators/typeorm-model-generator/schema.json b/tools/typeorm/src/generators/typeorm-model-generator/schema.json index 34ebb40..9ab4598 100644 --- a/tools/typeorm/src/generators/typeorm-model-generator/schema.json +++ b/tools/typeorm/src/generators/typeorm-model-generator/schema.json @@ -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.",