From 807319460425945731a81cb949866d562080249d Mon Sep 17 00:00:00 2001 From: Rafael Date: Tue, 2 Dec 2025 15:10:31 -0300 Subject: [PATCH 01/41] fix: make Redis TLS configurable via REDIS_TLS env var MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change TLS check from ENV !== 'local' to REDIS_TLS === 'true' - Add REDIS_TLS env var to Helm deployment template - Default to true for OCI Redis, can override for self-hosted K8s 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- deploy/helm-chart/templates/deployment.yaml | 2 ++ deploy/helm-chart/values.yaml | 1 + environment.d.ts | 1 + src/services/service.module.ts | 2 +- 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/deploy/helm-chart/templates/deployment.yaml b/deploy/helm-chart/templates/deployment.yaml index 013aa06..63524f3 100644 --- a/deploy/helm-chart/templates/deployment.yaml +++ b/deploy/helm-chart/templates/deployment.yaml @@ -104,6 +104,8 @@ spec: value: {{ .Values.maestro.redis_host }} - name: REDIS_PORT value: "{{ .Values.maestro.redis_port }}" + - name: REDIS_TLS + value: "{{ .Values.maestro.redis_tls }}" - name: JWT_PRIVATE_KEY valueFrom: secretKeyRef: diff --git a/deploy/helm-chart/values.yaml b/deploy/helm-chart/values.yaml index 0c4db2e..2f703b7 100644 --- a/deploy/helm-chart/values.yaml +++ b/deploy/helm-chart/values.yaml @@ -48,6 +48,7 @@ maestro: redis_host: "aaapzppmlyamkocqwstpo7zvopczyyiyuy6xzm2g6c5k4mq3a66be4a-0.redis.sa-saopaulo-1.oci.oraclecloud.com" redis_port: "6379" redis_database: "0" + redis_tls: "true" cookie_secret: "13cc5e136d3074bcc05bec8697092ec1f5f376bf" autoscaling: enabled: false diff --git a/environment.d.ts b/environment.d.ts index 3b44250..85d1a52 100644 --- a/environment.d.ts +++ b/environment.d.ts @@ -16,6 +16,7 @@ declare global { OPEN_CUSTOMER_ID: string; DEDICATED_PROXY: string; COOKIE_SECRET: string; + REDIS_TLS?: string; } } } diff --git a/src/services/service.module.ts b/src/services/service.module.ts index 0286b01..8bf406d 100644 --- a/src/services/service.module.ts +++ b/src/services/service.module.ts @@ -14,7 +14,7 @@ import { redisStore } from 'cache-manager-ioredis-yet'; keyPrefix: 'maestro:sso', } - if (process.env.ENV !== 'local') { + if (process.env.REDIS_TLS === 'true') { baseRedisConfig['tls'] = { servername: process.env.REDIS_HOST, } From e789076ed4bfb581affa92e6725883c78b43d09e Mon Sep 17 00:00:00 2001 From: Rafael Date: Tue, 2 Dec 2025 15:29:36 -0300 Subject: [PATCH 02/41] UPDATE: force deployment --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bb50d3a..4d4a5b1 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@

+ # Maestro Maestro é a API principal da Dadosfera. É responsável pela comunicação do Frontend com nossos microsserviços. From 7616b1e32c88fb40e9ec5731fd05d6b79a5d8c7c Mon Sep 17 00:00:00 2001 From: Marcos Rodrigues Date: Wed, 3 Dec 2025 13:51:32 -0300 Subject: [PATCH 03/41] FEAT: add resource origin validate in me endpoint --- src/modules/auth/auth.controller.ts | 125 ++++---------- src/modules/auth/auth.service.ts | 247 ++++++++++++++++++++++------ 2 files changed, 226 insertions(+), 146 deletions(-) diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index 9a4fb01..c648a41 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -13,6 +13,7 @@ import { Req, Param, Res, + UnauthorizedException, } from '@nestjs/common'; import { ApiHeaders, @@ -102,14 +103,13 @@ export class AuthController { const data = await this.authClient.signIn({ username, password, totp }, metadata); if (data.tokens) { - this.addTokenInCookie(res, { + this.authClient.writeAuthSession(res, { accessToken: data.tokens.accessToken, refreshToken: data.tokens.refreshToken, userId: data.user.id }); } - return res.send(data); } catch (error) { this.logger.error('/auth - SignIn - ERROR', error); @@ -126,27 +126,8 @@ export class AuthController { ) { try { this.logger.info('/auth - SignOut'); - const exp = 1000 * 60 * 3; - - res.cookie('ddf-auth', '', { - domain: 'dadosfera.local', - maxAge: Date.now() - exp, - expires: new Date(), - httpOnly: true, - secure: true, - sameSite: 'none', // Necessário para cookies em requisições cross-site - }); - - res.cookie('ddf-refresh-auth', '', { - domain: 'dadosfera.local', - maxAge: Date.now() - exp, - expires: new Date(), - httpOnly: true, - secure: true, - sameSite: 'none', // Necessário para cookies em requisições cross-site - }); - - this.logger.info('Clean cookie sessions'); + + this.authClient.cleanUpAuthSession(res); return res.send(); } catch (error) { @@ -175,8 +156,9 @@ export class AuthController { const data = await this.authClient.refreshAccessToken({ refreshToken, userId }, metadata); - this.addTokenInCookie(res, { + this.authClient.writeAuthSession(res, { accessToken: data.accessToken, + refreshToken: data.refreshToken, userId }); @@ -492,85 +474,36 @@ export class AuthController { @Get('me') async getMe(@Req() req: Request, @Res() res: Response) { this.logger.info('GET /auth/me ') - // Lê cookies + + // Get token and headers const accessToken = req.cookies['ddf-auth']; - const userId = req.cookies['ddf-user-id']; + const refreshToken = req.cookies['ddf-refresh-auth']; + const userId = req.cookies['ddf-user-id']; + const resourceOrigin = req.headers["origin"] + + const hasUserSession = Boolean(accessToken) && Boolean(userId); + this.logger.info('Has User Session: ' + hasUserSession); + + if (!hasUserSession) { + throw new UnauthorizedException() + } - this.logger.info('Has cookie: ' + Boolean(accessToken)) try { - // Decodifica e valida o JWT de acesso - const userDto = await this.authClient.extractUserFrom(accessToken); + const userDto = await this.authClient.validateUserSession(accessToken, resourceOrigin); return res.status(200).json(userDto); - } catch (err) { - this.logger.error(err.message); - const refreshToken = req.cookies['ddf-refresh-auth']; + } catch (error) { - this.logger.info('Token is invalid') - this.logger.info('Has Refresh Token: '+ Boolean(refreshToken)) - // Se access token inválido, tenta refresh - if (!refreshToken || !userId) { + if (!refreshToken) { this.logger.error('Invalid refresh token or customer name'); - return res.status(401).json({ error: 'Not authenticated' }); - } - try { - // Chama refreshAccessToken - const metadata = PackTheMetadata({ - }); - this.logger.info('Call Refresh Token') - const data = await this.authClient.refreshAccessToken({ refreshToken, userId }, metadata); - this.logger.info('Finish Refresh Token') - // Retorna novo access token e dados mínimos - this.addTokenInCookie(res, { - accessToken: data.accessToken, - userId - }); - // Decodifica novo token - const userDto = await this.authClient.extractUserFrom(accessToken); - return res.status(200).json(userDto); - } catch (refreshErr) { - this.logger.error(refreshErr) - return res.status(401).json({ error: 'Not authenticated' }); - } - } - } + throw new UnauthorizedException("Invalid refresh token or customer name"); + }; - private addTokenInCookie(res: Response, data: CookiesValues) { - let exp = 1000 * 60 * 5; // 5 minutes - - if (data.accessToken) { - const { exp: expiration } = jwt.decode(data.accessToken) as JwtPayload; - exp = (expiration - 30) * 1000; // exp em segundos, maxAge em ms - - this.logger.info('Set Cookie ddf-auth') - res.cookie('ddf-auth', data.accessToken, { - domain: '.dadosfera.ai', - maxAge: exp, - httpOnly: true, - secure: true, - sameSite: 'none', // Necessário para cookies em requisições cross-site - }); - } - - if (data.refreshToken) { - this.logger.info('Set Cookie ddf-refresh-auth') - res.cookie('ddf-refresh-auth', data.refreshToken, { - domain: '.dadosfera.ai', - maxAge: exp, - httpOnly: true, - secure: true, - sameSite: 'none', // Necessário para cookies em requisições cross-site - }); - } - - if (data.userId) { - this.logger.info('Set Cookie ddf-refresh-auth') - res.cookie('ddf-user-id', data.userId, { - domain: '.dadosfera.ai', - maxAge: exp, - httpOnly: true, - secure: true, - sameSite: 'none', // Necessário para cookies em requisições cross-site - }); + const { + authSession, + user + } = await this.authClient.refreshUserSession(refreshToken, userId, resourceOrigin); + this.authClient.writeAuthSession(res, authSession); + return res.status(200).json(user); } } } diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 3a17327..3133462 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -1,10 +1,20 @@ -import { OnModuleInit, Inject, Injectable, ForbiddenException } from '@nestjs/common'; +import { + OnModuleInit, + Inject, + Injectable, + ForbiddenException, + UnauthorizedException, +} from '@nestjs/common'; import { ClientGrpc } from '@nestjs/microservices'; import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { lastValueFrom } from 'rxjs'; import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc'; -import { AuthProtoService as AuthServiceInterface, IdentityProviderProtoService, UsersProtoService } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service'; +import { + AuthProtoService as AuthServiceInterface, + IdentityProviderProtoService, + UsersProtoService, +} from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service'; import { AuthSnowflakeSignInRequest, AuthSignInRequest, @@ -22,12 +32,18 @@ import { import { DucClient } from '../duc/client.config'; import { Metadata } from '@grpc/grpc-js'; import { BulkEditResponse, UserDTO } from './dtos/login'; -import jwt from 'jsonwebtoken'; +import jwt, { JwtPayload } from 'jsonwebtoken'; import { PackTheMetadata } from 'src/utils/PackTheMetadata'; +import { Request, Response } from 'express'; + +type AuthSession = { + accessToken?: string; + refreshToken?: string; + userId?: string; +}; @Injectable() export class AuthClientService implements OnModuleInit { - logger: DadosferaLogger; private authService: AuthServiceInterface; @@ -63,11 +79,11 @@ export class AuthClientService implements OnModuleInit { return lastValueFrom(this.authService.AuthSnowflakeSignIn(input)); } - checkDedicatedProxy({ - customer - }: AuthSignInResponse) { + checkDedicatedProxy({ customer }: AuthSignInResponse) { const DEDICATED_PROXY = process.env.DEDICATED_PROXY || ''; - this.logger.info('SignIn - Setting customer ID for dedicated proxy: ' + DEDICATED_PROXY); + this.logger.info( + 'SignIn - Setting customer ID for dedicated proxy: ' + DEDICATED_PROXY, + ); this.logger.info('Customer ID: ' + customer.id); if (DEDICATED_PROXY !== '' && DEDICATED_PROXY !== customer.id) { @@ -75,7 +91,9 @@ export class AuthClientService implements OnModuleInit { } // Bloquear o customer de acesso o maestro publico - this.logger.info('Check if customer have network policy: ' + customer.modules); + this.logger.info( + 'Check if customer have network policy: ' + customer.modules, + ); const hasNetworkPolicyModule = customer.modules.includes('network-policy'); if (hasNetworkPolicyModule && DEDICATED_PROXY === '') { throw new ForbiddenException(); @@ -94,7 +112,6 @@ export class AuthClientService implements OnModuleInit { result = await lastValueFrom( this.authService.AuthSignIn({ username, password, totp }, metadata), ); - } catch (error) { this.logger.error('SignIn - Error during sign-in'); this.logger.error(error); @@ -105,7 +122,7 @@ export class AuthClientService implements OnModuleInit { this.checkDedicatedProxy(result); } - return result + return result; } async refreshAccessToken( @@ -115,7 +132,10 @@ export class AuthClientService implements OnModuleInit { this.logger.info('RefreshAccessToken'); return lastValueFrom( - this.authService.AuthRefreshAccessToken({ refreshToken, userId }, metadata), + this.authService.AuthRefreshAccessToken( + { refreshToken, userId }, + metadata, + ), ); } @@ -284,50 +304,177 @@ export class AuthClientService implements OnModuleInit { } } - private async getUser(id: string, metadata: Metadata) { - this.logger.info('getUser'); - return await lastValueFrom( - this.userService.UserFindOneById({ id }, metadata), - ); + public async validateUserSession(accessToken: any, originHeader: string) { + const payload = await this.validateJwtToken(accessToken); + + const userDto = await this.getUserfromPayload(payload); + + this.validateResourceAccess(originHeader, userDto); + return userDto; } - public async extractUserFrom(token: string) { - const decoded: any = token && jwt.decode(token, { complete: true }); - if (!decoded) throw new Error('Invalid token'); + public async refreshUserSession( + refreshToken: string, + userId: string, + originHeader: string, + ): Promise<{ + user: UserDTO; + authSession: AuthSession; + }> { + const metadata = PackTheMetadata({}); - const { kid } = decoded.header; - // Busca a chave pública - const { keys } = await this.getPublicKeys(); - const pemValue = keys.find((k) => k.kid === kid)?.pem; - if (!pemValue) - throw new Error('Public key not found'); - jwt.verify(token, pemValue); + this.logger.info('Call Refresh Token'); + const refreshCredentials = await this.refreshAccessToken( + { refreshToken, userId }, + metadata, + ); + this.logger.info('Finish Refresh Token'); - const payload = decoded.payload; - const metadata = PackTheMetadata({ - customer_id: payload.customer_id - }) + const userDto = await this.validateUserSession( + refreshCredentials.accessToken, + originHeader, + ); + return { + user: userDto, + authSession: { + accessToken: refreshCredentials.accessToken, + refreshToken: refreshCredentials.refreshToken, + userId, + }, + }; + } - const { - user - } = await this.getUser( - payload.user_id, - metadata - ); + public writeAuthSession(res: Response, data: AuthSession) { + let exp = 1000 * 60 * 5; // 5 minutes - const userDto: UserDTO = { - id: user.id, - name: user.username, - jobTitle: user?.jobTitle || null, - department: user?.department || null, - hierarchy: user?.hierarchy || null, - customer: { - id: payload.customer_id, - name: payload.customer_name, - tier: payload.customer_tier, - } - }; + if (data.accessToken) { + const { exp: expiration } = jwt.decode(data.accessToken) as JwtPayload; + exp = (expiration - 30) * 1000; // exp em segundos, maxAge em ms - return userDto; + this.logger.info('Set Cookie ddf-auth'); + res.cookie('ddf-auth', data.accessToken, { + domain: '.dadosfera.ai', + maxAge: exp, + httpOnly: true, + secure: true, + sameSite: 'none', // Necessário para cookies em requisições cross-site + }); } + + if (data.refreshToken) { + this.logger.info('Set Cookie ddf-refresh-auth'); + res.cookie('ddf-refresh-auth', data.refreshToken, { + domain: '.dadosfera.ai', + maxAge: exp, + httpOnly: true, + secure: true, + sameSite: 'none', // Necessário para cookies em requisições cross-site + }); + } + + if (data.userId) { + this.logger.info('Set Cookie ddf-refresh-auth'); + res.cookie('ddf-user-id', data.userId, { + domain: '.dadosfera.ai', + maxAge: exp, + httpOnly: true, + secure: true, + sameSite: 'none', // Necessário para cookies em requisições cross-site + }); + } + } + + public cleanUpAuthSession(res: Response) { + const exp = 1000 * 60 * 3; + + res.cookie('ddf-auth', '', { + domain: 'dadosfera.ai', + maxAge: Date.now() - exp, + expires: new Date(), + httpOnly: true, + secure: true, + sameSite: 'none', // Necessário para cookies em requisições cross-site + }); + + res.cookie('ddf-refresh-auth', '', { + domain: 'dadosfera.ai', + maxAge: Date.now() - exp, + expires: new Date(), + httpOnly: true, + secure: true, + sameSite: 'none', // Necessário para cookies em requisições cross-site + }); + + this.logger.info('Clean cookie sessions'); + } + + private async validateJwtToken(token: string) { + const decoded: any = token && jwt.decode(token, { complete: true }); + if (!decoded) throw new Error('Invalid token'); + + const { kid } = decoded.header; + // Busca a chave pública + const { keys } = await this.getPublicKeys(); + const pemValue = keys.find((k) => k.kid === kid)?.pem; + if (!pemValue) throw new Error('Public key not found'); + jwt.verify(token, pemValue); + + return decoded.payload; + } + + private async getUserfromPayload(payload: JwtPayload): Promise { + this.logger.info('getUser'); + + const metadata = PackTheMetadata({ + customer_id: payload.customer_id, + }); + + const { user } = await lastValueFrom( + this.userService.UserFindOneById({ id: payload.user_id }, metadata), + ); + + const userDto: UserDTO = { + id: user.id, + name: user.username, + jobTitle: user?.jobTitle || null, + department: user?.department || null, + hierarchy: user?.hierarchy || null, + customer: { + id: payload.customer_id, + name: payload.customer_name, + tier: payload.customer_tier, + }, + }; + + return userDto; + } + + private validateResourceAccess(origin: string, user: UserDTO) { + this.logger.info( + "Validate whether the source URL is a resource belonging to the user's client", + ); + this.logger.info('Origin: ' + origin); + this.logger.info('Customer: ' + user.customer.name); + + const urlParts = origin.replace('https://', '').split('.'); + const domain = urlParts[0]; + const isResouceStg = urlParts[1] === 'stg'; + + const domainParts = domain.split('-'); + const customerInDomain = domainParts[domainParts.length - 1]; + + if (isResouceStg && process.env.ENV !== 'stg') { + throw new ForbiddenException( + `Customer ${user.customer.name} cannot access ${origin}`, + ); + } + + if (customerInDomain != user.customer.name) { + throw new ForbiddenException( + `Customer ${user.customer.name} cannot access ${origin}`, + ); + } + + return; + } } From 82290285d015bdacfeaa01614937e8f7abbb63f0 Mon Sep 17 00:00:00 2001 From: Marcos Rodrigues Date: Wed, 3 Dec 2025 14:41:51 -0300 Subject: [PATCH 04/41] FIX: using header host --- src/modules/auth/auth.controller.ts | 6 +++--- src/modules/auth/auth.service.ts | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/modules/auth/auth.controller.ts b/src/modules/auth/auth.controller.ts index c648a41..3d86f14 100644 --- a/src/modules/auth/auth.controller.ts +++ b/src/modules/auth/auth.controller.ts @@ -479,7 +479,7 @@ export class AuthController { const accessToken = req.cookies['ddf-auth']; const refreshToken = req.cookies['ddf-refresh-auth']; const userId = req.cookies['ddf-user-id']; - const resourceOrigin = req.headers["origin"] + const resourceHost = req.headers["host"] const hasUserSession = Boolean(accessToken) && Boolean(userId); this.logger.info('Has User Session: ' + hasUserSession); @@ -489,7 +489,7 @@ export class AuthController { } try { - const userDto = await this.authClient.validateUserSession(accessToken, resourceOrigin); + const userDto = await this.authClient.validateUserSession(accessToken, resourceHost); return res.status(200).json(userDto); } catch (error) { @@ -501,7 +501,7 @@ export class AuthController { const { authSession, user - } = await this.authClient.refreshUserSession(refreshToken, userId, resourceOrigin); + } = await this.authClient.refreshUserSession(refreshToken, userId, resourceHost); this.authClient.writeAuthSession(res, authSession); return res.status(200).json(user); } diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 3133462..3229752 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -449,16 +449,16 @@ export class AuthClientService implements OnModuleInit { return userDto; } - private validateResourceAccess(origin: string, user: UserDTO) { + private validateResourceAccess(host: string, user: UserDTO) { this.logger.info( "Validate whether the source URL is a resource belonging to the user's client", ); - this.logger.info('Origin: ' + origin); + this.logger.info('Host: ' + host); this.logger.info('Customer: ' + user.customer.name); - const urlParts = origin.replace('https://', '').split('.'); - const domain = urlParts[0]; - const isResouceStg = urlParts[1] === 'stg'; + const hostParts = host.split('.'); + const domain = hostParts[0]; + const isResouceStg = hostParts[1] === 'stg'; const domainParts = domain.split('-'); const customerInDomain = domainParts[domainParts.length - 1]; From bb671a90d61310cbc35e765c637ee0cec04ee1e1 Mon Sep 17 00:00:00 2001 From: Marcos Rodrigues Date: Wed, 3 Dec 2025 15:20:41 -0300 Subject: [PATCH 05/41] FIX: change origin to host --- src/modules/auth/auth.service.ts | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/modules/auth/auth.service.ts b/src/modules/auth/auth.service.ts index 3229752..c1b85f6 100644 --- a/src/modules/auth/auth.service.ts +++ b/src/modules/auth/auth.service.ts @@ -3,7 +3,8 @@ import { Inject, Injectable, ForbiddenException, - UnauthorizedException, + HttpException, + HttpStatus, } from '@nestjs/common'; import { ClientGrpc } from '@nestjs/microservices'; import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; @@ -12,7 +13,6 @@ import { lastValueFrom } from 'rxjs'; import { ProtoServices } from '@dadosfera/protospack-v2/dist/lib/Duc'; import { AuthProtoService as AuthServiceInterface, - IdentityProviderProtoService, UsersProtoService, } from '@dadosfera/protospack-v2/dist/lib/Duc/interfaces/write-service'; import { @@ -304,12 +304,12 @@ export class AuthClientService implements OnModuleInit { } } - public async validateUserSession(accessToken: any, originHeader: string) { + public async validateUserSession(accessToken: any, resourceHost: string) { const payload = await this.validateJwtToken(accessToken); const userDto = await this.getUserfromPayload(payload); - this.validateResourceAccess(originHeader, userDto); + this.validateResourceAccess(resourceHost, userDto); return userDto; } @@ -460,18 +460,30 @@ export class AuthClientService implements OnModuleInit { const domain = hostParts[0]; const isResouceStg = hostParts[1] === 'stg'; + const notFoundCustomerInDomain = !domain.includes('-') + + if (notFoundCustomerInDomain) { + this.logger.info(`Not found Customer Name in domain`); + return; + } + const domainParts = domain.split('-'); + const customerInDomain = domainParts[domainParts.length - 1]; if (isResouceStg && process.env.ENV !== 'stg') { - throw new ForbiddenException( - `Customer ${user.customer.name} cannot access ${origin}`, + this.logger.error(`Customer ${user.customer.name} cannot access ${host}`); + throw new HttpException( + `Customer ${user.customer.name} cannot access ${host}`, + HttpStatus.FORBIDDEN ); } if (customerInDomain != user.customer.name) { - throw new ForbiddenException( - `Customer ${user.customer.name} cannot access ${origin}`, + this.logger.error(`Customer ${user.customer.name} cannot access ${host}`); + throw new HttpException( + `Customer ${user.customer.name} cannot access ${host}`, + HttpStatus.FORBIDDEN ); } From 399d3492d38ccb34ba5aaa93be2f0aa3a4180861 Mon Sep 17 00:00:00 2001 From: viniciusgadea Date: Tue, 9 Dec 2025 14:46:02 -0300 Subject: [PATCH 06/41] DOCS: update @dadosfera/protospack-v2 to version 3.38.0-beta.15 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e58ffab..8c9f50b 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "@aws-sdk/client-secrets-manager": "^3.414.0", "@dadosfera/dadosfera-logs": "^1.0.0-beta.4", "@dadosfera/protospack": "2.5.3", - "@dadosfera/protospack-v2": "3.38.0-beta.14", + "@dadosfera/protospack-v2": "3.38.0-beta.15", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", From c21d977f7c6fba15a502465dee1517fbb725e895 Mon Sep 17 00:00:00 2001 From: viniciusgadea Date: Tue, 9 Dec 2025 14:51:25 -0300 Subject: [PATCH 07/41] FEAT: enhance catalog service to support any asset type in data docs creation --- src/modules/catalog/catalog.controller.ts | 27 +++++++++++--- src/modules/catalog/catalog.service.ts | 45 ++++++++++++++++++----- src/modules/catalog/dtos/index.ts | 7 ++++ 3 files changed, 65 insertions(+), 14 deletions(-) diff --git a/src/modules/catalog/catalog.controller.ts b/src/modules/catalog/catalog.controller.ts index b87829b..13bcf42 100644 --- a/src/modules/catalog/catalog.controller.ts +++ b/src/modules/catalog/catalog.controller.ts @@ -439,7 +439,13 @@ export class CatalogController { language, }); - const docs = await this.catalogService.getDataDocs(id, metadata); + const { data_asset } = await this.catalogService.getOneDataAsset({ + customer_id, + id, + metadata, + }); + + const docs = await this.catalogService.getDataDocs(id, data_asset.data_asset_type, metadata); return { docs }; } @@ -487,21 +493,32 @@ export class CatalogController { @Headers() headers, @Param('id') table_id: string, @Body('docs') docs: string, + @Query('asset_type') asset_type: string, ) { - const { user_id, customer_name } = user; + const { user_id, customer_name, customer_id, username } = user; + + const metadata = PackTheMetadata({ + customer_id, + customer_name, + user_id, + username, + }); - this.logger.info(`/catalog - ON GET DATA DOCS ROUTE`, { + this.logger.info(`/catalog - ON POST DATA DOCS ROUTE`, { user_id, customer_name, }); - const res = await this.catalogService.createDataDocs({ + const body = { table_id, docs, + asset_type, info: { customer: customer_name, }, - }); + } + + const res = await this.catalogService.createDataDocs(body, metadata); return res; } diff --git a/src/modules/catalog/catalog.service.ts b/src/modules/catalog/catalog.service.ts index 1848dcd..16cc4ab 100644 --- a/src/modules/catalog/catalog.service.ts +++ b/src/modules/catalog/catalog.service.ts @@ -24,9 +24,11 @@ import { CatalogClientConfiguration } from './catalog-client'; import { UsersService } from '../users/users.service'; import { RolesService } from '../roles/roles.service'; import { Metadata } from '@grpc/grpc-js'; +import { PackTheMetadata } from 'src/utils/PackTheMetadata'; import { AssetReporter, BatchRemoveRlsRulesRequest, + CreateDataDocsDTO, IUpdateDataRequest, TriggerCatalogReq, } from './dtos'; @@ -337,9 +339,11 @@ class CatalogService implements OnModuleInit { return { data_asset: asset[0] }; } - async getDataDocs(id: string, metadata: Metadata) { + async getDataDocs(id: string, assetType: string, metadata: Metadata) { + const requestType = assetType === 'dataset' ? undefined : assetType; + const { documentation } = await lastValueFrom( - this.catalogReadService.GetDatasetDoc({ id, type: undefined }, metadata), + this.catalogReadService.GetDatasetDoc({ id, type: requestType }, metadata), ); const docs = JSON.parse(documentation); @@ -368,13 +372,36 @@ class CatalogService implements OnModuleInit { return result; } - async createDataDocs(body) { - const nimbusUrl = this._getNimbusUrl(body); - const { data } = await axios.post( - `${nimbusUrl}/api/catalog/data-docs/`, - body, - ); - return data; + async createDataDocs(body: CreateDataDocsDTO, metadata: Metadata) { + if (body.asset_type === 'table' || body.asset_type === 'view') { + const nimbusUrl = this._getNimbusUrl(body); + const { data } = await axios.post( + `${nimbusUrl}/api/catalog/data-docs/`, + body, + ); + return data; + } + + try { + const response: any = await lastValueFrom( + this.catalogWriteService.UpdateDataAssetDoc( + { + id: body.table_id, + docs: body.docs, + }, + metadata, + ), + ); + + return response; + + } catch (error) { + this.logger.error('Error creating data asset docs:', error); + throw new HttpException( + 'Failed to create data asset documentation', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } } async findAllTags(data, metadata) { diff --git a/src/modules/catalog/dtos/index.ts b/src/modules/catalog/dtos/index.ts index ef12e78..4989672 100644 --- a/src/modules/catalog/dtos/index.ts +++ b/src/modules/catalog/dtos/index.ts @@ -328,3 +328,10 @@ export type AssetReporter = { created_at: string; tags: string; } + +export type CreateDataDocsDTO = { + table_id: string; + docs: string; + asset_type: string; + +} \ No newline at end of file From a0a1303515fedee6c81eb5a5e1a525d43f5c6aad Mon Sep 17 00:00:00 2001 From: viniciusgadea Date: Tue, 9 Dec 2025 14:54:39 -0300 Subject: [PATCH 08/41] DOCS: add automatic asset_type query parameter to API schema and packages dependencies --- docsfera.json | 8 +++++ package-lock.json | 76 +++++++++++++++++++++-------------------------- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/docsfera.json b/docsfera.json index ab3a2c1..591cea9 100644 --- a/docsfera.json +++ b/docsfera.json @@ -4471,6 +4471,14 @@ "schema": { "type": "string" } + }, + { + "name": "asset_type", + "required": true, + "in": "query", + "schema": { + "type": "string" + } } ], "responses": { diff --git a/package-lock.json b/package-lock.json index 0a1aa0e..2764511 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@aws-sdk/client-secrets-manager": "^3.414.0", "@dadosfera/dadosfera-logs": "^1.0.0-beta.4", "@dadosfera/protospack": "2.5.3", - "@dadosfera/protospack-v2": "3.38.0-beta.14", + "@dadosfera/protospack-v2": "3.38.0-beta.15", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", @@ -850,7 +850,6 @@ "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -1409,10 +1408,9 @@ } }, "node_modules/@dadosfera/protospack-v2": { - "version": "3.38.0-beta.14", - "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.38.0-beta.14.tgz", - "integrity": "sha512-BiE1fUIHuam3cJjAGIknsRNIlf1Z0JHJOV/VZ2pyAp5mIkGOSoNFBAMXJZb+VMTnd2xsp+vPxNnWVu7OJ/CvMQ==", - "license": "ISC", + "version": "3.38.0-beta.15", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.38.0-beta.15.tgz", + "integrity": "sha512-WJ6wANzTt/fo0BrE7nviAqwN1COh66qIujoYfBlHW7FgrHkgu8CAUF38WmtjqjYCvpfcd/A1j1MESsuUg9QWJw==", "dependencies": { "@grpc/grpc-js": "^1.9.3", "rxjs": "^7.5.5" @@ -2261,7 +2259,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-9.4.3.tgz", "integrity": "sha512-Gd6D4IaYj01o14Bwv81ukidn4w3bPHCblMUq+SmUmWLyosK+XQmInCS09SbDDZyL8jy86PngtBLTdhJ2bXSUig==", "license": "MIT", - "peer": true, "dependencies": { "iterare": "1.2.1", "tslib": "2.5.3", @@ -2340,7 +2337,6 @@ "integrity": "sha512-Qi63+wi55Jh4sDyaj5Hhx2jOpKqT386aeo+VOKsxnd+Ql9VvkO/FjmuwBGUyzkJt29ENYc+P0Sx/k5LtstNpPQ==", "hasInstallScript": true, "license": "MIT", - "peer": true, "dependencies": { "@nuxtjs/opencollective": "0.3.2", "fast-safe-stringify": "2.1.1", @@ -2478,7 +2474,6 @@ "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-9.4.3.tgz", "integrity": "sha512-FpdczWoRSC0zz2dNL9u2AQLXKXRVtq4HgHklAhbL59X0uy+mcxhlSThG7DHzDMkoSnuuHY8ojDVf7mDxk+GtCw==", "license": "MIT", - "peer": true, "dependencies": { "body-parser": "1.20.2", "cors": "2.8.5", @@ -2842,7 +2837,6 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=8.0.0" } @@ -3002,7 +2996,6 @@ "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", "license": "MIT", - "peer": true, "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", @@ -3799,7 +3792,6 @@ "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -3880,7 +3872,6 @@ "integrity": "sha512-4dRxkS/AFX0c5XW6IPMNOydLn2tEhNhJV7DnYK+0bjoJZ+QTmfucBlihX7aoEsh/ocYtkLC73UbnBXBXIxsULA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "jest-diff": "^27.0.0", "pretty-format": "^27.0.0" @@ -3944,8 +3935,7 @@ "version": "16.18.126", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.126.tgz", "integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@types/oauth": { "version": "0.9.6", @@ -4175,7 +4165,6 @@ "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -4522,7 +4511,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4619,7 +4607,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -5248,7 +5235,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -5355,7 +5341,6 @@ "resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-5.7.6.tgz", "integrity": "sha512-wBxnBHjDxF1RXpHCBD6HGvKER003Ts7IIm0CHpggliHzN1RZditb7rXoduE1rplc2DEFYKxhLKgFuchXMJje9w==", "license": "MIT", - "peer": true, "dependencies": { "eventemitter3": "^5.0.1", "lodash.clonedeep": "^4.5.0", @@ -5579,15 +5564,13 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/class-validator": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz", "integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==", "license": "MIT", - "peer": true, "dependencies": { "@types/validator": "^13.11.8", "libphonenumber-js": "^1.10.53", @@ -6251,8 +6234,7 @@ "version": "0.0.1425554", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1425554.tgz", "integrity": "sha512-uRfxR6Nlzdzt0ihVIkV+sLztKgs7rgquY/Mhcv1YNCWDh5IZgl5mnn2aeEnW5stYTE0wwiF4RYVz8eMEpV1SEw==", - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/dezalgo": { "version": "1.0.4", @@ -6529,6 +6511,7 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -6716,7 +6699,6 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -7063,6 +7045,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -7109,6 +7092,7 @@ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "license": "MIT", + "peer": true, "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -7133,6 +7117,7 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -7142,6 +7127,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -7150,19 +7136,22 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/express/node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/express/node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "license": "BSD-3-Clause", + "peer": true, "dependencies": { "side-channel": "^1.0.6" }, @@ -7191,7 +7180,8 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/external-editor": { "version": "3.1.0", @@ -7426,6 +7416,7 @@ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "license": "MIT", + "peer": true, "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", @@ -7444,6 +7435,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -7452,7 +7444,8 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/find-up": { "version": "5.0.0", @@ -8246,7 +8239,6 @@ "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz", "integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==", "license": "MIT", - "peer": true, "dependencies": { "@ioredis/commands": "^1.1.1", "cluster-key-slot": "^1.1.0", @@ -8588,7 +8580,6 @@ "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^27.5.1", "import-local": "^3.0.2", @@ -9878,6 +9869,7 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -10635,7 +10627,6 @@ "resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz", "integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==", "license": "MIT", - "peer": true, "dependencies": { "passport-strategy": "1.x.x", "pause": "0.0.1", @@ -11025,7 +11016,6 @@ "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, "license": "MIT", - "peer": true, "bin": { "prettier": "bin-prettier.js" }, @@ -11528,8 +11518,7 @@ "version": "0.1.14", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==", - "license": "Apache-2.0", - "peer": true + "license": "Apache-2.0" }, "node_modules/relative-microtime": { "version": "2.0.0", @@ -11716,7 +11705,6 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -11778,7 +11766,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -11822,6 +11809,7 @@ "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "license": "MIT", + "peer": true, "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -11846,6 +11834,7 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", + "peer": true, "dependencies": { "ms": "2.0.0" } @@ -11854,13 +11843,15 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/send/node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -11869,7 +11860,8 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/serialize-javascript": { "version": "6.0.2", @@ -11885,6 +11877,7 @@ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "license": "MIT", + "peer": true, "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", @@ -12862,7 +12855,6 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -13092,7 +13084,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -13420,6 +13411,7 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", "license": "MIT", + "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", From b1dc5673942a900736cf061b2367cf6f8846bdb3 Mon Sep 17 00:00:00 2001 From: viniciusgadea Date: Wed, 10 Dec 2025 11:12:13 -0300 Subject: [PATCH 09/41] DOCS: update @dadosfera/protospack-v2 to version 3.38.0-beta.16 --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2764511..5d30872 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@aws-sdk/client-secrets-manager": "^3.414.0", "@dadosfera/dadosfera-logs": "^1.0.0-beta.4", "@dadosfera/protospack": "2.5.3", - "@dadosfera/protospack-v2": "3.38.0-beta.15", + "@dadosfera/protospack-v2": "3.38.0-beta.16", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", @@ -1408,9 +1408,9 @@ } }, "node_modules/@dadosfera/protospack-v2": { - "version": "3.38.0-beta.15", - "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.38.0-beta.15.tgz", - "integrity": "sha512-WJ6wANzTt/fo0BrE7nviAqwN1COh66qIujoYfBlHW7FgrHkgu8CAUF38WmtjqjYCvpfcd/A1j1MESsuUg9QWJw==", + "version": "3.38.0-beta.16", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.38.0-beta.16.tgz", + "integrity": "sha512-ijURcmETXdgVgXhIPxapK7w0z6begXm4WDZ6EJtUGg+oYlJd9QOxbwaD7akRzW8XTdccmIo0Vov065KB812J0A==", "dependencies": { "@grpc/grpc-js": "^1.9.3", "rxjs": "^7.5.5" diff --git a/package.json b/package.json index 8c9f50b..1da6c28 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "@aws-sdk/client-secrets-manager": "^3.414.0", "@dadosfera/dadosfera-logs": "^1.0.0-beta.4", "@dadosfera/protospack": "2.5.3", - "@dadosfera/protospack-v2": "3.38.0-beta.15", + "@dadosfera/protospack-v2": "3.38.0-beta.16", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", From 165b533172502ba2bf35a778581b98fb52cc8bec Mon Sep 17 00:00:00 2001 From: viniciusgadea Date: Wed, 10 Dec 2025 11:17:43 -0300 Subject: [PATCH 10/41] FIX: removing unnecessary type parameter from GetDatasetDoc call --- src/modules/catalog/share/share.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/catalog/share/share.service.ts b/src/modules/catalog/share/share.service.ts index 3e088a6..86b17b9 100644 --- a/src/modules/catalog/share/share.service.ts +++ b/src/modules/catalog/share/share.service.ts @@ -160,7 +160,7 @@ export class ShareService implements OnModuleInit { }); const { documentation } = await lastValueFrom( - this.catalogReadService.GetDatasetDoc({ id, type: undefined }, metadata), + this.catalogReadService.GetDatasetDoc({ id }, metadata), ); console.log(documentation); const docs = JSON.parse(documentation); From 09dced9fcdec7da65342f703b736cf571df7ee0b Mon Sep 17 00:00:00 2001 From: viniciusgadea Date: Wed, 10 Dec 2025 11:21:46 -0300 Subject: [PATCH 11/41] REFACTOR: add asset_type query parameter to getDataDocs and separate data docs methods --- src/modules/catalog/catalog.controller.ts | 9 ++------ src/modules/catalog/catalog.service.ts | 28 +++++++++++++++-------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/src/modules/catalog/catalog.controller.ts b/src/modules/catalog/catalog.controller.ts index 13bcf42..83c1386 100644 --- a/src/modules/catalog/catalog.controller.ts +++ b/src/modules/catalog/catalog.controller.ts @@ -423,6 +423,7 @@ export class CatalogController { @User() user: RequestUser, @Language() language: LanguageEnum, @Param('id') id: string, + @Query('asset_type') asset_type: string, ): Promise { const { customer_name, customer_id, user_id, username } = user; @@ -439,13 +440,7 @@ export class CatalogController { language, }); - const { data_asset } = await this.catalogService.getOneDataAsset({ - customer_id, - id, - metadata, - }); - - const docs = await this.catalogService.getDataDocs(id, data_asset.data_asset_type, metadata); + const docs = await this.catalogService.getDataDocs(id, asset_type, metadata); return { docs }; } diff --git a/src/modules/catalog/catalog.service.ts b/src/modules/catalog/catalog.service.ts index 16cc4ab..73c9111 100644 --- a/src/modules/catalog/catalog.service.ts +++ b/src/modules/catalog/catalog.service.ts @@ -340,10 +340,8 @@ class CatalogService implements OnModuleInit { } async getDataDocs(id: string, assetType: string, metadata: Metadata) { - const requestType = assetType === 'dataset' ? undefined : assetType; - const { documentation } = await lastValueFrom( - this.catalogReadService.GetDatasetDoc({ id, type: requestType }, metadata), + this.catalogReadService.GetDatasetDoc({ id }, metadata), ); const docs = JSON.parse(documentation); @@ -374,14 +372,24 @@ class CatalogService implements OnModuleInit { async createDataDocs(body: CreateDataDocsDTO, metadata: Metadata) { if (body.asset_type === 'table' || body.asset_type === 'view') { - const nimbusUrl = this._getNimbusUrl(body); - const { data } = await axios.post( - `${nimbusUrl}/api/catalog/data-docs/`, - body, - ); - return data; + return this.createDataDocsViaNimbus(body); } - + + return this.createDataDocsViaGrpc(body, metadata); + } + + private async createDataDocsViaNimbus(body: CreateDataDocsDTO) { + this.logger.info('Creating data docs via Nimbus for table/view'); + const nimbusUrl = this._getNimbusUrl(body); + const { data } = await axios.post( + `${nimbusUrl}/api/catalog/data-docs/`, + body, + ); + return data; + } + + private async createDataDocsViaGrpc(body: CreateDataDocsDTO, metadata: Metadata) { + this.logger.info('Creating data docs via gRPC for other asset types'); try { const response: any = await lastValueFrom( this.catalogWriteService.UpdateDataAssetDoc( From df674dd441ca8ce8be4a940929756c0c12cbda54 Mon Sep 17 00:00:00 2001 From: Rafael Date: Wed, 10 Dec 2025 12:41:14 -0300 Subject: [PATCH 12/41] FEAT: add Platform API proxy with Elasticsearch and DynamoDB sync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add Platform API module to proxy requests to Platform API service - Add Elasticsearch service for pipeline catalog sync (CRUD operations) - Add DynamoDB service for storing pipeline tables/inputs configuration - Sync pipeline creation/update/delete to Elasticsearch index - Extract tables from jobs with connector-specific mappings: - JDBC: table_name, load_type, column_include_list, incremental_column_name - Singer: replication_method -> full_load/incremental - S3: same as Singer - Map connector types to DynamoDB types (jdbc->database, singer->application, s3->file) - Validate connector type is provided in job input - Normalize pipeline IDs for Platform API (replace - with _), keep UUIDs for ES 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- package-lock.json | 2194 ++++++++++++++--- package.json | 7 +- src/app.module.ts | 2 + .../platform-api/platform-api.config.ts | 11 + .../platform-api/platform-api.controller.ts | 663 +++++ .../platform-api/platform-api.module.ts | 16 + .../platform-api/platform-api.service.ts | 123 + src/services/dynamodb/dynamodb.config.ts | 4 + src/services/dynamodb/dynamodb.module.ts | 9 + src/services/dynamodb/dynamodb.service.ts | 148 ++ src/services/dynamodb/index.ts | 3 + .../elasticsearch/elasticsearch.config.ts | 5 + .../elasticsearch/elasticsearch.module.ts | 9 + .../elasticsearch/elasticsearch.service.ts | 375 +++ src/services/elasticsearch/index.ts | 3 + 15 files changed, 3277 insertions(+), 295 deletions(-) create mode 100644 src/modules/platform-api/platform-api.config.ts create mode 100644 src/modules/platform-api/platform-api.controller.ts create mode 100644 src/modules/platform-api/platform-api.module.ts create mode 100644 src/modules/platform-api/platform-api.service.ts create mode 100644 src/services/dynamodb/dynamodb.config.ts create mode 100644 src/services/dynamodb/dynamodb.module.ts create mode 100644 src/services/dynamodb/dynamodb.service.ts create mode 100644 src/services/dynamodb/index.ts create mode 100644 src/services/elasticsearch/elasticsearch.config.ts create mode 100644 src/services/elasticsearch/elasticsearch.module.ts create mode 100644 src/services/elasticsearch/elasticsearch.service.ts create mode 100644 src/services/elasticsearch/index.ts diff --git a/package-lock.json b/package-lock.json index b491a76..35fb021 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,12 @@ "version": "0.0.0", "license": "UNLICENSED", "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-sdk/client-dynamodb": "^3.414.0", "@aws-sdk/client-secrets-manager": "^3.414.0", + "@aws-sdk/credential-provider-node": "^3.940.0", + "@aws-sdk/lib-dynamodb": "^3.414.0", + "@aws-sdk/signature-v4": "^3.370.0", "@dadosfera/dadosfera-logs": "^1.0.0-beta.4", "@dadosfera/protospack": "2.5.3", "@dadosfera/protospack-v2": "3.38.0-beta.10", @@ -57,8 +62,8 @@ "swagger-ui-express": "^4.6.3" }, "devDependencies": { - "@types/cookie-parser": "^1.4.9", "@types/cache-manager": "^4.0.6", + "@types/cookie-parser": "^1.4.9", "@types/express": "^4.17.17", "@types/express-session": "^1.18.1", "@types/jest": "27.0.2", @@ -214,6 +219,34 @@ "tslib": "^2.1.0" } }, + "node_modules/@aws-crypto/crc32": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", + "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^3.0.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/crc32/node_modules/@aws-crypto/util": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", + "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-utf8-browser": "^3.0.0", + "tslib": "^1.11.1" + } + }, + "node_modules/@aws-crypto/crc32/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, "node_modules/@aws-crypto/sha256-browser": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", @@ -339,6 +372,227 @@ "node": ">=14.0.0" } }, + "node_modules/@aws-sdk/client-dynamodb": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.946.0.tgz", + "integrity": "sha512-sMmJRa2Y+jQ+qMzx7V3SBKsTSQzLBvSuVbMNM/YGSZkr849a4a6Frl4U8HfFj4O+o71dvQ6h5PP6vlc/fW672g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.946.0", + "@aws-sdk/credential-provider-node": "3.946.0", + "@aws-sdk/dynamodb-codec": "3.946.0", + "@aws-sdk/middleware-endpoint-discovery": "3.936.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.946.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.946.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.7", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", + "@smithy/middleware-endpoint": "^4.3.14", + "@smithy/middleware-retry": "^4.4.14", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.13", + "@smithy/util-defaults-mode-node": "^4.2.16", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/core": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.946.0.tgz", + "integrity": "sha512-u2BkbLLVbMFrEiXrko2+S6ih5sUZPlbVyRPtXOqMHlCyzr70sE8kIiD6ba223rQeIFPcYfW/wHc6k4ihW2xxVg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws-sdk/xml-builder": "3.930.0", + "@smithy/core": "^3.18.7", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.936.0.tgz", + "integrity": "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/middleware-logger": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.936.0.tgz", + "integrity": "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.936.0.tgz", + "integrity": "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws/lambda-invoke-store": "^0.2.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.946.0.tgz", + "integrity": "sha512-7QcljCraeaWQNuqmOoAyZs8KpZcuhPiqdeeKoRd397jVGNRehLFsZbIMOvwaluUDFY11oMyXOkQEERe1Zo2fCw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.946.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@smithy/core": "^3.18.7", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.936.0.tgz", + "integrity": "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/types": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", + "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/util-endpoints": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.936.0.tgz", + "integrity": "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-endpoints": "^3.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.936.0.tgz", + "integrity": "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-dynamodb/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.946.0.tgz", + "integrity": "sha512-a2UwwvzbK5AxHKUBupfg4s7VnkqRAHjYsuezHnKCniczmT4HZfP1NnfwwvLKEH8qaTrwenxjKSfq4UWmWkvG+Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.946.0", + "@aws-sdk/types": "3.936.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, "node_modules/@aws-sdk/client-secrets-manager": { "version": "3.758.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.758.0.tgz", @@ -391,7 +645,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/client-sso": { + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/client-sso": { "version": "3.758.0", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.758.0.tgz", "integrity": "sha512-BoGO6IIWrLyLxQG6txJw6RT2urmbtlwfggapNCrNPyYjlXpzTSJhBYjndg7TpDATFd0SXL0zm8y/tXsUXNkdYQ==", @@ -440,29 +694,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/core": { - "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.758.0.tgz", - "integrity": "sha512-0RswbdR9jt/XKemaLNuxi2gGr4xGlHyGxkTdhSQzCyUe9A9OPCoLl3rIESRguQEech+oJnbHk/wuiwHqTuP9sg==", - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.734.0", - "@smithy/core": "^3.1.5", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/signature-v4": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/util-middleware": "^4.0.1", - "fast-xml-parser": "4.4.1", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-env": { + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-env": { "version": "3.758.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.758.0.tgz", "integrity": "sha512-N27eFoRrO6MeUNumtNHDW9WOiwfd59LPXPqDrIa3kWL/s+fOKFHb9xIcF++bAwtcZnAxKkgpDCUP+INNZskE+w==", @@ -478,7 +710,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http": { + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-http": { "version": "3.758.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.758.0.tgz", "integrity": "sha512-Xt9/U8qUCiw1hihztWkNeIR+arg6P+yda10OuCHX6kFVx3auTlU7+hCqs3UxqniGU4dguHuftf3mRpi5/GJ33Q==", @@ -499,7 +731,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-ini": { + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-ini": { "version": "3.758.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.758.0.tgz", "integrity": "sha512-cymSKMcP5d+OsgetoIZ5QCe1wnp2Q/tq+uIxVdh9MbfdBBEnl9Ecq6dH6VlYS89sp4QKuxHxkWXVnbXU3Q19Aw==", @@ -523,7 +755,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-node": { + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-node": { "version": "3.758.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.758.0.tgz", "integrity": "sha512-+DaMv63wiq7pJrhIQzZYMn4hSarKiizDoJRvyR7WGhnn0oQ/getX9Z0VNCV3i7lIFoLNTb7WMmQ9k7+z/uD5EQ==", @@ -546,7 +778,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-process": { + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-process": { "version": "3.758.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.758.0.tgz", "integrity": "sha512-AzcY74QTPqcbXWVgjpPZ3HOmxQZYPROIBz2YINF0OQk0MhezDWV/O7Xec+K1+MPGQO3qS6EDrUUlnPLjsqieHA==", @@ -563,7 +795,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-sso": { + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-sso": { "version": "3.758.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.758.0.tgz", "integrity": "sha512-x0FYJqcOLUCv8GLLFDYMXRAQKGjoM+L0BG4BiHYZRDf24yQWFCAZsCQAYKo6XZYh2qznbsW6f//qpyJ5b0QVKQ==", @@ -582,7 +814,7 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity": { + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/credential-provider-web-identity": { "version": "3.758.0", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.758.0.tgz", "integrity": "sha512-XGguXhBqiCXMXRxcfCAVPlMbm3VyJTou79r/3mxWddHWF0XbhaQiBIbUz6vobVTD25YQRbWSmSch7VA8kI5Lrw==", @@ -599,6 +831,947 @@ "node": ">=18.0.0" } }, + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/nested-clients": { + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.758.0.tgz", + "integrity": "sha512-YZ5s7PSvyF3Mt2h1EQulCG93uybprNGbBkPmVuy/HMMfbFTt4iL3SbKjxqvOZelm86epFfj7pvK7FliI2WOEcg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.758.0", + "@aws-sdk/middleware-host-header": "3.734.0", + "@aws-sdk/middleware-logger": "3.734.0", + "@aws-sdk/middleware-recursion-detection": "3.734.0", + "@aws-sdk/middleware-user-agent": "3.758.0", + "@aws-sdk/region-config-resolver": "3.734.0", + "@aws-sdk/types": "3.734.0", + "@aws-sdk/util-endpoints": "3.743.0", + "@aws-sdk/util-user-agent-browser": "3.734.0", + "@aws-sdk/util-user-agent-node": "3.758.0", + "@smithy/config-resolver": "^4.0.1", + "@smithy/core": "^3.1.5", + "@smithy/fetch-http-handler": "^5.0.1", + "@smithy/hash-node": "^4.0.1", + "@smithy/invalid-dependency": "^4.0.1", + "@smithy/middleware-content-length": "^4.0.1", + "@smithy/middleware-endpoint": "^4.0.6", + "@smithy/middleware-retry": "^4.0.7", + "@smithy/middleware-serde": "^4.0.2", + "@smithy/middleware-stack": "^4.0.1", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/node-http-handler": "^4.0.3", + "@smithy/protocol-http": "^5.0.1", + "@smithy/smithy-client": "^4.1.6", + "@smithy/types": "^4.1.0", + "@smithy/url-parser": "^4.0.1", + "@smithy/util-base64": "^4.0.0", + "@smithy/util-body-length-browser": "^4.0.0", + "@smithy/util-body-length-node": "^4.0.0", + "@smithy/util-defaults-mode-browser": "^4.0.7", + "@smithy/util-defaults-mode-node": "^4.0.7", + "@smithy/util-endpoints": "^3.0.1", + "@smithy/util-middleware": "^4.0.1", + "@smithy/util-retry": "^4.0.1", + "@smithy/util-utf8": "^4.0.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-secrets-manager/node_modules/@aws-sdk/token-providers": { + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.758.0.tgz", + "integrity": "sha512-ckptN1tNrIfQUaGWm/ayW1ddG+imbKN7HHhjFdS4VfItsP0QQOB0+Ov+tpgb4MoNR4JaUghMIVStjIeHN2ks1w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/nested-clients": "3.758.0", + "@aws-sdk/types": "3.734.0", + "@smithy/property-provider": "^4.0.1", + "@smithy/shared-ini-file-loader": "^4.0.1", + "@smithy/types": "^4.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.946.0.tgz", + "integrity": "sha512-kGAs5iIVyUz4p6TX3pzG5q3cNxXnVpC4pwRC6DCSaSv9ozyPjc2d74FsK4fZ+J+ejtvCdJk72uiuQtWJc86Wuw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.946.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.946.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.946.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.7", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", + "@smithy/middleware-endpoint": "^4.3.14", + "@smithy/middleware-retry": "^4.4.14", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.13", + "@smithy/util-defaults-mode-node": "^4.2.16", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/core": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.946.0.tgz", + "integrity": "sha512-u2BkbLLVbMFrEiXrko2+S6ih5sUZPlbVyRPtXOqMHlCyzr70sE8kIiD6ba223rQeIFPcYfW/wHc6k4ihW2xxVg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws-sdk/xml-builder": "3.930.0", + "@smithy/core": "^3.18.7", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.936.0.tgz", + "integrity": "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/middleware-logger": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.936.0.tgz", + "integrity": "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.936.0.tgz", + "integrity": "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws/lambda-invoke-store": "^0.2.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.946.0.tgz", + "integrity": "sha512-7QcljCraeaWQNuqmOoAyZs8KpZcuhPiqdeeKoRd397jVGNRehLFsZbIMOvwaluUDFY11oMyXOkQEERe1Zo2fCw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.946.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@smithy/core": "^3.18.7", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.936.0.tgz", + "integrity": "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/types": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", + "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-endpoints": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.936.0.tgz", + "integrity": "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-endpoints": "^3.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.936.0.tgz", + "integrity": "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/client-sso/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.946.0.tgz", + "integrity": "sha512-a2UwwvzbK5AxHKUBupfg4s7VnkqRAHjYsuezHnKCniczmT4HZfP1NnfwwvLKEH8qaTrwenxjKSfq4UWmWkvG+Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.946.0", + "@aws-sdk/types": "3.936.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.758.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.758.0.tgz", + "integrity": "sha512-0RswbdR9jt/XKemaLNuxi2gGr4xGlHyGxkTdhSQzCyUe9A9OPCoLl3rIESRguQEech+oJnbHk/wuiwHqTuP9sg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.734.0", + "@smithy/core": "^3.1.5", + "@smithy/node-config-provider": "^4.0.1", + "@smithy/property-provider": "^4.0.1", + "@smithy/protocol-http": "^5.0.1", + "@smithy/signature-v4": "^5.0.1", + "@smithy/smithy-client": "^4.1.6", + "@smithy/types": "^4.1.0", + "@smithy/util-middleware": "^4.0.1", + "fast-xml-parser": "4.4.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.946.0.tgz", + "integrity": "sha512-P4l+K6wX1tf8LmWUvZofdQ+BgCNyk6Tb9u1H10npvqpuCD+dCM4pXIBq3PQcv/juUBOvLGGREo+Govuh3lfD0Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.946.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env/node_modules/@aws-sdk/core": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.946.0.tgz", + "integrity": "sha512-u2BkbLLVbMFrEiXrko2+S6ih5sUZPlbVyRPtXOqMHlCyzr70sE8kIiD6ba223rQeIFPcYfW/wHc6k4ihW2xxVg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws-sdk/xml-builder": "3.930.0", + "@smithy/core": "^3.18.7", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env/node_modules/@aws-sdk/types": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", + "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.946.0.tgz", + "integrity": "sha512-/zeOJ6E7dGZQ/l2k7KytEoPJX0APIhwt0A79hPf/bUpMF4dDs2P6JmchDrotk0a0Y/MIdNF8sBQ/MEOPnBiYoQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.946.0", + "@aws-sdk/types": "3.936.0", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/util-stream": "^4.5.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@aws-sdk/core": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.946.0.tgz", + "integrity": "sha512-u2BkbLLVbMFrEiXrko2+S6ih5sUZPlbVyRPtXOqMHlCyzr70sE8kIiD6ba223rQeIFPcYfW/wHc6k4ihW2xxVg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws-sdk/xml-builder": "3.930.0", + "@smithy/core": "^3.18.7", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http/node_modules/@aws-sdk/types": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", + "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.946.0.tgz", + "integrity": "sha512-Pdgcra3RivWj/TuZmfFaHbqsvvgnSKO0CxlRUMMr0PgBiCnUhyl+zBktdNOeGsOPH2fUzQpYhcUjYUgVSdcSDQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.946.0", + "@aws-sdk/credential-provider-env": "3.946.0", + "@aws-sdk/credential-provider-http": "3.946.0", + "@aws-sdk/credential-provider-login": "3.946.0", + "@aws-sdk/credential-provider-process": "3.946.0", + "@aws-sdk/credential-provider-sso": "3.946.0", + "@aws-sdk/credential-provider-web-identity": "3.946.0", + "@aws-sdk/nested-clients": "3.946.0", + "@aws-sdk/types": "3.936.0", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/core": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.946.0.tgz", + "integrity": "sha512-u2BkbLLVbMFrEiXrko2+S6ih5sUZPlbVyRPtXOqMHlCyzr70sE8kIiD6ba223rQeIFPcYfW/wHc6k4ihW2xxVg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws-sdk/xml-builder": "3.930.0", + "@smithy/core": "^3.18.7", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini/node_modules/@aws-sdk/types": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", + "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.946.0.tgz", + "integrity": "sha512-5iqLNc15u2Zx+7jOdQkIbP62N7n2031tw5hkmIG0DLnozhnk64osOh2CliiOE9x3c4P9Pf4frAwgyy9GzNTk2g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.946.0", + "@aws-sdk/nested-clients": "3.946.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/core": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.946.0.tgz", + "integrity": "sha512-u2BkbLLVbMFrEiXrko2+S6ih5sUZPlbVyRPtXOqMHlCyzr70sE8kIiD6ba223rQeIFPcYfW/wHc6k4ihW2xxVg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws-sdk/xml-builder": "3.930.0", + "@smithy/core": "^3.18.7", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login/node_modules/@aws-sdk/types": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", + "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.946.0.tgz", + "integrity": "sha512-I7URUqnBPng1a5y81OImxrwERysZqMBREG6svhhGeZgxmqcpAZ8z5ywILeQXdEOCuuES8phUp/ojzxFjPXp/eA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.946.0", + "@aws-sdk/credential-provider-http": "3.946.0", + "@aws-sdk/credential-provider-ini": "3.946.0", + "@aws-sdk/credential-provider-process": "3.946.0", + "@aws-sdk/credential-provider-sso": "3.946.0", + "@aws-sdk/credential-provider-web-identity": "3.946.0", + "@aws-sdk/types": "3.936.0", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node/node_modules/@aws-sdk/types": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", + "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.946.0.tgz", + "integrity": "sha512-GtGHX7OGqIeVQ3DlVm5RRF43Qmf3S1+PLJv9svrdvAhAdy2bUb044FdXXqrtSsIfpzTKlHgQUiRo5MWLd35Ntw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.946.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process/node_modules/@aws-sdk/core": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.946.0.tgz", + "integrity": "sha512-u2BkbLLVbMFrEiXrko2+S6ih5sUZPlbVyRPtXOqMHlCyzr70sE8kIiD6ba223rQeIFPcYfW/wHc6k4ihW2xxVg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws-sdk/xml-builder": "3.930.0", + "@smithy/core": "^3.18.7", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process/node_modules/@aws-sdk/types": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", + "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.946.0.tgz", + "integrity": "sha512-LeGSSt2V5iwYey1ENGY75RmoDP3bA2iE/py8QBKW8EDA8hn74XBLkprhrK5iccOvU3UGWY8WrEKFAFGNjJOL9g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.946.0", + "@aws-sdk/core": "3.946.0", + "@aws-sdk/token-providers": "3.946.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/core": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.946.0.tgz", + "integrity": "sha512-u2BkbLLVbMFrEiXrko2+S6ih5sUZPlbVyRPtXOqMHlCyzr70sE8kIiD6ba223rQeIFPcYfW/wHc6k4ihW2xxVg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws-sdk/xml-builder": "3.930.0", + "@smithy/core": "^3.18.7", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso/node_modules/@aws-sdk/types": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", + "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.946.0.tgz", + "integrity": "sha512-ocBCvjWfkbjxElBI1QUxOnHldsNhoU0uOICFvuRDAZAoxvypJHN3m5BJkqb7gqorBbcv3LRgmBdEnWXOAvq+7Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.946.0", + "@aws-sdk/nested-clients": "3.946.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@aws-sdk/core": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.946.0.tgz", + "integrity": "sha512-u2BkbLLVbMFrEiXrko2+S6ih5sUZPlbVyRPtXOqMHlCyzr70sE8kIiD6ba223rQeIFPcYfW/wHc6k4ihW2xxVg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws-sdk/xml-builder": "3.930.0", + "@smithy/core": "^3.18.7", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity/node_modules/@aws-sdk/types": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", + "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/dynamodb-codec": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/dynamodb-codec/-/dynamodb-codec-3.946.0.tgz", + "integrity": "sha512-doDGHfTV8nFvDOnFJDcI4lx0NR4GT6uagTFM20AoIIVngm7UEITQ0toU1uTRRyRa+52ibMktdLkv7c8OcCG6Qw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.946.0", + "@smithy/core": "^3.18.7", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.946.0" + } + }, + "node_modules/@aws-sdk/dynamodb-codec/node_modules/@aws-sdk/core": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.946.0.tgz", + "integrity": "sha512-u2BkbLLVbMFrEiXrko2+S6ih5sUZPlbVyRPtXOqMHlCyzr70sE8kIiD6ba223rQeIFPcYfW/wHc6k4ihW2xxVg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws-sdk/xml-builder": "3.930.0", + "@smithy/core": "^3.18.7", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/dynamodb-codec/node_modules/@aws-sdk/types": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", + "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/endpoint-cache": { + "version": "3.893.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/endpoint-cache/-/endpoint-cache-3.893.0.tgz", + "integrity": "sha512-KSwTfyLZyNLszz5f/yoLC+LC+CRKpeJii/+zVAy7JUOQsKhSykiRUPYUx7o2Sdc4oJfqqUl26A/jSttKYnYtAA==", + "license": "Apache-2.0", + "dependencies": { + "mnemonist": "0.38.3", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/eventstream-codec": { + "version": "3.370.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/eventstream-codec/-/eventstream-codec-3.370.0.tgz", + "integrity": "sha512-PiaDMum7TNsIE3DGECSsNYwibBIPN2/e13BJbTwi6KgVx8BV2mYA3kQkaUDiy++tEpzN81Nh5OPTFVb7bvgYYg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "3.0.0", + "@aws-sdk/types": "3.370.0", + "@aws-sdk/util-hex-encoding": "3.310.0", + "tslib": "^2.5.0" + } + }, + "node_modules/@aws-sdk/eventstream-codec/node_modules/@aws-sdk/types": { + "version": "3.370.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.370.0.tgz", + "integrity": "sha512-8PGMKklSkRKjunFhzM2y5Jm0H2TBu7YRNISdYzXLUHKSP9zlMEYagseKVdmox0zKHf1LXVNuSlUV2b6SRrieCQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^1.1.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/eventstream-codec/node_modules/@smithy/types": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-1.2.0.tgz", + "integrity": "sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/is-array-buffer": { + "version": "3.310.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/is-array-buffer/-/is-array-buffer-3.310.0.tgz", + "integrity": "sha512-urnbcCR+h9NWUnmOtet/s4ghvzsidFmspfhYaHAmSRdy9yDjdjBJMFjjsn85A1ODUktztm+cVncXjQ38WCMjMQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/lib-dynamodb": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/lib-dynamodb/-/lib-dynamodb-3.946.0.tgz", + "integrity": "sha512-1qI5EZUa/E0Ex4GITb9SiY5YvXcGKCYYawGYKiacpnA+YldLzKtllwns23upiTrHLmeBHZxwrXAG1sh1UqM3tw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.946.0", + "@aws-sdk/util-dynamodb": "3.946.0", + "@smithy/core": "^3.18.7", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.946.0" + } + }, + "node_modules/@aws-sdk/lib-dynamodb/node_modules/@aws-sdk/core": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.946.0.tgz", + "integrity": "sha512-u2BkbLLVbMFrEiXrko2+S6ih5sUZPlbVyRPtXOqMHlCyzr70sE8kIiD6ba223rQeIFPcYfW/wHc6k4ihW2xxVg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws-sdk/xml-builder": "3.930.0", + "@smithy/core": "^3.18.7", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/lib-dynamodb/node_modules/@aws-sdk/types": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", + "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-endpoint-discovery": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-endpoint-discovery/-/middleware-endpoint-discovery-3.936.0.tgz", + "integrity": "sha512-wNJZ8PDw0eQK2x4z1q8JqiDvw9l9xd36EoklVT2CIBt8FnqGdrMGjAx93RRbH3G6Fmvwoe+D3VJXbWHBlhD0Bw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/endpoint-cache": "3.893.0", + "@aws-sdk/types": "3.936.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-endpoint-discovery/node_modules/@aws-sdk/types": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", + "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@aws-sdk/middleware-host-header": { "version": "3.734.0", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.734.0.tgz", @@ -662,54 +1835,222 @@ } }, "node_modules/@aws-sdk/nested-clients": { - "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.758.0.tgz", - "integrity": "sha512-YZ5s7PSvyF3Mt2h1EQulCG93uybprNGbBkPmVuy/HMMfbFTt4iL3SbKjxqvOZelm86epFfj7pvK7FliI2WOEcg==", + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.946.0.tgz", + "integrity": "sha512-rjAtEguukeW8mlyEQMQI56vxFoyWlaNwowmz1p1rav948SUjtrzjHAp4TOQWhibb7AR7BUTHBCgIcyCRjBEf4g==", "license": "Apache-2.0", "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "3.758.0", - "@aws-sdk/middleware-host-header": "3.734.0", - "@aws-sdk/middleware-logger": "3.734.0", - "@aws-sdk/middleware-recursion-detection": "3.734.0", - "@aws-sdk/middleware-user-agent": "3.758.0", - "@aws-sdk/region-config-resolver": "3.734.0", - "@aws-sdk/types": "3.734.0", - "@aws-sdk/util-endpoints": "3.743.0", - "@aws-sdk/util-user-agent-browser": "3.734.0", - "@aws-sdk/util-user-agent-node": "3.758.0", - "@smithy/config-resolver": "^4.0.1", - "@smithy/core": "^3.1.5", - "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/hash-node": "^4.0.1", - "@smithy/invalid-dependency": "^4.0.1", - "@smithy/middleware-content-length": "^4.0.1", - "@smithy/middleware-endpoint": "^4.0.6", - "@smithy/middleware-retry": "^4.0.7", - "@smithy/middleware-serde": "^4.0.2", - "@smithy/middleware-stack": "^4.0.1", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/node-http-handler": "^4.0.3", - "@smithy/protocol-http": "^5.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-body-length-node": "^4.0.0", - "@smithy/util-defaults-mode-browser": "^4.0.7", - "@smithy/util-defaults-mode-node": "^4.0.7", - "@smithy/util-endpoints": "^3.0.1", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-retry": "^4.0.1", - "@smithy/util-utf8": "^4.0.0", + "@aws-sdk/core": "3.946.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.946.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.946.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.7", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", + "@smithy/middleware-endpoint": "^4.3.14", + "@smithy/middleware-retry": "^4.4.14", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.13", + "@smithy/util-defaults-mode-node": "^4.2.16", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/core": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.946.0.tgz", + "integrity": "sha512-u2BkbLLVbMFrEiXrko2+S6ih5sUZPlbVyRPtXOqMHlCyzr70sE8kIiD6ba223rQeIFPcYfW/wHc6k4ihW2xxVg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws-sdk/xml-builder": "3.930.0", + "@smithy/core": "^3.18.7", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/middleware-host-header": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.936.0.tgz", + "integrity": "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/middleware-logger": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.936.0.tgz", + "integrity": "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.936.0.tgz", + "integrity": "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws/lambda-invoke-store": "^0.2.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.946.0.tgz", + "integrity": "sha512-7QcljCraeaWQNuqmOoAyZs8KpZcuhPiqdeeKoRd397jVGNRehLFsZbIMOvwaluUDFY11oMyXOkQEERe1Zo2fCw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.946.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@smithy/core": "^3.18.7", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/region-config-resolver": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.936.0.tgz", + "integrity": "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/types": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", + "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-endpoints": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.936.0.tgz", + "integrity": "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-endpoints": "^3.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.936.0.tgz", + "integrity": "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/nested-clients/node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.946.0.tgz", + "integrity": "sha512-a2UwwvzbK5AxHKUBupfg4s7VnkqRAHjYsuezHnKCniczmT4HZfP1NnfwwvLKEH8qaTrwenxjKSfq4UWmWkvG+Q==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.946.0", + "@aws-sdk/types": "3.936.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, "node_modules/@aws-sdk/region-config-resolver": { "version": "3.734.0", "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.734.0.tgz", @@ -727,17 +2068,99 @@ "node": ">=18.0.0" } }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.758.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.758.0.tgz", - "integrity": "sha512-ckptN1tNrIfQUaGWm/ayW1ddG+imbKN7HHhjFdS4VfItsP0QQOB0+Ov+tpgb4MoNR4JaUghMIVStjIeHN2ks1w==", + "node_modules/@aws-sdk/signature-v4": { + "version": "3.370.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4/-/signature-v4-3.370.0.tgz", + "integrity": "sha512-Mh++NJiXoBxMzz4d8GQPNB37nqjS1gsVwjKoSAWFE67sjgsjb8D5JWRCm9CinqPoXi2iN57+1DcQalTDKQGc0A==", "license": "Apache-2.0", "dependencies": { - "@aws-sdk/nested-clients": "3.758.0", - "@aws-sdk/types": "3.734.0", - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", + "@aws-sdk/eventstream-codec": "3.370.0", + "@aws-sdk/is-array-buffer": "3.310.0", + "@aws-sdk/types": "3.370.0", + "@aws-sdk/util-hex-encoding": "3.310.0", + "@aws-sdk/util-middleware": "3.370.0", + "@aws-sdk/util-uri-escape": "3.310.0", + "@aws-sdk/util-utf8": "3.310.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4/node_modules/@aws-sdk/types": { + "version": "3.370.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.370.0.tgz", + "integrity": "sha512-8PGMKklSkRKjunFhzM2y5Jm0H2TBu7YRNISdYzXLUHKSP9zlMEYagseKVdmox0zKHf1LXVNuSlUV2b6SRrieCQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^1.1.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4/node_modules/@smithy/types": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-1.2.0.tgz", + "integrity": "sha512-z1r00TvBqF3dh4aHhya7nz1HhvCg4TRmw51fjMrh5do3h+ngSstt/yKlNbHeb9QxJmFbmN8KEVSWgb1bRvfEoA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.946.0.tgz", + "integrity": "sha512-a5c+rM6CUPX2ExmUZ3DlbLlS5rQr4tbdoGcgBsjnAHiYx8MuMNAI+8M7wfjF13i2yvUQj5WEIddvLpayfEZj9g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.946.0", + "@aws-sdk/nested-clients": "3.946.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers/node_modules/@aws-sdk/core": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.946.0.tgz", + "integrity": "sha512-u2BkbLLVbMFrEiXrko2+S6ih5sUZPlbVyRPtXOqMHlCyzr70sE8kIiD6ba223rQeIFPcYfW/wHc6k4ihW2xxVg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws-sdk/xml-builder": "3.930.0", + "@smithy/core": "^3.18.7", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers/node_modules/@aws-sdk/types": { + "version": "3.936.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.936.0.tgz", + "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { @@ -757,6 +2180,34 @@ "node": ">=18.0.0" } }, + "node_modules/@aws-sdk/util-buffer-from": { + "version": "3.310.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-buffer-from/-/util-buffer-from-3.310.0.tgz", + "integrity": "sha512-i6LVeXFtGih5Zs8enLrt+ExXY92QV25jtEnTKHsmlFqFAuL3VBeod6boeMXkN2p9lbSVVQ1sAOOYZOHYbYkntw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/is-array-buffer": "3.310.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-dynamodb": { + "version": "3.946.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-dynamodb/-/util-dynamodb-3.946.0.tgz", + "integrity": "sha512-L1i5w52Ue2R4hmvtrZQ/8hmXiGBLrv6YlWKZCU4nN45E16TDLcn0rNMR7DXbLM/vRU8/pvqrhIwJRxuR1sjy1A==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@aws-sdk/client-dynamodb": "^3.946.0" + } + }, "node_modules/@aws-sdk/util-endpoints": { "version": "3.743.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.743.0.tgz", @@ -772,6 +2223,18 @@ "node": ">=18.0.0" } }, + "node_modules/@aws-sdk/util-hex-encoding": { + "version": "3.310.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-hex-encoding/-/util-hex-encoding-3.310.0.tgz", + "integrity": "sha512-sVN7mcCCDSJ67pI1ZMtk84SKGqyix6/0A1Ab163YKn+lFBQRMKexleZzpYzNGxYzmQS6VanP/cfU7NiLQOaSfA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-sdk/util-locate-window": { "version": "3.723.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.723.0.tgz", @@ -784,6 +2247,30 @@ "node": ">=18.0.0" } }, + "node_modules/@aws-sdk/util-middleware": { + "version": "3.370.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-middleware/-/util-middleware-3.370.0.tgz", + "integrity": "sha512-Jvs9FZHaQznWGLkRel3PFEP93I1n0Kp6356zxYHk3LIOmjpzoob3R+v96mzyN+dZrnhPdPubYS41qbU2F9lROg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-uri-escape": { + "version": "3.310.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-uri-escape/-/util-uri-escape-3.310.0.tgz", + "integrity": "sha512-drzt+aB2qo2LgtDoiy/3sVG8w63cgLkqFIa2NFlGpUgHFWTXkqtbgf4L5QdjRGKWhmZsnqkbtL7vkSWEcYDJ4Q==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@aws-sdk/util-user-agent-browser": { "version": "3.734.0", "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.734.0.tgz", @@ -820,6 +2307,81 @@ } } }, + "node_modules/@aws-sdk/util-utf8": { + "version": "3.310.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8/-/util-utf8-3.310.0.tgz", + "integrity": "sha512-DnLfFT8uCO22uOJc0pt0DsSNau1GTisngBCDw8jQuWT5CqogMJu4b/uXmwEqfj8B3GX6Xsz8zOd6JpRlPftQoA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/util-buffer-from": "3.310.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/util-utf8-browser": { + "version": "3.259.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", + "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.3.1" + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.930.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.930.0.tgz", + "integrity": "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/@aws-sdk/xml-builder/node_modules/strnum": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.1.tgz", + "integrity": "sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.2.tgz", + "integrity": "sha512-C0NBLsIqzDIae8HFw9YIrIBsbc0xTiOtt7fAukGPnqQ/+zZNaq+4jhuccltK0QuWHBnNm/a6kLIRA6GFiM10eg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", @@ -3063,12 +4625,12 @@ } }, "node_modules/@smithy/abort-controller": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.0.1.tgz", - "integrity": "sha512-fiUIYgIgRjMWznk6iLJz35K2YxSLHzLBA/RC6lBrKfQ8fHbPfvk7Pk9UvpKoHgJjI18MnbPuEju53zcVy6KF1g==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.5.tgz", + "integrity": "sha512-j7HwVkBw68YW8UmFRcjZOmssE77Rvk0GWAIN1oFBhsaovQmZWYCIcGa9/pwRB0ExI8Sk9MWNALTjftjHZea7VA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { @@ -3076,15 +4638,16 @@ } }, "node_modules/@smithy/config-resolver": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.0.1.tgz", - "integrity": "sha512-Igfg8lKu3dRVkTSEm98QpZUvKEOa71jDX4vKRcvJVyRc3UgN3j7vFMf0s7xLQhYmKa8kyJGQgUJDOV5V3neVlQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.3.tgz", + "integrity": "sha512-ezHLe1tKLUxDJo2LHtDuEDyWXolw8WGOR92qb4bQdWq/zKenO5BvctZGrVJBK08zjezSk7bmbKFOXIVyChvDLw==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-config-provider": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" }, "engines": { @@ -3092,18 +4655,20 @@ } }, "node_modules/@smithy/core": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.1.5.tgz", - "integrity": "sha512-HLclGWPkCsekQgsyzxLhCQLa8THWXtB5PxyYN+2O6nkyLt550KQKTlbV2D1/j5dNIQapAZM1+qFnpBFxZQkgCA==", + "version": "3.18.7", + "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.18.7.tgz", + "integrity": "sha512-axG9MvKhMWOhFbvf5y2DuyTxQueO0dkedY9QC3mAfndLosRI/9LJv8WaL0mw7ubNhsO4IuXX9/9dYGPFvHrqlw==", "license": "Apache-2.0", "dependencies": { - "@smithy/middleware-serde": "^4.0.2", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-body-length-browser": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-stream": "^4.1.2", - "@smithy/util-utf8": "^4.0.0", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-stream": "^4.5.6", + "@smithy/util-utf8": "^4.2.0", + "@smithy/uuid": "^1.1.0", "tslib": "^2.6.2" }, "engines": { @@ -3111,15 +4676,15 @@ } }, "node_modules/@smithy/credential-provider-imds": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.0.1.tgz", - "integrity": "sha512-l/qdInaDq1Zpznpmev/+52QomsJNZ3JkTl5yrTl02V6NBgJOQ4LY0SFw/8zsMwj3tLe8vqiIuwF6nxaEwgf6mg==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.5.tgz", + "integrity": "sha512-BZwotjoZWn9+36nimwm/OLIcVe+KYRwzMjfhd4QT7QxPm9WY0HiOV8t/Wlh+HVUif0SBVV7ksq8//hPaBC/okQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", "tslib": "^2.6.2" }, "engines": { @@ -3127,15 +4692,15 @@ } }, "node_modules/@smithy/fetch-http-handler": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.0.1.tgz", - "integrity": "sha512-3aS+fP28urrMW2KTjb6z9iFow6jO8n3MFfineGbndvzGZit3taZhKWtTorf+Gp5RpFDDafeHlhfsGlDCXvUnJA==", + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.6.tgz", + "integrity": "sha512-3+RG3EA6BBJ/ofZUeTFJA7mHfSYrZtQIrDP9dI8Lf7X6Jbos2jptuLrAAteDiFVrmbEmLSuRG/bUKzfAXk7dhg==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.0.1", - "@smithy/querystring-builder": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-base64": "^4.0.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/querystring-builder": "^4.2.5", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", "tslib": "^2.6.2" }, "engines": { @@ -3143,14 +4708,14 @@ } }, "node_modules/@smithy/hash-node": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.0.1.tgz", - "integrity": "sha512-TJ6oZS+3r2Xu4emVse1YPB3Dq3d8RkZDKcPr71Nj/lJsdAP1c7oFzYqEn1IBc915TsgLl2xIJNuxCz+gLbLE0w==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.5.tgz", + "integrity": "sha512-DpYX914YOfA3UDT9CN1BM787PcHfWRBB43fFGCYrZFUH0Jv+5t8yYl+Pd5PW4+QzoGEDvn5d5QIO4j2HyYZQSA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.1.0", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/types": "^4.9.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -3158,12 +4723,12 @@ } }, "node_modules/@smithy/invalid-dependency": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.0.1.tgz", - "integrity": "sha512-gdudFPf4QRQ5pzj7HEnu6FhKRi61BfH/Gk5Yf6O0KiSbr1LlVhgjThcvjdu658VE6Nve8vaIWB8/fodmS1rBPQ==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.5.tgz", + "integrity": "sha512-2L2erASEro1WC5nV+plwIMxrTXpvpfzl4e+Nre6vBVRR2HKeGGcvpJyyL3/PpiSg+cJG2KpTmZmq934Olb6e5A==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { @@ -3171,9 +4736,9 @@ } }, "node_modules/@smithy/is-array-buffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.0.0.tgz", - "integrity": "sha512-saYhF8ZZNoJDTvJBEWgeBccCg+yvp1CX+ed12yORU3NilJScfc6gfch2oVb4QgxZrGUx3/ZJlb+c/dJbyupxlw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", + "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -3183,13 +4748,13 @@ } }, "node_modules/@smithy/middleware-content-length": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.0.1.tgz", - "integrity": "sha512-OGXo7w5EkB5pPiac7KNzVtfCW2vKBTZNuCctn++TTSOMpe6RZO/n6WEC1AxJINn3+vWLKW49uad3lo/u0WJ9oQ==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.5.tgz", + "integrity": "sha512-Y/RabVa5vbl5FuHYV2vUCwvh/dqzrEY/K2yWPSqvhFUwIY0atLqO4TienjBXakoy4zrKAMCZwg+YEqmH7jaN7A==", "license": "Apache-2.0", "dependencies": { - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { @@ -3197,18 +4762,18 @@ } }, "node_modules/@smithy/middleware-endpoint": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.0.6.tgz", - "integrity": "sha512-ftpmkTHIFqgaFugcjzLZv3kzPEFsBFSnq1JsIkr2mwFzCraZVhQk2gqN51OOeRxqhbPTkRFj39Qd2V91E/mQxg==", + "version": "4.3.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.3.14.tgz", + "integrity": "sha512-v0q4uTKgBM8dsqGjqsabZQyH85nFaTnFcgpWU1uydKFsdyyMzfvOkNum9G7VK+dOP01vUnoZxIeRiJ6uD0kjIg==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.1.5", - "@smithy/middleware-serde": "^4.0.2", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", - "@smithy/url-parser": "^4.0.1", - "@smithy/util-middleware": "^4.0.1", + "@smithy/core": "^3.18.7", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-middleware": "^4.2.5", "tslib": "^2.6.2" }, "engines": { @@ -3216,32 +4781,33 @@ } }, "node_modules/@smithy/middleware-retry": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.0.7.tgz", - "integrity": "sha512-58j9XbUPLkqAcV1kHzVX/kAR16GT+j7DUZJqwzsxh1jtz7G82caZiGyyFgUvogVfNTg3TeAOIJepGc8TXF4AVQ==", + "version": "4.4.14", + "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.14.tgz", + "integrity": "sha512-Z2DG8Ej7FyWG1UA+7HceINtSLzswUgs2np3sZX0YBBxCt+CXG4QUxv88ZDS3+2/1ldW7LqtSY1UO/6VQ1pND8Q==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/service-error-classification": "^4.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-retry": "^4.0.1", - "tslib": "^2.6.2", - "uuid": "^9.0.1" + "@smithy/node-config-provider": "^4.3.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/service-error-classification": "^4.2.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/uuid": "^1.1.0", + "tslib": "^2.6.2" }, "engines": { "node": ">=18.0.0" } }, "node_modules/@smithy/middleware-serde": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.0.2.tgz", - "integrity": "sha512-Sdr5lOagCn5tt+zKsaW+U2/iwr6bI9p08wOkCp6/eL6iMbgdtc2R5Ety66rf87PeohR0ExI84Txz9GYv5ou3iQ==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.6.tgz", + "integrity": "sha512-VkLoE/z7e2g8pirwisLz8XJWedUSY8my/qrp81VmAdyrhi94T+riBfwP+AOEEFR9rFTSonC/5D2eWNmFabHyGQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { @@ -3249,12 +4815,12 @@ } }, "node_modules/@smithy/middleware-stack": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.0.1.tgz", - "integrity": "sha512-dHwDmrtR/ln8UTHpaIavRSzeIk5+YZTBtLnKwDW3G2t6nAupCiQUvNzNoHBpik63fwUaJPtlnMzXbQrNFWssIA==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.5.tgz", + "integrity": "sha512-bYrutc+neOyWxtZdbB2USbQttZN0mXaOyYLIsaTbJhFsfpXyGWUxJpEuO1rJ8IIJm2qH4+xJT0mxUSsEDTYwdQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { @@ -3262,14 +4828,14 @@ } }, "node_modules/@smithy/node-config-provider": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.0.1.tgz", - "integrity": "sha512-8mRTjvCtVET8+rxvmzRNRR0hH2JjV0DFOmwXPrISmTIJEfnCBugpYYGAsCj8t41qd+RB5gbheSQ/6aKZCQvFLQ==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.5.tgz", + "integrity": "sha512-UTurh1C4qkVCtqggI36DGbLB2Kv8UlcFdMXDcWMbqVY2uRg0XmT9Pb4Vj6oSQ34eizO1fvR0RnFV4Axw4IrrAg==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.0.1", - "@smithy/shared-ini-file-loader": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { @@ -3277,15 +4843,15 @@ } }, "node_modules/@smithy/node-http-handler": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.0.3.tgz", - "integrity": "sha512-dYCLeINNbYdvmMLtW0VdhW1biXt+PPCGazzT5ZjKw46mOtdgToQEwjqZSS9/EN8+tNs/RO0cEWG044+YZs97aA==", + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.5.tgz", + "integrity": "sha512-CMnzM9R2WqlqXQGtIlsHMEZfXKJVTIrqCNoSd/QpAyp+Dw0a1Vps13l6ma1fH8g7zSPNsA59B/kWgeylFuA/lw==", "license": "Apache-2.0", "dependencies": { - "@smithy/abort-controller": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/querystring-builder": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/abort-controller": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/querystring-builder": "^4.2.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { @@ -3293,12 +4859,12 @@ } }, "node_modules/@smithy/property-provider": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.0.1.tgz", - "integrity": "sha512-o+VRiwC2cgmk/WFV0jaETGOtX16VNPp2bSQEzu0whbReqE1BMqsP2ami2Vi3cbGVdKu1kq9gQkDAGKbt0WOHAQ==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.5.tgz", + "integrity": "sha512-8iLN1XSE1rl4MuxvQ+5OSk/Zb5El7NJZ1td6Tn+8dQQHIjp59Lwl6bd0+nzw6SKm2wSSriH2v/I9LPzUic7EOg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { @@ -3306,12 +4872,12 @@ } }, "node_modules/@smithy/protocol-http": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.0.1.tgz", - "integrity": "sha512-TE4cpj49jJNB/oHyh/cRVEgNZaoPaxd4vteJNB0yGidOCVR0jCw/hjPVsT8Q8FRmj8Bd3bFZt8Dh7xGCT+xMBQ==", + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.5.tgz", + "integrity": "sha512-RlaL+sA0LNMp03bf7XPbFmT5gN+w3besXSWMkA8rcmxLSVfiEXElQi4O2IWwPfxzcHkxqrwBFMbngB8yx/RvaQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { @@ -3319,13 +4885,13 @@ } }, "node_modules/@smithy/querystring-builder": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.0.1.tgz", - "integrity": "sha512-wU87iWZoCbcqrwszsOewEIuq+SU2mSoBE2CcsLwE0I19m0B2gOJr1MVjxWcDQYOzHbR1xCk7AcOBbGFUYOKvdg==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.5.tgz", + "integrity": "sha512-y98otMI1saoajeik2kLfGyRp11e5U/iJYH/wLCh3aTV/XutbGT9nziKGkgCaMD1ghK7p6htHMm6b6scl9JRUWg==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.1.0", - "@smithy/util-uri-escape": "^4.0.0", + "@smithy/types": "^4.9.0", + "@smithy/util-uri-escape": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -3333,12 +4899,12 @@ } }, "node_modules/@smithy/querystring-parser": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.0.1.tgz", - "integrity": "sha512-Ma2XC7VS9aV77+clSFylVUnPZRindhB7BbmYiNOdr+CHt/kZNJoPP0cd3QxCnCFyPXC4eybmyE98phEHkqZ5Jw==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.5.tgz", + "integrity": "sha512-031WCTdPYgiQRYNPXznHXof2YM0GwL6SeaSyTH/P72M1Vz73TvCNH2Nq8Iu2IEPq9QP2yx0/nrw5YmSeAi/AjQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { @@ -3346,24 +4912,24 @@ } }, "node_modules/@smithy/service-error-classification": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.0.1.tgz", - "integrity": "sha512-3JNjBfOWpj/mYfjXJHB4Txc/7E4LVq32bwzE7m28GN79+M1f76XHflUaSUkhOriprPDzev9cX/M+dEB80DNDKA==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.5.tgz", + "integrity": "sha512-8fEvK+WPE3wUAcDvqDQG1Vk3ANLR8Px979te96m84CbKAjBVf25rPYSzb4xU4hlTyho7VhOGnh5i62D/JVF0JQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.1.0" + "@smithy/types": "^4.9.0" }, "engines": { "node": ">=18.0.0" } }, "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.0.1.tgz", - "integrity": "sha512-hC8F6qTBbuHRI/uqDgqqi6J0R4GtEZcgrZPhFQnMhfJs3MnUTGSnR1NSJCJs5VWlMydu0kJz15M640fJlRsIOw==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.0.tgz", + "integrity": "sha512-5WmZ5+kJgJDjwXXIzr1vDTG+RhF9wzSODQBfkrQ2VVkYALKGvZX1lgVSxEkgicSAFnFhPj5rudJV0zoinqS0bA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { @@ -3371,18 +4937,18 @@ } }, "node_modules/@smithy/signature-v4": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.0.1.tgz", - "integrity": "sha512-nCe6fQ+ppm1bQuw5iKoeJ0MJfz2os7Ic3GBjOkLOPtavbD1ONoyE3ygjBfz2ythFWm4YnRm6OxW+8p/m9uCoIA==", + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.5.tgz", + "integrity": "sha512-xSUfMu1FT7ccfSXkoLl/QRQBi2rOvi3tiBZU2Tdy3I6cgvZ6SEi9QNey+lqps/sJRnogIS+lq+B1gxxbra2a/w==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-middleware": "^4.0.1", - "@smithy/util-uri-escape": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-uri-escape": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -3390,17 +4956,17 @@ } }, "node_modules/@smithy/smithy-client": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.1.6.tgz", - "integrity": "sha512-UYDolNg6h2O0L+cJjtgSyKKvEKCOa/8FHYJnBobyeoeWDmNpXjwOAtw16ezyeu1ETuuLEOZbrynK0ZY1Lx9Jbw==", + "version": "4.9.10", + "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.9.10.tgz", + "integrity": "sha512-Jaoz4Jw1QYHc1EFww/E6gVtNjhoDU+gwRKqXP6C3LKYqqH2UQhP8tMP3+t/ePrhaze7fhLE8vS2q6vVxBANFTQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/core": "^3.1.5", - "@smithy/middleware-endpoint": "^4.0.6", - "@smithy/middleware-stack": "^4.0.1", - "@smithy/protocol-http": "^5.0.1", - "@smithy/types": "^4.1.0", - "@smithy/util-stream": "^4.1.2", + "@smithy/core": "^3.18.7", + "@smithy/middleware-endpoint": "^4.3.14", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" }, "engines": { @@ -3408,9 +4974,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.1.0.tgz", - "integrity": "sha512-enhjdwp4D7CXmwLtD6zbcDMbo6/T6WtuuKCY49Xxc6OMOmUWlBEBDREsxxgV2LIdeQPW756+f97GzcgAwp3iLw==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.9.0.tgz", + "integrity": "sha512-MvUbdnXDTwykR8cB1WZvNNwqoWVaTRA0RLlLmf/cIFNMM2cKWz01X4Ly6SMC4Kks30r8tT3Cty0jmeWfiuyHTA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -3420,13 +4986,13 @@ } }, "node_modules/@smithy/url-parser": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.0.1.tgz", - "integrity": "sha512-gPXcIEUtw7VlK8f/QcruNXm7q+T5hhvGu9tl63LsJPZ27exB6dtNwvh2HIi0v7JcXJ5emBxB+CJxwaLEdJfA+g==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.5.tgz", + "integrity": "sha512-VaxMGsilqFnK1CeBX+LXnSuaMx4sTL/6znSZh2829txWieazdVxr54HmiyTsIbpOTLcf5nYpq9lpzmwRdxj6rQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/querystring-parser": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/querystring-parser": "^4.2.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { @@ -3434,13 +5000,13 @@ } }, "node_modules/@smithy/util-base64": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.0.0.tgz", - "integrity": "sha512-CvHfCmO2mchox9kjrtzoHkWHxjHZzaFojLc8quxXY7WAAMAg43nuxwv95tATVgQFNDwd4M9S1qFzj40Ul41Kmg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", + "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -3448,9 +5014,9 @@ } }, "node_modules/@smithy/util-body-length-browser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.0.0.tgz", - "integrity": "sha512-sNi3DL0/k64/LO3A256M+m3CDdG6V7WKWHdAiBBMUN8S3hK3aMPhwnPik2A/a2ONN+9doY9UxaLfgqsIRg69QA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", + "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -3460,9 +5026,9 @@ } }, "node_modules/@smithy/util-body-length-node": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.0.0.tgz", - "integrity": "sha512-q0iDP3VsZzqJyje8xJWEJCNIu3lktUGVoSy1KB0UWym2CL1siV3artm+u1DFYTLejpsrdGyCSWBdGNjJzfDPjg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", + "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -3472,12 +5038,12 @@ } }, "node_modules/@smithy/util-buffer-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.0.0.tgz", - "integrity": "sha512-9TOQ7781sZvddgO8nxueKi3+yGvkY35kotA0Y6BWRajAv8jjmigQ1sBwz0UX47pQMYXJPahSKEKYFgt+rXdcug==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", + "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", "license": "Apache-2.0", "dependencies": { - "@smithy/is-array-buffer": "^4.0.0", + "@smithy/is-array-buffer": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -3485,9 +5051,9 @@ } }, "node_modules/@smithy/util-config-provider": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.0.0.tgz", - "integrity": "sha512-L1RBVzLyfE8OXH+1hsJ8p+acNUSirQnWQ6/EgpchV88G6zGBTDPdXiiExei6Z1wR2RxYvxY/XLw6AMNCCt8H3w==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", + "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -3497,15 +5063,14 @@ } }, "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.0.7.tgz", - "integrity": "sha512-CZgDDrYHLv0RUElOsmZtAnp1pIjwDVCSuZWOPhIOBvG36RDfX1Q9+6lS61xBf+qqvHoqRjHxgINeQz47cYFC2Q==", + "version": "4.3.13", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.13.tgz", + "integrity": "sha512-hlVLdAGrVfyNei+pKIgqDTxfu/ZI2NSyqj4IDxKd5bIsIqwR/dSlkxlPaYxFiIaDVrBy0he8orsFy+Cz119XvA==", "license": "Apache-2.0", "dependencies": { - "@smithy/property-provider": "^4.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", - "bowser": "^2.11.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { @@ -3513,17 +5078,17 @@ } }, "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.0.7.tgz", - "integrity": "sha512-79fQW3hnfCdrfIi1soPbK3zmooRFnLpSx3Vxi6nUlqaaQeC5dm8plt4OTNDNqEEEDkvKghZSaoti684dQFVrGQ==", + "version": "4.2.16", + "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.16.tgz", + "integrity": "sha512-F1t22IUiJLHrxW9W1CQ6B9PN+skZ9cqSuzB18Eh06HrJPbjsyZ7ZHecAKw80DQtyGTRcVfeukKaCRYebFwclbg==", "license": "Apache-2.0", "dependencies": { - "@smithy/config-resolver": "^4.0.1", - "@smithy/credential-provider-imds": "^4.0.1", - "@smithy/node-config-provider": "^4.0.1", - "@smithy/property-provider": "^4.0.1", - "@smithy/smithy-client": "^4.1.6", - "@smithy/types": "^4.1.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/smithy-client": "^4.9.10", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { @@ -3531,13 +5096,13 @@ } }, "node_modules/@smithy/util-endpoints": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.0.1.tgz", - "integrity": "sha512-zVdUENQpdtn9jbpD9SCFK4+aSiavRb9BxEtw9ZGUR1TYo6bBHbIoi7VkrFQ0/RwZlzx0wRBaRmPclj8iAoJCLA==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.5.tgz", + "integrity": "sha512-3O63AAWu2cSNQZp+ayl9I3NapW1p1rR5mlVHcF6hAB1dPZUQFfRPYtplWX/3xrzWthPGj5FqB12taJJCfH6s8A==", "license": "Apache-2.0", "dependencies": { - "@smithy/node-config-provider": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { @@ -3545,9 +5110,9 @@ } }, "node_modules/@smithy/util-hex-encoding": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.0.0.tgz", - "integrity": "sha512-Yk5mLhHtfIgW2W2WQZWSg5kuMZCVbvhFmC7rV4IO2QqnZdbEFPmQnCcGMAX2z/8Qj3B9hYYNjZOhWym+RwhePw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", + "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -3557,12 +5122,12 @@ } }, "node_modules/@smithy/util-middleware": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.0.1.tgz", - "integrity": "sha512-HiLAvlcqhbzhuiOa0Lyct5IIlyIz0PQO5dnMlmQ/ubYM46dPInB+3yQGkfxsk6Q24Y0n3/JmcA1v5iEhmOF5mA==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.5.tgz", + "integrity": "sha512-6Y3+rvBF7+PZOc40ybeZMcGln6xJGVeY60E7jy9Mv5iKpMJpHgRE6dKy9ScsVxvfAYuEX4Q9a65DQX90KaQ3bA==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.1.0", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { @@ -3570,13 +5135,13 @@ } }, "node_modules/@smithy/util-retry": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.0.1.tgz", - "integrity": "sha512-WmRHqNVwn3kI3rKk1LsKcVgPBG6iLTBGC1iYOV3GQegwJ3E8yjzHytPt26VNzOWr1qu0xE03nK0Ug8S7T7oufw==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.5.tgz", + "integrity": "sha512-GBj3+EZBbN4NAqJ/7pAhsXdfzdlznOh8PydUijy6FpNIMnHPSMO2/rP4HKu+UFeikJxShERk528oy7GT79YiJg==", "license": "Apache-2.0", "dependencies": { - "@smithy/service-error-classification": "^4.0.1", - "@smithy/types": "^4.1.0", + "@smithy/service-error-classification": "^4.2.5", + "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "engines": { @@ -3584,18 +5149,18 @@ } }, "node_modules/@smithy/util-stream": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.1.2.tgz", - "integrity": "sha512-44PKEqQ303d3rlQuiDpcCcu//hV8sn+u2JBo84dWCE0rvgeiVl0IlLMagbU++o0jCWhYCsHaAt9wZuZqNe05Hw==", + "version": "4.5.6", + "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.6.tgz", + "integrity": "sha512-qWw/UM59TiaFrPevefOZ8CNBKbYEP6wBAIlLqxn3VAIo9rgnTNc4ASbVrqDmhuwI87usnjhdQrxodzAGFFzbRQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/fetch-http-handler": "^5.0.1", - "@smithy/node-http-handler": "^4.0.3", - "@smithy/types": "^4.1.0", - "@smithy/util-base64": "^4.0.0", - "@smithy/util-buffer-from": "^4.0.0", - "@smithy/util-hex-encoding": "^4.0.0", - "@smithy/util-utf8": "^4.0.0", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-buffer-from": "^4.2.0", + "@smithy/util-hex-encoding": "^4.2.0", + "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" }, "engines": { @@ -3603,9 +5168,9 @@ } }, "node_modules/@smithy/util-uri-escape": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.0.0.tgz", - "integrity": "sha512-77yfbCbQMtgtTylO9itEAdpPXSog3ZxMe09AEhm0dU0NLTalV70ghDZFR+Nfi1C60jnJoh/Re4090/DuZh2Omg==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", + "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -3615,12 +5180,38 @@ } }, "node_modules/@smithy/util-utf8": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.0.0.tgz", - "integrity": "sha512-b+zebfKCfRdgNJDknHCob3O7FpeYQN6ZG6YLExMcasDHsCXlsXCEuiPZeLnJLpwa5dvPetGlnGCiMHuLwGvFow==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", + "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-4.2.5.tgz", + "integrity": "sha512-Dbun99A3InifQdIrsXZ+QLcC0PGBPAdrl4cj1mTgJvyc9N2zf7QSxg8TBkzsCmGJdE3TLbO9ycwpY0EkWahQ/g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/abort-controller": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/uuid": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", + "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", "license": "Apache-2.0", "dependencies": { - "@smithy/util-buffer-from": "^4.0.0", "tslib": "^2.6.2" }, "engines": { @@ -10041,6 +11632,15 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/mnemonist": { + "version": "0.38.3", + "resolved": "https://registry.npmjs.org/mnemonist/-/mnemonist-0.38.3.tgz", + "integrity": "sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==", + "license": "MIT", + "dependencies": { + "obliterator": "^1.6.1" + } + }, "node_modules/module-details-from-path": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", @@ -10318,6 +11918,12 @@ "node": ">= 0.4" } }, + "node_modules/obliterator": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/obliterator/-/obliterator-1.6.1.tgz", + "integrity": "sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==", + "license": "MIT" + }, "node_modules/oidc-token-hash": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.1.0.tgz", diff --git a/package.json b/package.json index 755c52f..17625d4 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,12 @@ "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-sdk/client-dynamodb": "^3.414.0", "@aws-sdk/client-secrets-manager": "^3.414.0", + "@aws-sdk/lib-dynamodb": "^3.414.0", + "@aws-sdk/credential-provider-node": "^3.940.0", + "@aws-sdk/signature-v4": "^3.370.0", "@dadosfera/dadosfera-logs": "^1.0.0-beta.4", "@dadosfera/protospack": "2.5.3", "@dadosfera/protospack-v2": "3.38.0-beta.10", @@ -78,8 +83,8 @@ "multer": "1.4.5-lts.1" }, "devDependencies": { - "@types/cookie-parser": "^1.4.9", "@types/cache-manager": "^4.0.6", + "@types/cookie-parser": "^1.4.9", "@types/express": "^4.17.17", "@types/express-session": "^1.18.1", "@types/jest": "27.0.2", diff --git a/src/app.module.ts b/src/app.module.ts index 9de9b19..27d042c 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -33,6 +33,7 @@ import { NetworkPolicyModule } from './modules/network-policy/network-policy.mod import { AssignModule } from './modules/assign/assign.module'; import { ShareMetadataModule } from './modules/share-metadata/share-metadata.module'; import { ApiKeyModule } from './modules/api-key/api-key.module'; +import { PlatformApiModule } from './modules/platform-api/platform-api.module'; @Module({ providers: [ @@ -73,6 +74,7 @@ import { ApiKeyModule } from './modules/api-key/api-key.module'; ApiKeyModule, IdentityProviderModule, NetworkPolicyModule, + PlatformApiModule, //Always leave HealthModule last, so it is on the bottom of swagger HealthModule, ], diff --git a/src/modules/platform-api/platform-api.config.ts b/src/modules/platform-api/platform-api.config.ts new file mode 100644 index 0000000..5875059 --- /dev/null +++ b/src/modules/platform-api/platform-api.config.ts @@ -0,0 +1,11 @@ +export const PLATFORM_API_CONFIG = { + getUrl: (): string => { + const url = process.env.PLATFORM_API_URL; + if (!url) { + throw new Error('PLATFORM_API_URL environment variable is not set'); + } + return url; + }, + region: process.env.AWS_REGION || 'us-east-1', + timeout: parseInt(process.env.PLATFORM_API_TIMEOUT || '30000', 10), +}; diff --git a/src/modules/platform-api/platform-api.controller.ts b/src/modules/platform-api/platform-api.controller.ts new file mode 100644 index 0000000..133b239 --- /dev/null +++ b/src/modules/platform-api/platform-api.controller.ts @@ -0,0 +1,663 @@ +import { + Controller, + Get, + Post, + Put, + Patch, + Delete, + Param, + Body, + Query, + Inject, + BadRequestException, +} from '@nestjs/common'; +import { ApiTags, ApiOperation } from '@nestjs/swagger'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; + +import { + Authenticated, + RequireAllPermissions, +} from '../../decorators/authentication.decorator'; +import { User, RequestUser } from '../../decorators/user.decorator'; +import { PlatformApiService } from './platform-api.service'; +import { PERMISSIONS_GROUPS } from '../../authentication/permissions.enum'; +import { ElasticsearchService } from '../../services/elasticsearch'; +import { DynamoDBService } from '../../services/dynamodb'; + +@ApiTags('Platform API') +@Controller('platform') +export class PlatformApiController { + private logger: any; + + constructor( + private readonly platformApiService: PlatformApiService, + private readonly elasticsearchService: ElasticsearchService, + private readonly dynamoDBService: DynamoDBService, + @Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger, + ) { + this.logger = dadosferaLogger.logger; + } + + /** + * Normalize pipeline ID to match Platform-API format. + * Platform-API replaces '-' with '_' in pipeline IDs. + */ + private normalizePipelineId(id: string): string { + return id?.replace(/-/g, '_') || ''; + } + + private readonly VALID_CONNECTORS = ['jdbc', 'singer', 's3']; + + /** + * Validate that connector is provided and is a valid type. + */ + private validateConnector(connector: string | undefined): void { + if (!connector || !this.VALID_CONNECTORS.includes(connector)) { + throw new BadRequestException( + `connector is required in job input and must be one of: ${this.VALID_CONNECTORS.join(', ')}`, + ); + } + } + + /** + * Map connector type to DynamoDB type. + * jdbc -> 'database', singer -> 'application', s3 -> 'file' + */ + private mapConnectorToDynamoType(connector: string): string { + switch (connector) { + case 'jdbc': + return 'database'; + case 'singer': + return 'application'; + case 's3': + return 'file'; + default: + return connector; + } + } + + /** + * Extract and transform tables from jobs for DynamoDB input. + * Maps connector-specific fields to a common table format. + * + * - JDBC: load_type, table_name, column_include_list (columns), incremental_column_name (reference_column) + * - Singer: type maps replication_method (FULL_TABLE -> full_load, INCREMENTAL -> incremental), no columns + * - S3: same mapping as Singer, no columns + */ + private extractTablesFromJobs(jobs: any[], connector: string): Array<{ + name: string; + type: string; + columns?: string[]; + reference_column?: { name: string; type: string }; + }> { + if (!jobs || jobs.length === 0) return []; + + const tables: Array<{ + name: string; + type: string; + columns?: string[]; + reference_column?: { name: string; type: string }; + }> = []; + + for (const job of jobs) { + const input = job.input; + if (!input) continue; + + if (connector === 'jdbc') { + // JDBC: table_name, load_type, column_include_list, incremental_column_name + const table: { + name: string; + type: string; + columns?: string[]; + reference_column?: { name: string; type: string }; + } = { + name: input.table_name || '', + type: input.load_type || 'full_load', + }; + if (input.column_include_list && input.column_include_list.length > 0) { + table.columns = input.column_include_list; + } + if (input.incremental_column_name) { + table.reference_column = { + name: input.incremental_column_name, + type: input.incremental_column_type || 'timestamp', + }; + } + tables.push(table); + } else if (connector === 'singer' || connector === 's3') { + // Singer/S3: replication_method -> type mapping, no columns + let type = 'full_load'; + if (input.replication_method === 'INCREMENTAL') { + type = 'incremental'; + } else if (input.replication_method === 'FULL_TABLE') { + type = 'full_load'; + } + + tables.push({ + name: input.table_name || '', + type, + }); + } + } + + return tables; + } + + /** + * Build properties object for Elasticsearch based on connector type. + * Different connectors have different property structures. + * + * Note: In pi-factory flow, properties come pre-built from frontend. + * In Maestro proxy flow, we reconstruct from job input fields. + */ + private buildPipelineProperties(jobInput: any): Record { + if (!jobInput) return {}; + + const connector = jobInput.connector; + const properties: Record = {}; + + // Determine credentials_type + if (jobInput.auth_parameters?.credentials_type) { + properties.credentials_type = jobInput.auth_parameters.credentials_type; + } else { + // Default based on connector type + // S3 connector typically uses iam_user, others use basic_auth + properties.credentials_type = connector === 's3' ? 'iam_user' : 'basic_auth'; + } + + if (connector === 'jdbc') { + // JDBC connectors: schema comes from table_schema + if (jobInput.table_schema) { + properties.schema = jobInput.table_schema; + } + } else if (connector === 'singer') { + // Singer connectors: merge config fields (dates, selected_competitions, etc.) + if (jobInput.config) { + Object.assign(properties, jobInput.config); + } + } else if (connector === 's3') { + // S3 connector + if (jobInput.engine) properties.engine = jobInput.engine; + if (jobInput.source_bucket) properties.source_bucket = jobInput.source_bucket; + if (jobInput.source_prefix) properties.source_prefix = jobInput.source_prefix; + if (jobInput.file_format_params) properties.file_format_params = jobInput.file_format_params; + } + + return properties; + } + + // ==================== PIPELINE ROUTES ==================== + + @Post('pipeline') + @ApiOperation({ summary: 'Create a new pipeline' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.CREATE) + async createPipeline(@Body() body: any, @User() user: RequestUser) { + // Validate that pipeline has jobs + if (!body.jobs || body.jobs.length === 0) { + throw new BadRequestException('Pipeline must have at least one job'); + } + + // Validate connector before proceeding + const firstJob = body.jobs[0]?.input; + this.validateConnector(firstJob?.connector); + + // Inject customer_id (actually customer_name) and normalized ID into body for Platform-API + // Note: Platform-API was created before customer_id existed, so it expects customer_name in the customer_id field + const enrichedBody = { + ...body, + id: this.normalizePipelineId(body.id), + customer_id: user.customer_name, + }; + + const result = await this.platformApiService.proxy('POST', '/pipeline', user, enrichedBody); + + // Sync to Elasticsearch and DynamoDB + try { + const plugin = firstJob?.plugin; + const connectorType = firstJob?.connector; + const connectionId = firstJob?.auth_parameters?.config_id; + + // Build properties based on connector type + const properties = this.buildPipelineProperties(firstJob); + + const connector = plugin + ? await this.elasticsearchService.getConnectorByPlugin(plugin) + : null; + + // Extract tables from jobs and create DynamoDB input + const tables = this.extractTablesFromJobs(body.jobs, connectorType); + let inputId: string | undefined; + + if (tables.length > 0) { + const inputDoc = await this.dynamoDBService.createInput( + user.customer_id, + user.user_id, + { + name: body.name, + description: body.description, + plugin: plugin || '', + type: this.mapConnectorToDynamoType(connectorType), + tables, + }, + ); + inputId = inputDoc.id; + this.logger.info('Created DynamoDB input for tables config', { + inputId, + pipelineId: body.id, + tablesCount: tables.length, + }); + } + + this.logger.info('Syncing pipeline to Elasticsearch', { + customerName: user.customer_name, + pipelineId: body.id, + plugin, + connector: connectorType, + properties, + inputId, + }); + + // Keep original UUID format for Elasticsearch (not normalized) + await this.elasticsearchService.createPipeline( + user.customer_name, + body.id, + { + name: body.name, + description: body.description, + user_id: user.user_id, + username: user.username, + customer_id: user.customer_id, + plugin, + connection_id: connectionId, + cron: body.cron, + tables: inputId, + properties, + }, + connector, + ); + } catch (error) { + this.logger.error('Failed to sync pipeline creation to Elasticsearch/DynamoDB', { + pipelineId: body.id, + customerName: user.customer_name, + error: error.message, + errorName: error.name, + }); + } + + return result; + } + + @Get('pipelines') + @ApiOperation({ summary: 'List all pipelines for customer' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.GET) + async getPipelines( + @User() user: RequestUser, + @Query() query: Record, + ) { + return this.platformApiService.proxy( + 'GET', + '/pipelines', + user, + undefined, + query, + ); + } + + @Get('pipeline/:pipelineId') + @ApiOperation({ summary: 'Get pipeline by ID' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.GET) + async getPipeline( + @Param('pipelineId') pipelineId: string, + @User() user: RequestUser, + ) { + const normalizedId = this.normalizePipelineId(pipelineId); + return this.platformApiService.proxy('GET', `/pipeline/${normalizedId}`, user); + } + + @Patch('pipeline/:pipelineId') + @ApiOperation({ summary: 'Update pipeline by ID' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE) + async updatePipeline( + @Param('pipelineId') pipelineId: string, + @Body() body: any, + @User() user: RequestUser, + ) { + const normalizedId = this.normalizePipelineId(pipelineId); + const result = await this.platformApiService.proxy( + 'PATCH', + `/pipeline/${normalizedId}`, + user, + body, + ); + + // Sync to Elasticsearch (use original UUID, not normalized) + try { + await this.elasticsearchService.updatePipeline( + user.customer_name, + pipelineId, + { + name: body.name, + description: body.description, + cron: body.cron, + status: body.status, + }, + ); + } catch (error) { + this.logger.error('Failed to sync pipeline update to Elasticsearch', { + pipelineId, + error: error.message, + }); + } + + return result; + } + + @Delete('pipeline/:pipelineId') + @ApiOperation({ summary: 'Delete pipeline by ID' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.DELETE) + async deletePipeline( + @Param('pipelineId') pipelineId: string, + @User() user: RequestUser, + ) { + const normalizedId = this.normalizePipelineId(pipelineId); + const result = await this.platformApiService.proxy( + 'DELETE', + `/pipeline/${normalizedId}`, + user, + ); + + // Sync to Elasticsearch (use original UUID, not normalized) + try { + await this.elasticsearchService.deletePipeline( + user.customer_name, + pipelineId, + ); + } catch (error) { + this.logger.error('Failed to sync pipeline deletion to Elasticsearch', { + pipelineId, + error: error.message, + }); + } + + return result; + } + + @Post('pipeline/execute') + @ApiOperation({ summary: 'Execute a pipeline' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE) + async executePipeline(@Body() body: any, @User() user: RequestUser) { + return this.platformApiService.proxy('POST', '/pipeline/execute', user, body); + } + + @Post('pipeline/pause') + @ApiOperation({ summary: 'Pause a pipeline' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE) + async pausePipeline(@Body() body: any, @User() user: RequestUser) { + return this.platformApiService.proxy('POST', '/pipeline/pause', user, body); + } + + @Post('pipeline/unpause') + @ApiOperation({ summary: 'Unpause a pipeline' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE) + async unpausePipeline(@Body() body: any, @User() user: RequestUser) { + return this.platformApiService.proxy('POST', '/pipeline/unpause', user, body); + } + + @Put('pipeline/:pipelineId/memory') + @ApiOperation({ summary: 'Update pipeline memory configuration' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE) + async updatePipelineMemory( + @Param('pipelineId') pipelineId: string, + @Body() body: any, + @User() user: RequestUser, + ) { + return this.platformApiService.proxy( + 'PUT', + `/pipeline/${pipelineId}/memory`, + user, + body, + ); + } + + // ==================== PIPELINE METADATA ROUTES ==================== + + @Put('pipeline/:pipelineId/metadata') + @ApiOperation({ summary: 'Update pipeline metadata' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE) + async updatePipelineMetadata( + @Param('pipelineId') pipelineId: string, + @Body() body: any, + @User() user: RequestUser, + ) { + return this.platformApiService.proxy( + 'PUT', + `/pipeline/${pipelineId}/metadata`, + user, + body, + ); + } + + @Get('pipelines/metadata') + @ApiOperation({ summary: 'Get all pipelines metadata' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.GET) + async getPipelinesMetadata( + @User() user: RequestUser, + @Query() query: Record, + ) { + return this.platformApiService.proxy( + 'GET', + '/pipelines/metadata', + user, + undefined, + query, + ); + } + + // ==================== PIPELINE RUN ROUTES ==================== + + @Post('pipeline/pipeline_run') + @ApiOperation({ summary: 'Create a pipeline run' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE) + async createPipelineRun(@Body() body: any, @User() user: RequestUser) { + return this.platformApiService.proxy( + 'POST', + '/pipeline/pipeline_run', + user, + body, + ); + } + + @Get('pipeline/:pipelineId/pipeline_run') + @ApiOperation({ summary: 'Get pipeline runs for a pipeline' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.GET) + async getPipelineRuns( + @Param('pipelineId') pipelineId: string, + @User() user: RequestUser, + @Query() query: Record, + ) { + return this.platformApiService.proxy( + 'GET', + `/pipeline/${pipelineId}/pipeline_run`, + user, + undefined, + query, + ); + } + + @Get('pipeline/:pipelineId/pipeline_run/:runId') + @ApiOperation({ summary: 'Get specific pipeline run' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.GET) + async getPipelineRun( + @Param('pipelineId') pipelineId: string, + @Param('runId') runId: string, + @User() user: RequestUser, + ) { + return this.platformApiService.proxy( + 'GET', + `/pipeline/${pipelineId}/pipeline_run/${runId}`, + user, + ); + } + + @Get('pipeline/pipeline_run/:runId/logs') + @ApiOperation({ summary: 'Get pipeline run logs' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.GET) + async getPipelineRunLogs( + @Param('runId') runId: string, + @User() user: RequestUser, + @Query() query: Record, + ) { + return this.platformApiService.proxy( + 'GET', + `/pipeline/pipeline_run/${runId}/logs`, + user, + undefined, + query, + ); + } + + // ==================== JOBS - COLUMN EDITING ROUTES ==================== + + @Put('jobs/:jobId/input') + @ApiOperation({ summary: 'Update job input columns' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE) + async updateJobInput( + @Param('jobId') jobId: string, + @Body() body: any, + @User() user: RequestUser, + ) { + return this.platformApiService.proxy( + 'PUT', + `/jobs/${jobId}/input`, + user, + body, + ); + } + + @Patch('jobs/:jobId/input') + @ApiOperation({ summary: 'Partial update job input columns' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE) + async patchJobInput( + @Param('jobId') jobId: string, + @Body() body: any, + @User() user: RequestUser, + ) { + return this.platformApiService.proxy( + 'PATCH', + `/jobs/${jobId}/input`, + user, + body, + ); + } + + @Put('jobs/:jobId/memory') + @ApiOperation({ summary: 'Update job memory configuration' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE) + async updateJobMemory( + @Param('jobId') jobId: string, + @Body() body: any, + @User() user: RequestUser, + ) { + return this.platformApiService.proxy( + 'PUT', + `/jobs/${jobId}/memory`, + user, + body, + ); + } + + @Post('jobs/:jobId/reset-state') + @ApiOperation({ summary: 'Reset job state' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE) + async resetJobState( + @Param('jobId') jobId: string, + @Body() body: any, + @User() user: RequestUser, + ) { + return this.platformApiService.proxy( + 'POST', + `/jobs/${jobId}/reset-state`, + user, + body, + ); + } + + // ==================== JOBS - JDBC SYNC MODE ROUTES ==================== + + @Get('jobs/jdbc/:jobId') + @ApiOperation({ summary: 'Get JDBC job details' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.GET) + async getJdbcJob(@Param('jobId') jobId: string, @User() user: RequestUser) { + return this.platformApiService.proxy('GET', `/jobs/jdbc/${jobId}`, user); + } + + @Put('jobs/jdbc/:jobId/sync-mode') + @ApiOperation({ summary: 'Update JDBC job sync mode' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE) + async updateJdbcSyncMode( + @Param('jobId') jobId: string, + @Body() body: any, + @User() user: RequestUser, + ) { + return this.platformApiService.proxy( + 'PUT', + `/jobs/jdbc/${jobId}/sync-mode`, + user, + body, + ); + } + + @Get('jobs/jdbc/configs/allowed_datatypes') + @ApiOperation({ summary: 'Get allowed datatypes for JDBC' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.GET) + async getJdbcAllowedDatatypes(@User() user: RequestUser) { + return this.platformApiService.proxy( + 'GET', + '/jobs/jdbc/configs/allowed_datatypes', + user, + ); + } + + // ==================== JOBS - SINGER REPLICATION ROUTES ==================== + + @Get('jobs/singer/:jobId') + @ApiOperation({ summary: 'Get Singer job details' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.GET) + async getSingerJob(@Param('jobId') jobId: string, @User() user: RequestUser) { + return this.platformApiService.proxy('GET', `/jobs/singer/${jobId}`, user); + } + + @Put('jobs/singer/:jobId/sync-mode') + @ApiOperation({ summary: 'Update Singer job sync mode' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE) + async updateSingerSyncMode( + @Param('jobId') jobId: string, + @Body() body: any, + @User() user: RequestUser, + ) { + return this.platformApiService.proxy( + 'PUT', + `/jobs/singer/${jobId}/sync-mode`, + user, + body, + ); + } + + // ==================== JOBS - S3 ROUTES ==================== + + @Get('jobs/s3/:jobId') + @ApiOperation({ summary: 'Get S3 job details' }) + @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.GET) + async getS3Job(@Param('jobId') jobId: string, @User() user: RequestUser) { + return this.platformApiService.proxy('GET', `/jobs/s3/${jobId}`, user); + } + + // ==================== HEALTH ROUTE ==================== + + @Get('health') + @ApiOperation({ summary: 'Platform API health check' }) + @Authenticated() + async healthCheck(@User() user: RequestUser) { + return this.platformApiService.proxy('GET', '/health', user); + } +} diff --git a/src/modules/platform-api/platform-api.module.ts b/src/modules/platform-api/platform-api.module.ts new file mode 100644 index 0000000..427032a --- /dev/null +++ b/src/modules/platform-api/platform-api.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; + +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; + +import { PlatformApiController } from './platform-api.controller'; +import { PlatformApiService } from './platform-api.service'; +import { ElasticsearchModule } from '../../services/elasticsearch'; +import { DynamoDBModule } from '../../services/dynamodb'; + +@Module({ + imports: [ElasticsearchModule, DynamoDBModule], + controllers: [PlatformApiController], + providers: [PlatformApiService, DadosferaLogger], + exports: [PlatformApiService], +}) +export class PlatformApiModule {} diff --git a/src/modules/platform-api/platform-api.service.ts b/src/modules/platform-api/platform-api.service.ts new file mode 100644 index 0000000..97797a3 --- /dev/null +++ b/src/modules/platform-api/platform-api.service.ts @@ -0,0 +1,123 @@ +import { Injectable, Inject, HttpException } from '@nestjs/common'; +import { SignatureV4 } from '@aws-sdk/signature-v4'; +import { Sha256 } from '@aws-crypto/sha256-js'; +import { defaultProvider } from '@aws-sdk/credential-provider-node'; +import axios, { AxiosResponse, Method } from 'axios'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; + +import { RequestUser } from '../../decorators/user.decorator'; +import { PLATFORM_API_CONFIG } from './platform-api.config'; + +@Injectable() +export class PlatformApiService { + private signer: SignatureV4; + private logger: any; + + constructor( + @Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger, + ) { + this.logger = dadosferaLogger.logger; + this.signer = new SignatureV4({ + service: 'execute-api', + region: PLATFORM_API_CONFIG.region, + credentials: defaultProvider(), + sha256: Sha256, + }); + } + + async proxy( + method: string, + path: string, + user: RequestUser, + body?: any, + query?: Record, + ): Promise { + const baseUrl = PLATFORM_API_CONFIG.getUrl(); + const url = new URL(`${baseUrl}${path}`); + + // Add query params + if (query) { + Object.entries(query).forEach(([key, value]) => { + if (value !== undefined && value !== null) { + url.searchParams.set(key, String(value)); + } + }); + } + + const headers: Record = { + host: url.hostname, + 'content-type': 'application/json', + // Forward user context headers + // Note: platform-api expects customer_name in the 'customer_id' header (contract inconsistency) + 'customer_id': user.customer_name || '', + 'customer_name': user.customer_name || '', + 'x-user-id': user.user_id || '', + 'x-username': user.username || '', + 'x-customer-tier': user.customer_tier || '', + 'x-customer-id': user.customer_id || '', + }; + + const requestToSign = { + method: method.toUpperCase(), + protocol: url.protocol, + hostname: url.hostname, + port: url.port ? parseInt(url.port, 10) : undefined, + path: url.pathname + url.search, + headers, + body: body ? JSON.stringify(body) : undefined, + }; + + this.logger.info('Proxying request to platform-api', { + method: method.toUpperCase(), + path, + customer_id: user.customer_id, + user_id: user.user_id, + }); + + try { + // Sign with IAM v4 + const signedRequest = await this.signer.sign(requestToSign); + + const response: AxiosResponse = await axios({ + method: method as Method, + url: url.href, + headers: signedRequest.headers as Record, + data: body, + timeout: PLATFORM_API_CONFIG.timeout, + validateStatus: () => true, // Don't throw on non-2xx + }); + + // Propagate non-2xx responses as HttpExceptions + if (response.status >= 400) { + throw new HttpException(response.data, response.status); + } + + return response.data; + } catch (error) { + this.logger.error('Platform API proxy error', { + error: error.message, + status: error.response?.status, + path, + method: method.toUpperCase(), + }); + + if (error instanceof HttpException) { + throw error; + } + + if (error.response) { + throw new HttpException(error.response.data, error.response.status); + } + + if (error.code === 'ECONNREFUSED') { + throw new HttpException('Platform API service unavailable', 503); + } + + if (error.code === 'ETIMEDOUT' || error.code === 'ECONNABORTED') { + throw new HttpException('Platform API request timeout', 504); + } + + throw new HttpException('Internal server error', 500); + } + } +} diff --git a/src/services/dynamodb/dynamodb.config.ts b/src/services/dynamodb/dynamodb.config.ts new file mode 100644 index 0000000..011ea25 --- /dev/null +++ b/src/services/dynamodb/dynamodb.config.ts @@ -0,0 +1,4 @@ +export const DYNAMODB_CONFIG = { + region: () => process.env.AWS_REGION || 'us-east-1', + inputsTable: () => process.env.INPUTS_DB || 'dadosfera-inputs-prd', +}; diff --git a/src/services/dynamodb/dynamodb.module.ts b/src/services/dynamodb/dynamodb.module.ts new file mode 100644 index 0000000..d50d635 --- /dev/null +++ b/src/services/dynamodb/dynamodb.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { DynamoDBService } from './dynamodb.service'; + +@Module({ + providers: [DynamoDBService, DadosferaLogger], + exports: [DynamoDBService], +}) +export class DynamoDBModule {} diff --git a/src/services/dynamodb/dynamodb.service.ts b/src/services/dynamodb/dynamodb.service.ts new file mode 100644 index 0000000..aead208 --- /dev/null +++ b/src/services/dynamodb/dynamodb.service.ts @@ -0,0 +1,148 @@ +import { Injectable, Inject } from '@nestjs/common'; +import { DynamoDBClient } from '@aws-sdk/client-dynamodb'; +import { + DynamoDBDocumentClient, + GetCommand, + PutCommand, + DeleteCommand, + TranslateConfig, +} from '@aws-sdk/lib-dynamodb'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { v4 as uuid } from 'uuid'; +import { DYNAMODB_CONFIG } from './dynamodb.config'; + +export interface InputDocument { + id: string; + client_id: string; + user_id: string; + created_at: string; + name: string; + description?: string; + plugin: string; + type: string; + tables?: Array<{ + name: string; + type: string; + columns?: string[]; + reference_column?: { name: string; type: string }; + }>; + credentials?: Record; +} + +@Injectable() +export class DynamoDBService { + private documentClient: DynamoDBDocumentClient; + private logger: any; + + constructor( + @Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger, + ) { + this.logger = dadosferaLogger.logger; + + const dynamoConfig = { region: DYNAMODB_CONFIG.region() }; + const marshallOptions: TranslateConfig = { + marshallOptions: { + removeUndefinedValues: true, + }, + }; + + const dynamoDb = new DynamoDBClient(dynamoConfig); + this.documentClient = DynamoDBDocumentClient.from(dynamoDb, marshallOptions); + } + + async createInput( + clientId: string, + userId: string, + data: { + name: string; + description?: string; + plugin: string; + type: string; + tables?: Array<{ + name: string; + type: string; + columns?: string[]; + reference_column?: { name: string; type: string }; + }>; + }, + ): Promise { + const tableName = DYNAMODB_CONFIG.inputsTable(); + const id = uuid(); + const created_at = new Date().toISOString(); + + const item: InputDocument = { + id, + client_id: clientId, + user_id: userId, + created_at, + name: data.name, + description: data.description, + plugin: data.plugin, + type: data.type, + tables: data.tables, + }; + + this.logger.info('DynamoDB: Creating input', { + tableName, + inputId: id, + plugin: data.plugin, + }); + + const putCommand = new PutCommand({ + TableName: tableName, + Item: item, + }); + + try { + await this.documentClient.send(putCommand); + this.logger.info('DynamoDB: Input created successfully', { inputId: id }); + return item; + } catch (error) { + this.logger.error('DynamoDB: Failed to create input', { + tableName, + inputId: id, + region: DYNAMODB_CONFIG.region(), + error: error.message, + errorName: error.name, + }); + throw error; + } + } + + async findInput(clientId: string, inputId: string): Promise { + const tableName = DYNAMODB_CONFIG.inputsTable(); + + const getCommand = new GetCommand({ + TableName: tableName, + Key: { + id: inputId, + client_id: clientId, + }, + }); + + const { Item } = await this.documentClient.send(getCommand); + + return Item as InputDocument | null; + } + + async deleteInput(clientId: string, inputId: string): Promise { + const tableName = DYNAMODB_CONFIG.inputsTable(); + + this.logger.info('DynamoDB: Deleting input', { + tableName, + inputId, + }); + + const deleteCommand = new DeleteCommand({ + TableName: tableName, + Key: { + id: inputId, + client_id: clientId, + }, + }); + + await this.documentClient.send(deleteCommand); + + this.logger.info('DynamoDB: Input deleted successfully', { inputId }); + } +} diff --git a/src/services/dynamodb/index.ts b/src/services/dynamodb/index.ts new file mode 100644 index 0000000..c47362e --- /dev/null +++ b/src/services/dynamodb/index.ts @@ -0,0 +1,3 @@ +export * from './dynamodb.service'; +export * from './dynamodb.module'; +export * from './dynamodb.config'; diff --git a/src/services/elasticsearch/elasticsearch.config.ts b/src/services/elasticsearch/elasticsearch.config.ts new file mode 100644 index 0000000..b20d7fe --- /dev/null +++ b/src/services/elasticsearch/elasticsearch.config.ts @@ -0,0 +1,5 @@ +export const ELASTICSEARCH_CONFIG = { + getUrl: () => process.env.ELASTICSEARCH_URL || 'http://localhost:9200', + getApiKey: () => process.env.ELASTICSEARCH_API_KEY || '', + timeout: 10000, // 10 seconds +}; diff --git a/src/services/elasticsearch/elasticsearch.module.ts b/src/services/elasticsearch/elasticsearch.module.ts new file mode 100644 index 0000000..4d9d816 --- /dev/null +++ b/src/services/elasticsearch/elasticsearch.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { ElasticsearchService } from './elasticsearch.service'; + +@Module({ + providers: [ElasticsearchService, DadosferaLogger], + exports: [ElasticsearchService], +}) +export class ElasticsearchModule {} diff --git a/src/services/elasticsearch/elasticsearch.service.ts b/src/services/elasticsearch/elasticsearch.service.ts new file mode 100644 index 0000000..e03b127 --- /dev/null +++ b/src/services/elasticsearch/elasticsearch.service.ts @@ -0,0 +1,375 @@ +import { Injectable, Inject } from '@nestjs/common'; +import axios, { AxiosInstance, AxiosError } from 'axios'; +import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; +import { ELASTICSEARCH_CONFIG } from './elasticsearch.config'; + +interface MultiLang { + 'en-us': string; + 'pt-br': string; + 'es-es': string; +} + +interface MultiLangArray { + 'en-us': string[]; + 'pt-br': string[]; + 'es-es': string[]; +} + +interface ConnectorInfo { + plugin: string; + name: MultiLang; + image: string; + version: string; + tags: string[]; +} + +interface PipelineDocument { + id: string; + name: MultiLang; + description: MultiLang; + customer_id: string; + user_id: string; + username: string; + status: string; + created_at: string; + updated_at: string; + last_status_updated: string; + tags: string[]; + // Connector metadata + connection_id: string; + connector_name: string; + connector_plugin: string; + connector_version: string; + image_url: string; + // Config + config: { + cron: string; + tables?: string; + }; + properties: string; + type: string; + in_use: number; + keywords: MultiLangArray; +} + +@Injectable() +export class ElasticsearchService { + private client: AxiosInstance; + private logger: any; + + constructor( + @Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger, + ) { + this.logger = dadosferaLogger.logger; + this.client = axios.create({ + baseURL: ELASTICSEARCH_CONFIG.getUrl(), + headers: { + Authorization: `ApiKey ${ELASTICSEARCH_CONFIG.getApiKey()}`, + 'Content-Type': 'application/json', + }, + timeout: ELASTICSEARCH_CONFIG.timeout, + }); + } + + private getIndex(customerName: string): string { + return `${customerName}_pipelines`; + } + + private formatMultiLang(value: string): MultiLang { + return { + 'en-us': value, + 'pt-br': value, + 'es-es': value, + }; + } + + private formatMultiLangArray(value: string[] = []): MultiLangArray { + return { + 'en-us': value, + 'pt-br': value, + 'es-es': value, + }; + } + + buildPipelineDocument( + pipelineId: string, + data: { + name: string; + description?: string; + user_id: string; + username: string; + customer_id: string; + plugin: string; + connection_id: string; + cron?: string; + tables?: string; + properties?: Record; + type?: string; + status?: string; + created_at?: string; + keywords?: string[]; + }, + connector: ConnectorInfo | null, + ): PipelineDocument { + const now = new Date().toISOString(); + + return { + id: pipelineId, + connection_id: data.connection_id, + connector_name: connector?.name?.['en-us'] || '', + connector_plugin: connector?.plugin || data.plugin, + connector_version: connector?.version || '1.0.0', + created_at: data.created_at || now, + updated_at: now, + customer_id: data.customer_id, + description: this.formatMultiLang(data.description || ''), + image_url: connector?.image || '', + keywords: this.formatMultiLangArray(data.keywords), + name: this.formatMultiLang(data.name || ''), + status: data.status || 'CREATED', + user_id: data.user_id, + username: data.username, + config: { + cron: data.cron, + tables: data.tables, + }, + properties: data.properties ? JSON.stringify(data.properties) : '{}', + type: data.type, + in_use: 1, + last_status_updated: now, + tags: connector?.tags || [], + }; + } + + async getConnectorByPlugin(plugin: string): Promise { + this.logger.info('Elasticsearch: Looking up connector', { plugin }); + + try { + const response = await this.client.post('/connectors/_search', { + query: { + term: { plugin: plugin }, + }, + size: 1, + }); + + const hits = response.data.hits?.hits || []; + if (hits.length === 0) { + this.logger.warn('Elasticsearch: Connector not found', { plugin }); + return null; + } + + const source = hits[0]._source; + return { + plugin: source.plugin, + name: source.name, + image: source.image, + version: source.version, + tags: source.tags || [], + }; + } catch (error) { + this.handleError('getConnectorByPlugin', error, { plugin }); + return null; + } + } + + async createPipeline( + customerName: string, + pipelineId: string, + data: { + name: string; + description?: string; + user_id: string; + username: string; + customer_id: string; + status?: string; + created_at?: string; + plugin: string; + connection_id: string; + cron?: string; + tables?: string; + properties?: Record; + type?: string; + }, + connector: ConnectorInfo | null, + ): Promise { + const index = this.getIndex(customerName); + const document = this.buildPipelineDocument(pipelineId, data, connector); + + this.logger.info('Elasticsearch: Creating pipeline', { + index, + pipelineId, + plugin: document.connector_plugin, + }); + + try { + const response = await this.client.post( + `${index}/_doc/${pipelineId}`, + document, + { params: { refresh: 'wait_for' } }, + ); + + this.logger.info('Elasticsearch: Pipeline created successfully', { + pipelineId, + result: response.data.result, + }); + + return response.data; + } catch (error) { + this.handleError('createPipeline', error, { pipelineId, index }); + throw error; + } + } + + async updatePipeline( + customerName: string, + pipelineId: string, + changes: { + name?: string; + description?: string; + cron?: string; + status?: string; + tags?: string[]; + }, + ): Promise { + const index = this.getIndex(customerName); + const now = new Date().toISOString(); + + const formattedChanges: Record = { + updated_at: now, + }; + + if (changes.name !== undefined) { + formattedChanges.name = this.formatMultiLang(changes.name); + } + + if (changes.description !== undefined) { + formattedChanges.description = this.formatMultiLang(changes.description); + } + + if (changes.cron !== undefined) { + // Update cron inside config object using ES script + formattedChanges.config = { cron: changes.cron }; + } + + if (changes.status !== undefined) { + formattedChanges.status = changes.status; + formattedChanges.last_status_updated = now; + } + + if (changes.tags !== undefined) { + formattedChanges.tags = changes.tags; + } + + this.logger.info('Elasticsearch: Updating pipeline', { + index, + pipelineId, + fields: Object.keys(formattedChanges), + }); + + try { + // Use script for partial config update to preserve other config fields + const updateBody = changes.cron !== undefined + ? { + script: { + source: ` + ctx._source.updated_at = params.updated_at; + if (params.name != null) ctx._source.name = params.name; + if (params.description != null) ctx._source.description = params.description; + if (params.cron != null) ctx._source.config.cron = params.cron; + if (params.status != null) { + ctx._source.status = params.status; + ctx._source.last_status_updated = params.last_status_updated; + } + if (params.tags != null) ctx._source.tags = params.tags; + `, + params: { + updated_at: now, + name: changes.name !== undefined ? this.formatMultiLang(changes.name) : null, + description: changes.description !== undefined ? this.formatMultiLang(changes.description) : null, + cron: changes.cron || null, + status: changes.status || null, + last_status_updated: changes.status !== undefined ? now : null, + tags: changes.tags || null, + }, + }, + } + : { doc: formattedChanges }; + + const response = await this.client.post( + `${index}/_update/${pipelineId}`, + updateBody, + { params: { refresh: 'wait_for' } }, + ); + + this.logger.info('Elasticsearch: Pipeline updated successfully', { + pipelineId, + result: response.data.result, + }); + + return response.data; + } catch (error) { + this.handleError('updatePipeline', error, { pipelineId, index }); + throw error; + } + } + + async deletePipeline( + customerName: string, + pipelineId: string, + ): Promise { + const index = this.getIndex(customerName); + + this.logger.info('Elasticsearch: Deleting pipeline', { + index, + pipelineId, + }); + + try { + const response = await this.client.delete( + `${index}/_doc/${pipelineId}`, + { params: { refresh: 'wait_for' } }, + ); + + this.logger.info('Elasticsearch: Pipeline deleted successfully', { + pipelineId, + result: response.data.result, + }); + + return response.data; + } catch (error) { + // If document not found, log warning but don't throw + if (error instanceof AxiosError && error.response?.status === 404) { + this.logger.warn('Elasticsearch: Pipeline not found for deletion', { + pipelineId, + index, + }); + return { result: 'not_found' }; + } + + this.handleError('deletePipeline', error, { pipelineId, index }); + throw error; + } + } + + private handleError( + operation: string, + error: any, + context: Record, + ): void { + if (error instanceof AxiosError) { + this.logger.error(`Elasticsearch: ${operation} failed`, { + ...context, + status: error.response?.status, + statusText: error.response?.statusText, + errorData: error.response?.data, + message: error.message, + }); + } else { + this.logger.error(`Elasticsearch: ${operation} failed`, { + ...context, + message: error.message, + stack: error.stack, + }); + } + } +} diff --git a/src/services/elasticsearch/index.ts b/src/services/elasticsearch/index.ts new file mode 100644 index 0000000..852bfc9 --- /dev/null +++ b/src/services/elasticsearch/index.ts @@ -0,0 +1,3 @@ +export * from './elasticsearch.module'; +export * from './elasticsearch.service'; +export * from './elasticsearch.config'; From 2f140d213a8bcf68f4181ca16304fdd31c014845 Mon Sep 17 00:00:00 2001 From: Rafael Moraes Date: Wed, 10 Dec 2025 16:51:17 +0000 Subject: [PATCH 13/41] catalogo: alteracao nas pastas e acrescimo de filtros --- docsfera.json | 20 ++++++++ src/modules/catalog/catalog.service.ts | 54 ++++++++++++++++++++-- src/modules/catalog/dtos/index.ts | 6 +++ src/modules/catalog/share/share.service.ts | 4 +- 4 files changed, 79 insertions(+), 5 deletions(-) diff --git a/docsfera.json b/docsfera.json index ab3a2c1..9cb09aa 100644 --- a/docsfera.json +++ b/docsfera.json @@ -3877,6 +3877,16 @@ ], "type": "string" } + }, + { + "name": "owner", + "required": false, + "in": "query", + "description": "ID do usuário owner para filtrar data assets", + "example": "user-id-1,user-id-2", + "schema": { + "type": "string" + } } ], "responses": { @@ -4006,6 +4016,16 @@ ], "type": "string" } + }, + { + "name": "owner", + "required": false, + "in": "query", + "description": "ID do usuário owner para filtrar data assets", + "example": "user-id-1,user-id-2", + "schema": { + "type": "string" + } } ], "responses": { diff --git a/src/modules/catalog/catalog.service.ts b/src/modules/catalog/catalog.service.ts index 1848dcd..4fd9619 100644 --- a/src/modules/catalog/catalog.service.ts +++ b/src/modules/catalog/catalog.service.ts @@ -209,10 +209,58 @@ class CatalogService implements OnModuleInit { metadata: Metadata, customer_id: string, ) { - this.logger.info('CatalogService - searchDataAssets'); + this.logger.info('CatalogService - searchDataAssets', { query }); const { search, page, size, sort_by, order, ...filters } = query; + this.logger.debug('Extracted filters:', { filters }); + + if (filters.owner) { + const { users: customer_users } = + await this.userService.findAllUsersByCustomerId(customer_id); + + this.logger.info('Available users in database count:', { count: customer_users.length }); + this.logger.info('First 5 users:', { + users: customer_users.slice(0, 5).map(u => ({ id: u.id, email: u.email, name: u.name })) + }); + + const ownerValues = Array.isArray(filters.owner) + ? filters.owner + : (typeof filters.owner === 'string' && filters.owner.includes(',') + ? filters.owner.split(',').map((o: string) => o.trim()) + : [filters.owner]); + + this.logger.info('Owner values to convert:', { ownerValues, ownerFiltersOriginal: filters.owner }); + + const ownerIds = ownerValues + .map((ownerValue: string) => { + const normalizedOwner = ownerValue.replace(/\s/g, '+'); + const user = customer_users.find(u => { + const isIdMatch = u.id === ownerValue; + const isEmailMatch = u.email === ownerValue || u.email === normalizedOwner; + const isNameMatch = u.name === ownerValue || u.name === normalizedOwner; + this.logger.info('Comparing:', { + userId: u.id, + userEmail: u.email, + userName: u.name, + filterValue: ownerValue, + normalizedFilter: normalizedOwner, + idMatch: isIdMatch, + emailMatch: isEmailMatch, + nameMatch: isNameMatch, + }); + return isIdMatch || isEmailMatch || isNameMatch; + }); + this.logger.info('Looking for owner result:', { ownerValue, found: !!user, userId: user?.id }); + return user?.id || ownerValue; + }) + .filter((id: string) => id); + + if (ownerIds.length > 0) { + filters.owner = ownerIds; + } + } + const { data_assets, total } = await lastValueFrom( this.catalogReadService.GetAllDataAssets( { @@ -404,7 +452,7 @@ class CatalogService implements OnModuleInit { return data_assets.map((data_asset) => { const owner = customer_users.find( (u) => u.id === data_asset.owner, - )?.username; + )?.email; const roles = []; const users = []; @@ -417,7 +465,7 @@ class CatalogService implements OnModuleInit { const data_asset_users = data_asset?.users || [] for (const user_id of data_asset_users) { const user = customer_users.find((r) => r.id === user_id); - if (user) users.push({ id: user.id, username: user.username }); + if (user) users.push({ id: user.id, email: user.email }); } return { ...data_asset, diff --git a/src/modules/catalog/dtos/index.ts b/src/modules/catalog/dtos/index.ts index ef12e78..59de404 100644 --- a/src/modules/catalog/dtos/index.ts +++ b/src/modules/catalog/dtos/index.ts @@ -147,6 +147,12 @@ export class ICatalogAllRequest { description: 'Tipo de ordenação - `asc`: crescente; `desc`: decrescente ', }) order?: OrderEnum; + + @ApiPropertyOptional({ + description: 'ID do usuário owner para filtrar data assets', + example: 'user-id-1,user-id-2', + }) + owner?: string; } export class ICatalogAllResponse { diff --git a/src/modules/catalog/share/share.service.ts b/src/modules/catalog/share/share.service.ts index 3e088a6..48f2d5c 100644 --- a/src/modules/catalog/share/share.service.ts +++ b/src/modules/catalog/share/share.service.ts @@ -180,7 +180,7 @@ export class ShareService implements OnModuleInit { return data_assets.map((data_asset) => { const owner = customer_users.find( (u) => u.id === data_asset.owner, - )?.username; + )?.email; const roles = []; const users = []; @@ -190,7 +190,7 @@ export class ShareService implements OnModuleInit { } for (const user_id of data_asset.users) { const user = customer_users.find((r) => r.id === user_id); - if (user) users.push({ id: user.id, username: user.username }); + if (user) users.push({ id: user.id, email: user.email }); } return { ...data_asset, From f7efb757bf31a85880f909af7d363ddb79815180 Mon Sep 17 00:00:00 2001 From: Rafael Date: Wed, 10 Dec 2025 13:56:49 -0300 Subject: [PATCH 14/41] FEAT: add schedule limit validation and improve ES update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add cron-validation utility with Airflow presets support (@once, @daily, etc.) - Validate cron expressions against customer scheduleLimit from DUC - Fetch scheduleLimit via gRPC to ensure up-to-date configuration - Improve ES updatePipeline to fetch current doc and merge changes - Only update fields explicitly provided in PATCH request body - Preserve config.tables when updating config.cron 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../platform-api/platform-api.controller.ts | 47 ++++++++-- .../platform-api/platform-api.module.ts | 3 +- .../elasticsearch/elasticsearch.service.ts | 92 ++++++++----------- src/utils/cron-validation.ts | 90 ++++++++++++++++++ 4 files changed, 169 insertions(+), 63 deletions(-) create mode 100644 src/utils/cron-validation.ts diff --git a/src/modules/platform-api/platform-api.controller.ts b/src/modules/platform-api/platform-api.controller.ts index 133b239..d9006ef 100644 --- a/src/modules/platform-api/platform-api.controller.ts +++ b/src/modules/platform-api/platform-api.controller.ts @@ -23,6 +23,8 @@ import { PlatformApiService } from './platform-api.service'; import { PERMISSIONS_GROUPS } from '../../authentication/permissions.enum'; import { ElasticsearchService } from '../../services/elasticsearch'; import { DynamoDBService } from '../../services/dynamodb'; +import { CustomersService } from '../customers/customers.service'; +import { validateCronAgainstScheduleLimit } from '../../utils/cron-validation'; @ApiTags('Platform API') @Controller('platform') @@ -33,6 +35,7 @@ export class PlatformApiController { private readonly platformApiService: PlatformApiService, private readonly elasticsearchService: ElasticsearchService, private readonly dynamoDBService: DynamoDBService, + private readonly customersService: CustomersService, @Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger, ) { this.logger = dadosferaLogger.logger; @@ -59,6 +62,22 @@ export class PlatformApiController { } } + /** + * Validate cron expression against customer's schedule limit. + * Fetches current scheduleLimit from DUC to ensure up-to-date configuration. + */ + private async validateScheduleLimit(cron: string, customerId: string): Promise { + if (!cron) return; + + const { customer } = await this.customersService.getCustomer(customerId); + const scheduleLimit = customer?.scheduleLimit || 'day'; + + const result = validateCronAgainstScheduleLimit(cron, scheduleLimit); + if (!result.valid) { + throw new BadRequestException(result.message); + } + } + /** * Map connector type to DynamoDB type. * jdbc -> 'database', singer -> 'application', s3 -> 'file' @@ -201,6 +220,9 @@ export class PlatformApiController { const firstJob = body.jobs[0]?.input; this.validateConnector(firstJob?.connector); + // Validate cron against customer's schedule limit + await this.validateScheduleLimit(body.cron, user.customer_id); + // Inject customer_id (actually customer_name) and normalized ID into body for Platform-API // Note: Platform-API was created before customer_id existed, so it expects customer_name in the customer_id field const enrichedBody = { @@ -322,6 +344,11 @@ export class PlatformApiController { @Body() body: any, @User() user: RequestUser, ) { + // Validate cron against customer's schedule limit if cron is being updated + if (body.cron) { + await this.validateScheduleLimit(body.cron, user.customer_id); + } + const normalizedId = this.normalizePipelineId(pipelineId); const result = await this.platformApiService.proxy( 'PATCH', @@ -331,16 +358,24 @@ export class PlatformApiController { ); // Sync to Elasticsearch (use original UUID, not normalized) + // Only pass fields that are explicitly provided in the request body try { + const esChanges: { + name?: string; + description?: string; + cron?: string; + status?: string; + } = {}; + + if ('name' in body) esChanges.name = body.name; + if ('description' in body) esChanges.description = body.description; + if ('cron' in body) esChanges.cron = body.cron; + if ('status' in body) esChanges.status = body.status; + await this.elasticsearchService.updatePipeline( user.customer_name, pipelineId, - { - name: body.name, - description: body.description, - cron: body.cron, - status: body.status, - }, + esChanges, ); } catch (error) { this.logger.error('Failed to sync pipeline update to Elasticsearch', { diff --git a/src/modules/platform-api/platform-api.module.ts b/src/modules/platform-api/platform-api.module.ts index 427032a..c2b31e4 100644 --- a/src/modules/platform-api/platform-api.module.ts +++ b/src/modules/platform-api/platform-api.module.ts @@ -6,9 +6,10 @@ import { PlatformApiController } from './platform-api.controller'; import { PlatformApiService } from './platform-api.service'; import { ElasticsearchModule } from '../../services/elasticsearch'; import { DynamoDBModule } from '../../services/dynamodb'; +import { CustomersModule } from '../customers/customers.module'; @Module({ - imports: [ElasticsearchModule, DynamoDBModule], + imports: [ElasticsearchModule, DynamoDBModule, CustomersModule], controllers: [PlatformApiController], providers: [PlatformApiService, DadosferaLogger], exports: [PlatformApiService], diff --git a/src/services/elasticsearch/elasticsearch.service.ts b/src/services/elasticsearch/elasticsearch.service.ts index e03b127..281b5d6 100644 --- a/src/services/elasticsearch/elasticsearch.service.ts +++ b/src/services/elasticsearch/elasticsearch.service.ts @@ -234,70 +234,50 @@ export class ElasticsearchService { const index = this.getIndex(customerName); const now = new Date().toISOString(); - const formattedChanges: Record = { - updated_at: now, - }; - - if (changes.name !== undefined) { - formattedChanges.name = this.formatMultiLang(changes.name); - } - - if (changes.description !== undefined) { - formattedChanges.description = this.formatMultiLang(changes.description); - } - - if (changes.cron !== undefined) { - // Update cron inside config object using ES script - formattedChanges.config = { cron: changes.cron }; - } - - if (changes.status !== undefined) { - formattedChanges.status = changes.status; - formattedChanges.last_status_updated = now; - } - - if (changes.tags !== undefined) { - formattedChanges.tags = changes.tags; - } - this.logger.info('Elasticsearch: Updating pipeline', { index, pipelineId, - fields: Object.keys(formattedChanges), + fields: Object.keys(changes), }); try { - // Use script for partial config update to preserve other config fields - const updateBody = changes.cron !== undefined - ? { - script: { - source: ` - ctx._source.updated_at = params.updated_at; - if (params.name != null) ctx._source.name = params.name; - if (params.description != null) ctx._source.description = params.description; - if (params.cron != null) ctx._source.config.cron = params.cron; - if (params.status != null) { - ctx._source.status = params.status; - ctx._source.last_status_updated = params.last_status_updated; - } - if (params.tags != null) ctx._source.tags = params.tags; - `, - params: { - updated_at: now, - name: changes.name !== undefined ? this.formatMultiLang(changes.name) : null, - description: changes.description !== undefined ? this.formatMultiLang(changes.description) : null, - cron: changes.cron || null, - status: changes.status || null, - last_status_updated: changes.status !== undefined ? now : null, - tags: changes.tags || null, - }, - }, - } - : { doc: formattedChanges }; + // Fetch current document + const currentDoc = await this.client.get(`${index}/_doc/${pipelineId}`); + const current = currentDoc.data._source; + + // Build updated document, preserving existing values + const updated: Record = { + ...current, + updated_at: now, + }; + + if ('name' in changes) { + updated.name = this.formatMultiLang(changes.name); + } + + if ('description' in changes) { + updated.description = this.formatMultiLang(changes.description); + } + + if ('cron' in changes) { + updated.config = { + ...current.config, + cron: changes.cron, + }; + } + + if ('status' in changes) { + updated.status = changes.status; + updated.last_status_updated = now; + } + + if ('tags' in changes) { + updated.tags = changes.tags; + } const response = await this.client.post( - `${index}/_update/${pipelineId}`, - updateBody, + `${index}/_doc/${pipelineId}`, + updated, { params: { refresh: 'wait_for' } }, ); diff --git a/src/utils/cron-validation.ts b/src/utils/cron-validation.ts new file mode 100644 index 0000000..b46d0f6 --- /dev/null +++ b/src/utils/cron-validation.ts @@ -0,0 +1,90 @@ +import CronParser from 'cron-parser'; + +export enum ScheduleLimits { + MINUTE = 'minute', + HOUR = 'hour', + DAY = 'day', + UNLIMITED = 'unlimited', +} + +const SECONDS_IN_MINUTE = 60; +const SECONDS_IN_HOUR = 3600; +const SECONDS_IN_DAY = 86400; + +/** + * Airflow preset schedules mapped to cron expressions. + * @once is special - it means run only once (no recurring schedule). + */ +const AIRFLOW_PRESETS: Record = { + '@once': null, // No recurring schedule - always valid + '@hourly': '0 * * * *', // Every hour + '@daily': '0 0 * * *', // Every day at midnight + '@weekly': '0 0 * * 0', // Every week on Sunday + '@monthly': '0 0 1 * *', // First day of every month + '@yearly': '0 0 1 1 *', // First day of every year + '@annually': '0 0 1 1 *', // Same as @yearly +}; + +/** + * Convert Airflow preset to cron expression. + * Returns null for @once (no recurring schedule). + * Returns original string if not an Airflow preset. + */ +export function convertAirflowPresetToCron(schedule: string): string | null { + const preset = AIRFLOW_PRESETS[schedule.toLowerCase()]; + if (preset !== undefined) { + return preset; + } + return schedule; +} + +export function getMinimumIntervalSeconds(scheduleLimit: string): number { + switch (scheduleLimit) { + case ScheduleLimits.MINUTE: + return SECONDS_IN_MINUTE; + case ScheduleLimits.HOUR: + return SECONDS_IN_HOUR; + case ScheduleLimits.DAY: + return SECONDS_IN_DAY; + case ScheduleLimits.UNLIMITED: + default: + return 0; + } +} + +export function getCronIntervalSeconds(cron: string): number { + const interval = CronParser.parseExpression(cron); + const nextDate = interval.next().toDate(); + const afterNextDate = interval.next().toDate(); + return Math.floor((afterNextDate.getTime() - nextDate.getTime()) / 1000); +} + +export function validateCronAgainstScheduleLimit( + cron: string, + scheduleLimit: string, +): { valid: boolean; message?: string } { + if (!cron) return { valid: true }; + + // Convert Airflow presets to cron expressions + const cronExpression = convertAirflowPresetToCron(cron); + + // @once returns null - no recurring schedule, always valid + if (cronExpression === null) { + return { valid: true }; + } + + try { + const cronInterval = getCronIntervalSeconds(cronExpression); + const minInterval = getMinimumIntervalSeconds(scheduleLimit); + + if (cronInterval < minInterval) { + return { + valid: false, + message: `Schedule interval (${cronInterval}s) is below customer limit (${scheduleLimit}: ${minInterval}s minimum)`, + }; + } + return { valid: true }; + } catch (error) { + return { valid: false, message: `Invalid cron expression: ${error.message}` }; + } +} From 00304262bae0fe3b0429cd7478bc1b6107d67ae3 Mon Sep 17 00:00:00 2001 From: Rafael Moraes Date: Thu, 11 Dec 2025 13:52:58 +0000 Subject: [PATCH 15/41] FIX: wget change in helmfile --- .github/workflows/validate-k8s.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate-k8s.yml b/.github/workflows/validate-k8s.yml index 4d23e66..7f4255c 100644 --- a/.github/workflows/validate-k8s.yml +++ b/.github/workflows/validate-k8s.yml @@ -66,7 +66,7 @@ jobs: - name: Install Helmfile run: | - wget https://github.com/helmfile/helmfile/releases/download/v0.148.0/helmfile_0.148.0_linux_amd64.tar.gz + curl -fsSLO https://github.com/helmfile/helmfile/releases/download/v0.148.0/helmfile_0.148.0_linux_amd64.tar.gz tar -xzf helmfile_0.148.0_linux_amd64.tar.gz sudo mv helmfile /usr/local/bin/ helmfile --version From 5c29e0745046141478ba15ec4fee4beb99e50144 Mon Sep 17 00:00:00 2001 From: Rafael Moraes Date: Thu, 11 Dec 2025 14:07:04 +0000 Subject: [PATCH 16/41] FIX: helmfile --- .github/workflows/validate-k8s.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/validate-k8s.yml b/.github/workflows/validate-k8s.yml index 7f4255c..89dba0b 100644 --- a/.github/workflows/validate-k8s.yml +++ b/.github/workflows/validate-k8s.yml @@ -71,8 +71,10 @@ jobs: sudo mv helmfile /usr/local/bin/ helmfile --version - - name: Install Helm Diff Plugin - run: helm plugin install https://github.com/databus23/helm-diff || true + - name: Fix Helm Diff Plugin + run: | + helm plugin rm diff || true + helm plugin install https://github.com/databus23/helm-diff - name: Authenticate with OKE cluster env: From 6919a2a8d0a8b77c58513a85e73e8134b04d157c Mon Sep 17 00:00:00 2001 From: Rafael Moraes Date: Thu, 11 Dec 2025 14:14:02 +0000 Subject: [PATCH 17/41] FIX: helmfile diff plugin --- .github/workflows/validate-k8s.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/validate-k8s.yml b/.github/workflows/validate-k8s.yml index 89dba0b..3ad7408 100644 --- a/.github/workflows/validate-k8s.yml +++ b/.github/workflows/validate-k8s.yml @@ -72,9 +72,16 @@ jobs: helmfile --version - name: Fix Helm Diff Plugin + env: + HELM_PLUGINS: /home/runner/.local/share/helm/plugins run: | - helm plugin rm diff || true + rm -rf "$HELM_PLUGINS/diff" "$HELM_PLUGINS/helm-diff" || true + + mkdir -p "$HELM_PLUGINS" + ls -R "$HELM_PLUGINS" || true + helm plugin install https://github.com/databus23/helm-diff + helm plugin list - name: Authenticate with OKE cluster env: From dd699614aeb3d1b067e0817a4c02e5f8b2759d63 Mon Sep 17 00:00:00 2001 From: Rafael Moraes Date: Thu, 11 Dec 2025 16:47:28 +0000 Subject: [PATCH 18/41] FIX: Maestro helm fix --- .github/workflows/validate-k8s.yml | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/.github/workflows/validate-k8s.yml b/.github/workflows/validate-k8s.yml index 3ad7408..8a67a9c 100644 --- a/.github/workflows/validate-k8s.yml +++ b/.github/workflows/validate-k8s.yml @@ -71,17 +71,11 @@ jobs: sudo mv helmfile /usr/local/bin/ helmfile --version - - name: Fix Helm Diff Plugin - env: - HELM_PLUGINS: /home/runner/.local/share/helm/plugins + - name: Debug Helm env run: | - rm -rf "$HELM_PLUGINS/diff" "$HELM_PLUGINS/helm-diff" || true - - mkdir -p "$HELM_PLUGINS" - ls -R "$HELM_PLUGINS" || true - - helm plugin install https://github.com/databus23/helm-diff - helm plugin list + helm env + echo "HOME=$HOME" + ls -R $HOME/.local/share/helm || true - name: Authenticate with OKE cluster env: From df3f2489f986823fe2dcd1b80b9f80e22470d612 Mon Sep 17 00:00:00 2001 From: Rafael Moraes Date: Thu, 11 Dec 2025 22:10:32 +0000 Subject: [PATCH 19/41] FIX: Calendar Fix --- docsfera.json | 40 +++++++++++ src/modules/catalog/catalog.service.ts | 96 +++++++++++++++++++++++++- src/modules/catalog/dtos/index.ts | 12 ++++ 3 files changed, 147 insertions(+), 1 deletion(-) diff --git a/docsfera.json b/docsfera.json index 9cb09aa..d27ab0a 100644 --- a/docsfera.json +++ b/docsfera.json @@ -3887,6 +3887,26 @@ "schema": { "type": "string" } + }, + { + "name": "catalog_date_from", + "required": false, + "in": "query", + "description": "Data inicial para filtro de catálogo (formato: YYYY-MM-DD)", + "example": "2025-01-01", + "schema": { + "type": "string" + } + }, + { + "name": "catalog_date_to", + "required": false, + "in": "query", + "description": "Data final para filtro de catálogo (formato: YYYY-MM-DD)", + "example": "2025-12-31", + "schema": { + "type": "string" + } } ], "responses": { @@ -4026,6 +4046,26 @@ "schema": { "type": "string" } + }, + { + "name": "catalog_date_from", + "required": false, + "in": "query", + "description": "Data inicial para filtro de catálogo (formato: YYYY-MM-DD)", + "example": "2025-01-01", + "schema": { + "type": "string" + } + }, + { + "name": "catalog_date_to", + "required": false, + "in": "query", + "description": "Data final para filtro de catálogo (formato: YYYY-MM-DD)", + "example": "2025-12-31", + "schema": { + "type": "string" + } } ], "responses": { diff --git a/src/modules/catalog/catalog.service.ts b/src/modules/catalog/catalog.service.ts index 4fd9619..8d87d9c 100644 --- a/src/modules/catalog/catalog.service.ts +++ b/src/modules/catalog/catalog.service.ts @@ -39,6 +39,7 @@ import { import { TypeParser } from 'src/utils/FileParser/parser-types'; import { ParserBuilder } from 'src/utils/FileParser/parser.builder'; + class CatalogService implements OnModuleInit { catalogReadService: ReadService.CatalogReadServices; catalogWriteService: WriteService.CatalogWriteServices; @@ -55,6 +56,7 @@ class CatalogService implements OnModuleInit { this.logger = dadosferaLogger.logger; } + onModuleInit() { this.catalogReadService = this.grpcClient.getService( @@ -70,20 +72,24 @@ class CatalogService implements OnModuleInit { ); } + _getNimbusUrl(body) { this.logger.debug(`Body: ${JSON.stringify(body)}`); const customer = body.info.customer.toLowerCase(); + if (process.env.ENV === 'prd') { return `https://nimbus-${customer}.dadosfera.ai`; } + return `https://nimbus-${customer}.${process.env.ENV.replace( 'local', 'stg', )}.dadosfera.ai`; } + async getPiiReporter(metadata: Metadata, type: TypeParser) { this.logger.info('getPiiReporter: ' + type) try { @@ -94,8 +100,10 @@ class CatalogService implements OnModuleInit { ) this.logger.info("Finish grpc call") + const parser = ParserBuilder.build(type); + this.logger.info('parser file to: ' + type) const file = await parser.parse(data) this.logger.info('finish parser') @@ -105,9 +113,11 @@ class CatalogService implements OnModuleInit { 'pdf': 'application/pdf' } + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const filename = `relatorio-pii-${timestamp}.${type}`; + return { file, filename: filename, @@ -118,20 +128,25 @@ class CatalogService implements OnModuleInit { throw error; } + } + async createDataAsset(data: Messages.CreateDataAssetRequest, metadata) { this.logger.info('CatalogService - Manage Data assets permissions'); if (!data.embed) data.embed = undefined; + return lastValueFrom( this.catalogWriteService.CreateDataAsset(data, metadata), ); } + async managePermissions(data: Messages.ManagePermissionRequest, metadata) { this.logger.info('CatalogService - Manage Data assets permissions'); + return lastValueFrom( this.catalogWriteService.ManagePermission(data, metadata), ).catch((err) => { @@ -142,9 +157,11 @@ class CatalogService implements OnModuleInit { }); } + async revokePermissions(data: Messages.RevokePermissionRequest, metadata) { this.logger.info('CatalogService - Manage Data assets permissions'); + return lastValueFrom( this.catalogWriteService.RevokePermission(data, metadata), ).catch((err) => { @@ -155,9 +172,11 @@ class CatalogService implements OnModuleInit { }); } + async commentOnDataAsset(data: Messages.MakeACommentRequest, metadata) { this.logger.info('CatalogService - Manage Data assets permissions'); + return lastValueFrom( this.catalogWriteService.MakeAComment(data, metadata), ).catch((err) => { @@ -168,9 +187,11 @@ class CatalogService implements OnModuleInit { }); } + async deleteComment(data: Messages.UpdateACommentRequest, metadata) { this.logger.info('CatalogService - Manage Data assets permissions'); + return lastValueFrom( this.catalogWriteService.UpdateAComment(data, metadata), ).catch((err) => { @@ -181,9 +202,11 @@ class CatalogService implements OnModuleInit { }); } + async deleteDataAsset(data: Messages.DeleteDataAssetRequest, metadata) { this.logger.info('CatalogService - Manage Data assets permissions'); + return lastValueFrom( this.catalogWriteService.DeleteDataAsset(data, metadata), ).catch((err) => { @@ -194,16 +217,20 @@ class CatalogService implements OnModuleInit { }); } + async getUserRolesIds(userId: string) { const result = await this.userService.findOneById(userId).catch(() => null); + if (result) { return result.user.roles.map((role) => role.id); } + return []; } + async searchDataAssets( query: Record, metadata: Metadata, @@ -211,10 +238,19 @@ class CatalogService implements OnModuleInit { ) { this.logger.info('CatalogService - searchDataAssets', { query }); + const { search, page, size, sort_by, order, ...filters } = query; + this.logger.debug('Extracted filters:', { filters }); + + console.log('MAESTRO VAI CHAMAR PI-FACTORY COM:', { + search, page, size, sort_by, order, + filters: filters + }); + + if (filters.owner) { const { users: customer_users } = await this.userService.findAllUsersByCustomerId(customer_id); @@ -224,6 +260,7 @@ class CatalogService implements OnModuleInit { users: customer_users.slice(0, 5).map(u => ({ id: u.id, email: u.email, name: u.name })) }); + const ownerValues = Array.isArray(filters.owner) ? filters.owner : (typeof filters.owner === 'string' && filters.owner.includes(',') @@ -232,6 +269,7 @@ class CatalogService implements OnModuleInit { this.logger.info('Owner values to convert:', { ownerValues, ownerFiltersOriginal: filters.owner }); + const ownerIds = ownerValues .map((ownerValue: string) => { const normalizedOwner = ownerValue.replace(/\s/g, '+'); @@ -256,11 +294,13 @@ class CatalogService implements OnModuleInit { }) .filter((id: string) => id); + if (ownerIds.length > 0) { filters.owner = ownerIds; } } + const { data_assets, total } = await lastValueFrom( this.catalogReadService.GetAllDataAssets( { @@ -275,16 +315,25 @@ class CatalogService implements OnModuleInit { ), ); + + + console.log('MAESTRO RECEBEU RESPOSTA DO PI-FACTORY'); + + + const result = JSON.parse(data_assets); + const response = await this.getAssetsUsersAndRoles( result.data_assets, customer_id, ); + return { data_assets: response, total }; } + async downloadAssets( query: Record, metadata: Metadata, @@ -292,6 +341,7 @@ class CatalogService implements OnModuleInit { ) { const data = await this.searchDataAssets(query, metadata, customer_id); + const formatData = data.data_assets.map(asset => ({ id: asset.id, display_name: asset.display_name, @@ -300,19 +350,24 @@ class CatalogService implements OnModuleInit { tags: '[' + asset.tags.join(', ') + ']' })) + const parser = ParserBuilder.build('csv'); + const file = await parser.parse(formatData); + const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const filename = `dadosfera_assets_${timestamp}.csv`; + return { file, filename } } + async getOneDataAsset(data: { id: string; customer_id: string; @@ -333,9 +388,11 @@ class CatalogService implements OnModuleInit { }; asset = await this.getAssetsUsersAndRoles([asset], customer_id); + return { data_asset: asset[0] }; } + async getOneDataAssetByPipelineAndObject(data: { customer_id: string; pipeline: string; @@ -357,9 +414,11 @@ class CatalogService implements OnModuleInit { }; asset = await this.getAssetsUsersAndRoles([asset], customer_id); + return { data_asset: asset[0] }; } + async updateOneDataAsset(data: { data_asset_id: string; customer_id: string; @@ -368,6 +427,7 @@ class CatalogService implements OnModuleInit { }) { const { body, customer_id, data_asset_id, metadata } = data; + const { data_asset } = await lastValueFrom( this.catalogWriteService.UpdateDataAsset( { id: data_asset_id, changes: JSON.stringify(body) }, @@ -382,9 +442,11 @@ class CatalogService implements OnModuleInit { }; asset = await this.getAssetsUsersAndRoles([asset], customer_id); + return { data_asset: asset[0] }; } + async getDataDocs(id: string, metadata: Metadata) { const { documentation } = await lastValueFrom( this.catalogReadService.GetDatasetDoc({ id, type: undefined }, metadata), @@ -394,6 +456,7 @@ class CatalogService implements OnModuleInit { return docs; } + async getDatasetPreview(id: string, metadata: Metadata) { const { preview } = await lastValueFrom( this.catalogReadService.GetDatasetPreview( @@ -405,6 +468,7 @@ class CatalogService implements OnModuleInit { return result; } + async getDatasetColumnsMetadata(id: string, metadata: Metadata) { const { columns_metadata } = await lastValueFrom( this.catalogReadService.GetDatasetColumnsMetadata( @@ -416,6 +480,7 @@ class CatalogService implements OnModuleInit { return result; } + async createDataDocs(body) { const nimbusUrl = this._getNimbusUrl(body); const { data } = await axios.post( @@ -425,9 +490,11 @@ class CatalogService implements OnModuleInit { return data; } + async findAllTags(data, metadata) { this.logger.info('CatalogService - findAllCustomerTags'); + const response = await lastValueFrom( this.catalogReadService.GetCustomerTags(data, metadata), ) @@ -440,6 +507,7 @@ class CatalogService implements OnModuleInit { throw new Error(err); }); + return response; } async getAssetsUsersAndRoles(data_assets: Array, customer_id: string) { @@ -454,6 +522,7 @@ class CatalogService implements OnModuleInit { (u) => u.id === data_asset.owner, )?.email; + const roles = []; const users = []; const data_asset_roles = data_asset?.roles || [] @@ -462,6 +531,7 @@ class CatalogService implements OnModuleInit { if (role) roles.push({ id: role.id, name: role.name }); } + const data_asset_users = data_asset?.users || [] for (const user_id of data_asset_users) { const user = customer_users.find((r) => r.id === user_id); @@ -476,6 +546,7 @@ class CatalogService implements OnModuleInit { }); } + async triggerCatalog(data: TriggerCatalogReq, metadata: Metadata) { const { session } = await lastValueFrom( this.catalogWriteService.TriggerDatasetCataloging(data, metadata), @@ -489,6 +560,7 @@ class CatalogService implements OnModuleInit { return res; } + async addRlsRule(data: AddRlsRuleRequest, metadata: Metadata) { const res = await lastValueFrom( this.catalogWriteService.AddRlsRule(data, metadata), @@ -496,6 +568,7 @@ class CatalogService implements OnModuleInit { return res; } + async removeRlsRule(id: number, metadata: Metadata) { const res = await lastValueFrom( this.catalogWriteService.RemoveRlsRule({ id }, metadata), @@ -503,12 +576,14 @@ class CatalogService implements OnModuleInit { return res; } + async batchRemoveRlsRule( query: BatchRemoveRlsRulesRequest, metadata: Metadata, ) { const { id_rls, nimbus_dashboard_id } = query; + if (id_rls && nimbus_dashboard_id) { throw new BadRequestException( "You can't delete using both parameters. Choose either 'id_rls' or 'nimbus_dashboard_id'", @@ -529,6 +604,7 @@ class CatalogService implements OnModuleInit { return 'OK'; } + async getRlsRules(data: GetRlsRulesRequest, metadata: Metadata) { const res = await lastValueFrom( this.catalogReadService.GetRlsRules(data, metadata), @@ -536,6 +612,7 @@ class CatalogService implements OnModuleInit { return res.rls_rules; } + async getOneRlsRule(id: number, metadata: Metadata) { const res = await lastValueFrom( this.catalogReadService.GetOneRlsRule({ id }, metadata), @@ -543,6 +620,7 @@ class CatalogService implements OnModuleInit { return res.rls_rule; } + async getNimbusDashboards( data: GetNimbusDashboardsRequest, metadata: Metadata, @@ -553,19 +631,24 @@ class CatalogService implements OnModuleInit { return res.dashboards; } + async createTableMetadata(body: any): Promise { const nimbusUrl = this._getNimbusUrl(body); this.logger.info(`Nimbus URL: ${nimbusUrl}`, {...body.logMetadata}); + const endpoint = `${nimbusUrl}/api/catalog/table-metadata/`; + this.logger.info(`Creating table metadata for table ${body.table_metadata.table_name}`, {...body.logMetadata}); this.logger.info(`Using endpoint: ${endpoint}`, {...body.logMetadata}); this.logger.debug(`Payload: ${JSON.stringify(body.table_metadata)}`, {...body.logMetadata}); + try { const { data, status } = await axios.post(endpoint, {...body.table_metadata}); + this.logger.info( `Table metadata created successfully with status ${status} for table ${body.table_metadata.table_name}`, {...body.logMetadata}, @@ -580,19 +663,23 @@ class CatalogService implements OnModuleInit { } } + async createColumnMetadata(body: any): Promise { const nimbusUrl = this._getNimbusUrl(body); this.logger.info(`Nimbus URL: ${nimbusUrl}`, body.logMetadata); const endpoint = `${nimbusUrl}/api/catalog/column-metadata/`; + + try { this.logger.info(`Creating column metadata for table ${body.column_metadata.table_name}`, {...body.logMetadata}); this.logger.info(`Using endpoint: ${endpoint}`, {...body.logMetadata}); this.logger.debug(`Payload: ${JSON.stringify(body.column_metadata)}`, {...body.logMetadata}); const { data, status } = await axios.post(endpoint, body.column_metadata); + this.logger.info( `Column metadata created successfully with status ${status} for table ${body.column_metadata.table_name}`, {...body.logMetadata}, @@ -607,18 +694,22 @@ class CatalogService implements OnModuleInit { } } + async createDataPreview(body: any): Promise { const nimbusUrl = this._getNimbusUrl(body); this.logger.info(`Nimbus URL: ${nimbusUrl}`, {...body.logMetadata}); const endpoint = `${nimbusUrl}/api/catalog/data-preview/`; + this.logger.info(`Creating data preview for table ${body.data_preview.table_name}`, {...body.logMetadata}); this.logger.info(`Using endpoint: ${endpoint}`, {...body.logMetadata}); this.logger.debug(`Payload: ${JSON.stringify(body.data_preview)}`, {...body.logMetadata}); + try { const { data, status } = await axios.post(endpoint, body.data_preview); + this.logger.info( `Data preview created successfully with status ${status} for table ${body.data_preview.table_name}`, {...body.logMetadata}, @@ -635,9 +726,11 @@ class CatalogService implements OnModuleInit { } } + async catalogDatasetItem(table_metadata_id: number, metadata: Metadata) { const customer_name_raw = metadata.get('customer_name'); + const customer_name = customer_name_raw?.[0]?.toString(); if (!customer_name) { throw new BadRequestException('Customer name not found in metadata'); @@ -660,4 +753,5 @@ class CatalogService implements OnModuleInit { } } -export { CatalogService }; \ No newline at end of file + +export { CatalogService }; diff --git a/src/modules/catalog/dtos/index.ts b/src/modules/catalog/dtos/index.ts index 59de404..39d2708 100644 --- a/src/modules/catalog/dtos/index.ts +++ b/src/modules/catalog/dtos/index.ts @@ -153,6 +153,18 @@ export class ICatalogAllRequest { example: 'user-id-1,user-id-2', }) owner?: string; + + @ApiPropertyOptional({ + description: 'Data inicial para filtro de catálogo (formato: YYYY-MM-DD)', + example: '2025-01-01', + }) + catalog_date_from?: string; + + @ApiPropertyOptional({ + description: 'Data final para filtro de catálogo (formato: YYYY-MM-DD)', + example: '2025-12-31', + }) + catalog_date_to?: string; } export class ICatalogAllResponse { From 1ff5589a2e567bb6ec506f347e2648ac43608cd7 Mon Sep 17 00:00:00 2001 From: viniciusgadea Date: Fri, 12 Dec 2025 15:03:43 -0300 Subject: [PATCH 20/41] FIX: Simplify searchDataAssets by owner logging and remove log code --- src/modules/catalog/catalog.service.ts | 67 +------------------------- 1 file changed, 1 insertion(+), 66 deletions(-) diff --git a/src/modules/catalog/catalog.service.ts b/src/modules/catalog/catalog.service.ts index 27c158f..5929849 100644 --- a/src/modules/catalog/catalog.service.ts +++ b/src/modules/catalog/catalog.service.ts @@ -238,71 +238,11 @@ class CatalogService implements OnModuleInit { metadata: Metadata, customer_id: string, ) { - this.logger.info('CatalogService - searchDataAssets', { query }); + this.logger.info('CatalogService - searchDataAssets'); const { search, page, size, sort_by, order, ...filters } = query; - - this.logger.debug('Extracted filters:', { filters }); - - - console.log('MAESTRO VAI CHAMAR PI-FACTORY COM:', { - search, page, size, sort_by, order, - filters: filters - }); - - - if (filters.owner) { - const { users: customer_users } = - await this.userService.findAllUsersByCustomerId(customer_id); - - this.logger.info('Available users in database count:', { count: customer_users.length }); - this.logger.info('First 5 users:', { - users: customer_users.slice(0, 5).map(u => ({ id: u.id, email: u.email, name: u.name })) - }); - - - const ownerValues = Array.isArray(filters.owner) - ? filters.owner - : (typeof filters.owner === 'string' && filters.owner.includes(',') - ? filters.owner.split(',').map((o: string) => o.trim()) - : [filters.owner]); - - this.logger.info('Owner values to convert:', { ownerValues, ownerFiltersOriginal: filters.owner }); - - - const ownerIds = ownerValues - .map((ownerValue: string) => { - const normalizedOwner = ownerValue.replace(/\s/g, '+'); - const user = customer_users.find(u => { - const isIdMatch = u.id === ownerValue; - const isEmailMatch = u.email === ownerValue || u.email === normalizedOwner; - const isNameMatch = u.name === ownerValue || u.name === normalizedOwner; - this.logger.info('Comparing:', { - userId: u.id, - userEmail: u.email, - userName: u.name, - filterValue: ownerValue, - normalizedFilter: normalizedOwner, - idMatch: isIdMatch, - emailMatch: isEmailMatch, - nameMatch: isNameMatch, - }); - return isIdMatch || isEmailMatch || isNameMatch; - }); - this.logger.info('Looking for owner result:', { ownerValue, found: !!user, userId: user?.id }); - return user?.id || ownerValue; - }) - .filter((id: string) => id); - - - if (ownerIds.length > 0) { - filters.owner = ownerIds; - } - } - - const { data_assets, total } = await lastValueFrom( this.catalogReadService.GetAllDataAssets( { @@ -318,11 +258,6 @@ class CatalogService implements OnModuleInit { ); - - console.log('MAESTRO RECEBEU RESPOSTA DO PI-FACTORY'); - - - const result = JSON.parse(data_assets); From 918c3d7416cbb331d38868b2e5aeb2c2b7d9d3a4 Mon Sep 17 00:00:00 2001 From: viniciusgadea Date: Mon, 15 Dec 2025 09:08:08 -0300 Subject: [PATCH 21/41] FIX: Enhance searchDataAssets to convert owner names to IDs and log the conversion --- docsfera.json | 8 ++++++++ src/modules/catalog/catalog.service.ts | 27 ++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/docsfera.json b/docsfera.json index 4d80f69..4f1c426 100644 --- a/docsfera.json +++ b/docsfera.json @@ -4483,6 +4483,14 @@ "schema": { "type": "string" } + }, + { + "name": "asset_type", + "required": true, + "in": "query", + "schema": { + "type": "string" + } } ], "responses": { diff --git a/src/modules/catalog/catalog.service.ts b/src/modules/catalog/catalog.service.ts index 5929849..9d99f7d 100644 --- a/src/modules/catalog/catalog.service.ts +++ b/src/modules/catalog/catalog.service.ts @@ -243,6 +243,33 @@ class CatalogService implements OnModuleInit { const { search, page, size, sort_by, order, ...filters } = query; + if (filters.owner) { + const { users: customer_users } = + await this.userService.findAllUsersByCustomerId(customer_id); + + const ownerValues = Array.isArray(filters.owner) + ? filters.owner + : (typeof filters.owner === 'string' && filters.owner.includes(',') + ? filters.owner.split(',').map((o: string) => o.trim()) + : [filters.owner]); + + const ownerIds = ownerValues + .map((ownerValue: string) => { + const user = customer_users.find(u => + u.name === ownerValue || u.email === ownerValue || u.id === ownerValue + ); + return user ? user.id : ownerValue; + }) + .filter((id: string) => id); + + filters.owner = ownerIds; + + this.logger.info('Converted owner names to IDs:', { + original: ownerValues, + converted: ownerIds + }); + } + const { data_assets, total } = await lastValueFrom( this.catalogReadService.GetAllDataAssets( { From 288aaeabc0799262f16664fab42d1d6f0a9a0e98 Mon Sep 17 00:00:00 2001 From: viniciusgadea Date: Mon, 15 Dec 2025 12:00:50 -0300 Subject: [PATCH 22/41] FIX: Enhance filter handling in BuildDataAssetsFilterTerms to support empty owner condition --- src/modules/catalog/catalog.service.ts | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/modules/catalog/catalog.service.ts b/src/modules/catalog/catalog.service.ts index 9d99f7d..39090b1 100644 --- a/src/modules/catalog/catalog.service.ts +++ b/src/modules/catalog/catalog.service.ts @@ -240,10 +240,10 @@ class CatalogService implements OnModuleInit { ) { this.logger.info('CatalogService - searchDataAssets'); - const { search, page, size, sort_by, order, ...filters } = query; - - if (filters.owner) { + + // Verificar se filters.owner existe + if (filters.owner !== undefined && filters.owner !== null) { const { users: customer_users } = await this.userService.findAllUsersByCustomerId(customer_id); @@ -253,7 +253,11 @@ class CatalogService implements OnModuleInit { ? filters.owner.split(',').map((o: string) => o.trim()) : [filters.owner]); - const ownerIds = ownerValues + // Separar "sem owner" (marcador especial __null__) dos owners normais + const hasEmptyOwner = ownerValues.some(v => v === '__null__'); + const normalOwners = ownerValues.filter(v => v !== '__null__'); + + const ownerIds = normalOwners .map((ownerValue: string) => { const user = customer_users.find(u => u.name === ownerValue || u.email === ownerValue || u.id === ownerValue @@ -262,13 +266,12 @@ class CatalogService implements OnModuleInit { }) .filter((id: string) => id); - filters.owner = ownerIds; - - this.logger.info('Converted owner names to IDs:', { - original: ownerValues, - converted: ownerIds - }); - } + // Se tiver "sem owner", adicionar marcador especial + if (hasEmptyOwner) { + ownerIds.push('__null__'); + } + + filters.owner = ownerIds.length > 0 ? ownerIds : undefined; const { data_assets, total } = await lastValueFrom( this.catalogReadService.GetAllDataAssets( From fcf7fb054e7fa4f5e3fc84fd27e2e45a4f85631a Mon Sep 17 00:00:00 2001 From: viniciusgadea Date: Tue, 16 Dec 2025 09:55:24 -0300 Subject: [PATCH 23/41] FIX: Update searchDataAssets to handle owner filtering and convert owner names to IDs --- src/modules/catalog/catalog.service.ts | 46 +++++++++++++++----------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/modules/catalog/catalog.service.ts b/src/modules/catalog/catalog.service.ts index 39090b1..c56313e 100644 --- a/src/modules/catalog/catalog.service.ts +++ b/src/modules/catalog/catalog.service.ts @@ -240,38 +240,44 @@ class CatalogService implements OnModuleInit { ) { this.logger.info('CatalogService - searchDataAssets'); - const { search, page, size, sort_by, order, ...filters } = query; + const { search, page, size, sort_by, order, owner, ...filters } = query; // Verificar se filters.owner existe - if (filters.owner !== undefined && filters.owner !== null) { + if (owner !== undefined && owner !== null) { const { users: customer_users } = await this.userService.findAllUsersByCustomerId(customer_id); - const ownerValues = Array.isArray(filters.owner) - ? filters.owner - : (typeof filters.owner === 'string' && filters.owner.includes(',') - ? filters.owner.split(',').map((o: string) => o.trim()) - : [filters.owner]); + let ownerValues: string[]; + if (Array.isArray(owner)) { + ownerValues = owner; + } else if (typeof owner === 'string' && owner.includes(',')) { + ownerValues = owner.split(',').map((o: string) => o.trim()); + } else { + ownerValues = [owner]; + } - // Separar "sem owner" (marcador especial __null__) dos owners normais - const hasEmptyOwner = ownerValues.some(v => v === '__null__'); - const normalOwners = ownerValues.filter(v => v !== '__null__'); + // Separar "sem owner" (marcador especial null) dos owners normais + const hasEmptyOwner = ownerValues.some(v => v === 'null'); + const normalOwners = ownerValues.filter(v => v !== 'null'); - const ownerIds = normalOwners - .map((ownerValue: string) => { - const user = customer_users.find(u => - u.name === ownerValue || u.email === ownerValue || u.id === ownerValue - ); - return user ? user.id : ownerValue; - }) - .filter((id: string) => id); + // Converter nomes de owners para IDs + const ownerIds = normalOwners.map((ownerValue: string) => { + const user = customer_users.find(u => + u.name === ownerValue || u.email === ownerValue || u.id === ownerValue + ); + return user ? user.id : ownerValue; + }); // Se tiver "sem owner", adicionar marcador especial if (hasEmptyOwner) { - ownerIds.push('__null__'); + ownerIds.push('null'); } - filters.owner = ownerIds.length > 0 ? ownerIds : undefined; + // Adicionar ao filters apenas se houver valores + if (ownerIds.length > 0) { + filters.owner = ownerIds; + } + } const { data_assets, total } = await lastValueFrom( this.catalogReadService.GetAllDataAssets( From 01c1087e0725a159bb6958190d27660d055d758c Mon Sep 17 00:00:00 2001 From: Rafael Date: Tue, 16 Dec 2025 16:37:33 -0300 Subject: [PATCH 24/41] FEAT: add schedule limit validation and improve ES update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add schedule limit validation against customer's scheduleLimit from DUC - Improve ES update to only update provided fields - Mark GET /platform/pipeline/:pipelineId/pipeline_run as READY 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- docsfera.json | 893 +++++++++++++++++- src/main.ts | 1 + .../platform-api/platform-api.controller.ts | 304 +++++- src/services/dynamodb/dynamodb.service.ts | 85 +- .../elasticsearch/elasticsearch.service.ts | 27 + 5 files changed, 1275 insertions(+), 35 deletions(-) diff --git a/docsfera.json b/docsfera.json index a18dad5..3ae0f4c 100644 --- a/docsfera.json +++ b/docsfera.json @@ -42,6 +42,33 @@ ] } }, + "/auth/sign-out": { + "post": { + "operationId": "AuthController_signOut", + "parameters": [ + { + "name": "dadosfera-lang", + "in": "header", + "required": false, + "schema": { + "enum": [ + "pt-br", + "en-us" + ], + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "" + } + }, + "tags": [ + "Auth" + ] + } + }, "/auth/refresh-access-token": { "post": { "operationId": "AuthController_refreshAccessToken", @@ -3415,9 +3442,6 @@ "PipelinesV2" ], "security": [ - { - "access-token": [] - }, { "access-token": [] } @@ -3467,9 +3491,6 @@ "PipelinesV2" ], "security": [ - { - "access-token": [] - }, { "access-token": [] } @@ -3519,9 +3540,6 @@ "PipelinesV2" ], "security": [ - { - "access-token": [] - }, { "access-token": [] } @@ -6760,6 +6778,863 @@ } } }, + "/platform/pipeline": { + "post": { + "operationId": "PlatformApiController_createPipeline", + "summary": "Create a new pipeline", + "parameters": [], + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, + "/platform/pipelines": { + "get": { + "operationId": "PlatformApiController_getPipelines", + "summary": "List all pipelines for customer", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, + "/platform/pipeline/{pipelineId}": { + "get": { + "operationId": "PlatformApiController_getPipeline", + "summary": "Get pipeline by ID", + "parameters": [ + { + "name": "pipelineId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + }, + "patch": { + "operationId": "PlatformApiController_updatePipeline", + "summary": "Update pipeline by ID", + "parameters": [ + { + "name": "pipelineId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + }, + "delete": { + "operationId": "PlatformApiController_deletePipeline", + "summary": "Delete pipeline by ID", + "parameters": [ + { + "name": "pipelineId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, + "/platform/pipeline/execute": { + "post": { + "operationId": "PlatformApiController_executePipeline", + "summary": "Execute a pipeline", + "parameters": [], + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, + "/platform/pipeline/pause": { + "post": { + "operationId": "PlatformApiController_pausePipeline", + "summary": "Pause a pipeline", + "parameters": [], + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, + "/platform/pipeline/unpause": { + "post": { + "operationId": "PlatformApiController_unpausePipeline", + "summary": "Unpause a pipeline", + "parameters": [], + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, + "/platform/pipeline/{pipelineId}/memory": { + "put": { + "operationId": "PlatformApiController_updatePipelineMemory", + "summary": "Update pipeline memory configuration", + "parameters": [ + { + "name": "pipelineId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, + "/platform/pipeline/{pipelineId}/metadata": { + "put": { + "operationId": "PlatformApiController_updatePipelineMetadata", + "summary": "Update pipeline metadata", + "parameters": [ + { + "name": "pipelineId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, + "/platform/pipelines/metadata": { + "get": { + "operationId": "PlatformApiController_getPipelinesMetadata", + "summary": "Get all pipelines metadata", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, + "/platform/pipeline/pipeline_run": { + "post": { + "operationId": "PlatformApiController_createPipelineRun", + "summary": "Create a pipeline run", + "parameters": [], + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, + "/platform/pipeline/{pipelineId}/pipeline_run": { + "get": { + "operationId": "PlatformApiController_getPipelineRuns", + "summary": "Get pipeline runs for a pipeline", + "parameters": [ + { + "name": "pipelineId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, + "/platform/pipeline/{pipelineId}/pipeline_run/{runId}": { + "get": { + "operationId": "PlatformApiController_getPipelineRun", + "summary": "Get specific pipeline run", + "parameters": [ + { + "name": "pipelineId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "runId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, + "/platform/pipeline/pipeline_run/{runId}/logs": { + "get": { + "operationId": "PlatformApiController_getPipelineRunLogs", + "summary": "Get pipeline run logs", + "parameters": [ + { + "name": "runId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, + "/platform/jobs/{jobId}/input": { + "put": { + "operationId": "PlatformApiController_updateJobInput", + "summary": "Update job input columns", + "parameters": [ + { + "name": "jobId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + }, + "patch": { + "operationId": "PlatformApiController_patchJobInput", + "summary": "Partial update job input columns", + "parameters": [ + { + "name": "jobId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, + "/platform/jobs/{jobId}/memory": { + "put": { + "operationId": "PlatformApiController_updateJobMemory", + "summary": "Update job memory configuration", + "parameters": [ + { + "name": "jobId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, + "/platform/jobs/{jobId}/reset-state": { + "post": { + "operationId": "PlatformApiController_resetJobState", + "summary": "Reset job state", + "parameters": [ + { + "name": "jobId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, + "/platform/jobs/jdbc/{jobId}": { + "get": { + "operationId": "PlatformApiController_getJdbcJob", + "summary": "Get JDBC job details", + "parameters": [ + { + "name": "jobId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, + "/platform/jobs/jdbc/{jobId}/sync-mode": { + "post": { + "operationId": "PlatformApiController_updateJdbcSyncMode", + "summary": "Update JDBC job sync mode", + "parameters": [ + { + "name": "jobId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, + "/platform/jobs/jdbc/configs/allowed_datatypes": { + "get": { + "operationId": "PlatformApiController_getJdbcAllowedDatatypes", + "summary": "Get allowed datatypes for JDBC", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, + "/platform/jobs/singer/{jobId}": { + "get": { + "operationId": "PlatformApiController_getSingerJob", + "summary": "Get Singer job details", + "parameters": [ + { + "name": "jobId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, + "/platform/jobs/singer/{jobId}/sync-mode": { + "post": { + "operationId": "PlatformApiController_updateSingerSyncMode", + "summary": "Update Singer job sync mode", + "parameters": [ + { + "name": "jobId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, + "/platform/jobs/s3/{jobId}": { + "get": { + "operationId": "PlatformApiController_getS3Job", + "summary": "Get S3 job details", + "parameters": [ + { + "name": "jobId", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, + "/platform/health": { + "get": { + "operationId": "PlatformApiController_healthCheck", + "summary": "Platform API health check", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + } + }, + "tags": [ + "Platform API" + ], + "security": [ + { + "access-token": [] + } + ] + } + }, "/health": { "get": { "operationId": "HealthController_check", diff --git a/src/main.ts b/src/main.ts index 70161e2..fc90db0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -22,6 +22,7 @@ async function bootstrap() { logger, cors: { origin: [ + 'http://localhost:4200', 'https://app.stg.dadosfera.ai', 'https://app.dadosfera.ai', 'https://unimed.dadosfera.ai', diff --git a/src/modules/platform-api/platform-api.controller.ts b/src/modules/platform-api/platform-api.controller.ts index d9006ef..3be5530 100644 --- a/src/modules/platform-api/platform-api.controller.ts +++ b/src/modules/platform-api/platform-api.controller.ts @@ -49,7 +49,52 @@ export class PlatformApiController { return id?.replace(/-/g, '_') || ''; } + /** + * Denormalize ID back to UUID format (replace _ with -). + * Used when we receive a normalized ID but need the original UUID. + */ + private denormalizeId(id: string): string { + return id?.replace(/_/g, '-') || ''; + } + + /** + * Normalize job ID to match Platform-API format. + * Platform-API replaces '-' with '_' in job IDs. + * + * Example: "2ccf5481-59f5-4036-8a94-7d5f28f4f899-0" -> "2ccf5481_59f5_4036_8a94_7d5f28f4f899_0" + */ + private normalizeJobId(jobId: string): string { + return jobId?.replace(/-/g, '_') || ''; + } + + /** + * Extract the pipeline ID (base UUID) from a job ID. + * Job IDs have format "uuid-suffix" where suffix is the job index (e.g., "0", "1"). + * Handles both hyphenated and underscored formats, always returns hyphenated UUID for ES. + * + * Examples: + * - "2ccf5481-59f5-4036-8a94-7d5f28f4f899-0" -> "2ccf5481-59f5-4036-8a94-7d5f28f4f899" + * - "2ccf5481_59f5_4036_8a94_7d5f28f4f899_0" -> "2ccf5481-59f5-4036-8a94-7d5f28f4f899" + */ + private extractPipelineIdFromJobId(jobId: string): string { + if (!jobId) return ''; + + // Determine the separator used in the jobId + const hasUnderscores = jobId.includes('_'); + const separator = hasUnderscores ? '_' : '-'; + + const parts = jobId.split(separator); + // UUID has 5 parts (8-4-4-4-12), job suffix is the 6th part + if (parts.length >= 6) { + // Always return hyphenated format for Elasticsearch lookup + return parts.slice(0, 5).join('-'); + } + // If no suffix found, return the ID in hyphenated format + return hasUnderscores ? jobId.replace(/_/g, '-') : jobId; + } + private readonly VALID_CONNECTORS = ['jdbc', 'singer', 's3']; + private readonly MAX_MEMORY_MB = 12000; // 12GB maximum memory per pipeline/job /** * Validate that connector is provided and is a valid type. @@ -62,6 +107,17 @@ export class PlatformApiController { } } + /** + * Validate memory allocation against maximum limit. + */ + private validateMemory(memoryMb: number): void { + if (memoryMb > this.MAX_MEMORY_MB) { + throw new BadRequestException( + `Memory limit exceeded. Maximum allowed: ${this.MAX_MEMORY_MB}MB (12GB)`, + ); + } + } + /** * Validate cron expression against customer's schedule limit. * Fetches current scheduleLimit from DUC to ensure up-to-date configuration. @@ -107,7 +163,7 @@ export class PlatformApiController { name: string; type: string; columns?: string[]; - reference_column?: { name: string; type: string }; + reference_column?: string; }> { if (!jobs || jobs.length === 0) return []; @@ -115,7 +171,7 @@ export class PlatformApiController { name: string; type: string; columns?: string[]; - reference_column?: { name: string; type: string }; + reference_column?: string; }> = []; for (const job of jobs) { @@ -128,7 +184,7 @@ export class PlatformApiController { name: string; type: string; columns?: string[]; - reference_column?: { name: string; type: string }; + reference_column?: string; } = { name: input.table_name || '', type: input.load_type || 'full_load', @@ -137,10 +193,8 @@ export class PlatformApiController { table.columns = input.column_include_list; } if (input.incremental_column_name) { - table.reference_column = { - name: input.incremental_column_name, - type: input.incremental_column_type || 'timestamp', - }; + // reference_column is stored as a string (column name) + table.reference_column = input.incremental_column_name; } tables.push(table); } else if (connector === 'singer' || connector === 's3') { @@ -205,6 +259,148 @@ export class PlatformApiController { return properties; } + /** + * Sync job input changes to DynamoDB for a specific connector type. + * Extracts pipeline ID from job ID, fetches ES document to find input ID, + * then updates the table entry in DynamoDB. + * + * Job ID transformations: + * - Raw format (from endpoint): "2ccf5481-59f5-4036-8a94-7d5f28f4f899-0" + * - Platform API format: "2ccf5481_59f5_4036_8a94_7d5f28f4f899_0" (underscores) + * - Elasticsearch pipeline ID: "2ccf5481-59f5-4036-8a94-7d5f28f4f899" (UUID only, hyphens) + * + * @param connectorType - The connector type ('jdbc', 'singer', 's3') for the Platform API endpoint + */ + private async syncJobInputToDynamoDB( + jobId: string, + body: any, + user: RequestUser, + connectorType: 'jdbc' | 'singer' | 's3', + ): Promise { + try { + // Normalize job ID for Platform API GET (replace - with _) + const normalizedJobId = this.normalizeJobId(jobId); + + // Get job details using connector-specific endpoint to find table_name + const jobResult = await this.platformApiService.proxy( + 'GET', + `/jobs/${connectorType}/${normalizedJobId}`, + user, + ); + + // Extract the pipeline ID (base UUID) from the raw job ID for ES lookup + const esPipelineId = this.extractPipelineIdFromJobId(jobId); + const tableName = body.table_name || jobResult.source_config?.table_name; + + if (!esPipelineId || !tableName) { + this.logger.warn('Cannot sync job input: missing pipeline_id or table_name', { + jobId, + esPipelineId, + tableName, + }); + return; + } + + // Get pipeline from ES to find input ID (stored in config.tables) + const pipeline = await this.elasticsearchService.getPipeline( + user.customer_name, + esPipelineId, + ); + + const inputId = pipeline?.config?.tables; + if (!inputId) { + this.logger.warn('Cannot sync job input: no input ID in ES', { + jobId, + esPipelineId, + }); + return; + } + + // Build changes for DynamoDB table entry + // reference_column is stored as a string (column name), not an object + const changes: { + type?: string; + columns?: string[]; + reference_column?: string | null; + } = {}; + + + if ('target_load_type' in body) { + changes.type = body.target_load_type; + } + if ('column_include_list' in body) { + changes.columns = body.column_include_list; + } + if ('incremental_column_name' in body) { + // reference_column is just the column name as a string + changes.reference_column = body.incremental_column_name || null; + } + + // Update DynamoDB if there are changes + if (Object.keys(changes).length > 0) { + await this.dynamoDBService.updateInputTable( + user.customer_id, + inputId, + tableName, + changes, + ); + } + } catch (error) { + this.logger.error('Failed to sync job input to DynamoDB', { + jobId, + connectorType, + error: error.message, + }); + // Don't throw - Platform API update succeeded, just log the sync error + } + } + + /** + * Sync sync-mode changes to DynamoDB for JDBC connectors. + * Always passes both target_load_type and incremental_column_name to ensure proper sync. + */ + private async syncJdbcSyncModeToDynamoDB( + jobId: string, + body: any, + user: RequestUser, + ): Promise { + // JDBC sync mode uses target_load_type field + const changes: any = {}; + + if ('target_load_type' in body) { + changes.target_load_type = body.target_load_type; + } + + // Handle incremental_column_name: + // - If provided in body, use that value + // - If changing to full_load, explicitly clear it + if ('incremental_column_name' in body) { + changes.incremental_column_name = body.incremental_column_name; + changes.incremental_column_type = body.incremental_column_type; + } else if (body.target_load_type === 'full_load') { + // Changing to full_load without specifying incremental_column - clear it + changes.incremental_column_name = null; + } + + await this.syncJobInputToDynamoDB(jobId, changes, user, 'jdbc'); + } + + /** + * Sync sync-mode changes to DynamoDB for Singer connectors. + */ + private async syncSingerSyncModeToDynamoDB( + jobId: string, + body: any, + user: RequestUser, + ): Promise { + // Singer sync mode uses replication_method field + // Map to DynamoDB type: FULL_TABLE -> full_load, INCREMENTAL -> incremental + if ('replication_method' in body) { + const type = body.replication_method === 'INCREMENTAL' ? 'incremental' : 'full_load'; + await this.syncJobInputToDynamoDB(jobId, { load_type: type }, user, 'singer'); + } + } + // ==================== PIPELINE ROUTES ==================== @Post('pipeline') @@ -446,6 +642,11 @@ export class PlatformApiController { @Body() body: any, @User() user: RequestUser, ) { + // Validate memory limit + if (body.amount) { + this.validateMemory(body.amount); + } + return this.platformApiService.proxy( 'PUT', `/pipeline/${pipelineId}/memory`, @@ -561,12 +762,23 @@ export class PlatformApiController { @Body() body: any, @User() user: RequestUser, ) { - return this.platformApiService.proxy( + // Normalize job ID for Platform API (replace - with _) + const normalizedJobId = this.normalizeJobId(jobId); + + const result = await this.platformApiService.proxy( 'PUT', - `/jobs/${jobId}/input`, + `/jobs/${normalizedJobId}/input`, user, body, ); + + // Sync to DynamoDB if connector type is provided + const connectorType = body.connector as 'jdbc' | 'singer' | 's3' | undefined; + if (connectorType && this.VALID_CONNECTORS.includes(connectorType)) { + await this.syncJobInputToDynamoDB(jobId, body, user, connectorType); + } + + return result; } @Patch('jobs/:jobId/input') @@ -577,12 +789,23 @@ export class PlatformApiController { @Body() body: any, @User() user: RequestUser, ) { - return this.platformApiService.proxy( + // Normalize job ID for Platform API (replace - with _) + const normalizedJobId = this.normalizeJobId(jobId); + + const result = await this.platformApiService.proxy( 'PATCH', - `/jobs/${jobId}/input`, + `/jobs/${normalizedJobId}/input`, user, body, ); + + // Sync to DynamoDB if connector type is provided + const connectorType = body.connector as 'jdbc' | 'singer' | 's3' | undefined; + if (connectorType && this.VALID_CONNECTORS.includes(connectorType)) { + await this.syncJobInputToDynamoDB(jobId, body, user, connectorType); + } + + return result; } @Put('jobs/:jobId/memory') @@ -593,9 +816,17 @@ export class PlatformApiController { @Body() body: any, @User() user: RequestUser, ) { + // Validate memory limit + if (body.amount) { + this.validateMemory(body.amount); + } + + // Normalize job ID for Platform API (replace - with _) + const normalizedJobId = this.normalizeJobId(jobId); + return this.platformApiService.proxy( 'PUT', - `/jobs/${jobId}/memory`, + `/jobs/${normalizedJobId}/memory`, user, body, ); @@ -609,9 +840,12 @@ export class PlatformApiController { @Body() body: any, @User() user: RequestUser, ) { + // Normalize job ID for Platform API (replace - with _) + const normalizedJobId = this.normalizeJobId(jobId); + return this.platformApiService.proxy( 'POST', - `/jobs/${jobId}/reset-state`, + `/jobs/${normalizedJobId}/reset-state`, user, body, ); @@ -623,10 +857,12 @@ export class PlatformApiController { @ApiOperation({ summary: 'Get JDBC job details' }) @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.GET) async getJdbcJob(@Param('jobId') jobId: string, @User() user: RequestUser) { - return this.platformApiService.proxy('GET', `/jobs/jdbc/${jobId}`, user); + // Normalize job ID for Platform API (replace - with _) + const normalizedJobId = this.normalizeJobId(jobId); + return this.platformApiService.proxy('GET', `/jobs/jdbc/${normalizedJobId}`, user); } - @Put('jobs/jdbc/:jobId/sync-mode') + @Post('jobs/jdbc/:jobId/sync-mode') @ApiOperation({ summary: 'Update JDBC job sync mode' }) @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE) async updateJdbcSyncMode( @@ -634,12 +870,20 @@ export class PlatformApiController { @Body() body: any, @User() user: RequestUser, ) { - return this.platformApiService.proxy( - 'PUT', - `/jobs/jdbc/${jobId}/sync-mode`, + // Normalize job ID for Platform API (replace - with _) + const normalizedJobId = this.normalizeJobId(jobId); + + const result = await this.platformApiService.proxy( + 'POST', + `/jobs/jdbc/${normalizedJobId}/sync-mode`, user, body, ); + + // Sync to DynamoDB (pass raw jobId for pipeline extraction) + await this.syncJdbcSyncModeToDynamoDB(jobId, body, user); + + return result; } @Get('jobs/jdbc/configs/allowed_datatypes') @@ -659,10 +903,12 @@ export class PlatformApiController { @ApiOperation({ summary: 'Get Singer job details' }) @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.GET) async getSingerJob(@Param('jobId') jobId: string, @User() user: RequestUser) { - return this.platformApiService.proxy('GET', `/jobs/singer/${jobId}`, user); + // Normalize job ID for Platform API (replace - with _) + const normalizedJobId = this.normalizeJobId(jobId); + return this.platformApiService.proxy('GET', `/jobs/singer/${normalizedJobId}`, user); } - @Put('jobs/singer/:jobId/sync-mode') + @Post('jobs/singer/:jobId/sync-mode') @ApiOperation({ summary: 'Update Singer job sync mode' }) @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE) async updateSingerSyncMode( @@ -670,12 +916,20 @@ export class PlatformApiController { @Body() body: any, @User() user: RequestUser, ) { - return this.platformApiService.proxy( - 'PUT', - `/jobs/singer/${jobId}/sync-mode`, + // Normalize job ID for Platform API (replace - with _) + const normalizedJobId = this.normalizeJobId(jobId); + + const result = await this.platformApiService.proxy( + 'POST', + `/jobs/singer/${normalizedJobId}/sync-mode`, user, body, ); + + // Sync to DynamoDB (pass raw jobId for pipeline extraction) + await this.syncSingerSyncModeToDynamoDB(jobId, body, user); + + return result; } // ==================== JOBS - S3 ROUTES ==================== @@ -684,7 +938,9 @@ export class PlatformApiController { @ApiOperation({ summary: 'Get S3 job details' }) @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.GET) async getS3Job(@Param('jobId') jobId: string, @User() user: RequestUser) { - return this.platformApiService.proxy('GET', `/jobs/s3/${jobId}`, user); + // Normalize job ID for Platform API (replace - with _) + const normalizedJobId = this.normalizeJobId(jobId); + return this.platformApiService.proxy('GET', `/jobs/s3/${normalizedJobId}`, user); } // ==================== HEALTH ROUTE ==================== diff --git a/src/services/dynamodb/dynamodb.service.ts b/src/services/dynamodb/dynamodb.service.ts index aead208..099eb99 100644 --- a/src/services/dynamodb/dynamodb.service.ts +++ b/src/services/dynamodb/dynamodb.service.ts @@ -24,7 +24,7 @@ export interface InputDocument { name: string; type: string; columns?: string[]; - reference_column?: { name: string; type: string }; + reference_column?: string; }>; credentials?: Record; } @@ -62,7 +62,7 @@ export class DynamoDBService { name: string; type: string; columns?: string[]; - reference_column?: { name: string; type: string }; + reference_column?: string; }>; }, ): Promise { @@ -145,4 +145,85 @@ export class DynamoDBService { this.logger.info('DynamoDB: Input deleted successfully', { inputId }); } + + /** + * Update a specific table entry in the input document. + * Fetches the current document, updates the matching table, and saves. + */ + async updateInputTable( + clientId: string, + inputId: string, + tableName: string, + changes: { + type?: string; + columns?: string[]; + reference_column?: string | null; + }, + ): Promise { + const dynamoTableName = DYNAMODB_CONFIG.inputsTable(); + + this.logger.info('DynamoDB: Updating input table', { + inputId, + tableName, + changes: Object.keys(changes), + }); + + // Get current document + const current = await this.findInput(clientId, inputId); + if (!current) { + this.logger.warn('DynamoDB: Input not found for update', { inputId }); + return; + } + + // Find and update the matching table + const tables = current.tables || []; + const tableIndex = tables.findIndex((t) => t.name === tableName); + + if (tableIndex === -1) { + this.logger.warn('DynamoDB: Table not found in input', { + inputId, + tableName, + }); + return; + } + + // Merge changes into the table entry + const updatedTable = { ...tables[tableIndex] }; + if ('type' in changes) updatedTable.type = changes.type; + if ('columns' in changes) updatedTable.columns = changes.columns; + if ('reference_column' in changes) { + if (changes.reference_column === null) { + delete updatedTable.reference_column; + } else { + updatedTable.reference_column = changes.reference_column; + } + } + + tables[tableIndex] = updatedTable; + + // Save updated document + const putCommand = new PutCommand({ + TableName: dynamoTableName, + Item: { + ...current, + tables, + updated_at: new Date().toISOString(), + }, + }); + + try { + await this.documentClient.send(putCommand); + this.logger.info('DynamoDB: Input table updated successfully', { + inputId, + tableName, + }); + } catch (error) { + this.logger.error('DynamoDB: Failed to update input table', { + inputId, + tableName, + error: error.message, + }); + throw error; + } + } } diff --git a/src/services/elasticsearch/elasticsearch.service.ts b/src/services/elasticsearch/elasticsearch.service.ts index 281b5d6..e2c1857 100644 --- a/src/services/elasticsearch/elasticsearch.service.ts +++ b/src/services/elasticsearch/elasticsearch.service.ts @@ -293,6 +293,33 @@ export class ElasticsearchService { } } + async getPipeline( + customerName: string, + pipelineId: string, + ): Promise { + const index = this.getIndex(customerName); + + this.logger.info('Elasticsearch: Getting pipeline', { + index, + pipelineId, + }); + + try { + const response = await this.client.get(`${index}/_doc/${pipelineId}`); + return response.data._source as PipelineDocument; + } catch (error) { + if (error instanceof AxiosError && error.response?.status === 404) { + this.logger.warn('Elasticsearch: Pipeline not found', { + pipelineId, + index, + }); + return null; + } + this.handleError('getPipeline', error, { pipelineId, index }); + throw error; + } + } + async deletePipeline( customerName: string, pipelineId: string, From 0a5e8001f9245c2700aebd0772e143fc7b64d666 Mon Sep 17 00:00:00 2001 From: Rafael Date: Tue, 16 Dec 2025 16:57:25 -0300 Subject: [PATCH 25/41] UPDATE: adding platform_api_url --- deploy/helm-chart/templates/deployment.yaml | 2 ++ deploy/helm-chart/values-stg.yaml | 1 + deploy/helm-chart/values.yaml | 1 + 3 files changed, 4 insertions(+) diff --git a/deploy/helm-chart/templates/deployment.yaml b/deploy/helm-chart/templates/deployment.yaml index 63524f3..8614986 100644 --- a/deploy/helm-chart/templates/deployment.yaml +++ b/deploy/helm-chart/templates/deployment.yaml @@ -106,6 +106,8 @@ spec: value: "{{ .Values.maestro.redis_port }}" - name: REDIS_TLS value: "{{ .Values.maestro.redis_tls }}" + - name: PLATFORM_API_URL + value: {{ .Values.maestro.platform_api_url }} - name: JWT_PRIVATE_KEY valueFrom: secretKeyRef: diff --git a/deploy/helm-chart/values-stg.yaml b/deploy/helm-chart/values-stg.yaml index 13e2d05..360aafe 100644 --- a/deploy/helm-chart/values-stg.yaml +++ b/deploy/helm-chart/values-stg.yaml @@ -8,6 +8,7 @@ maestro: open_group_id: e3f98a2f-7748-4981-8505-7695c8ca8218 cookie_secret: "ff7bc13823edb2ae50d248e5780bddc9d4b31c36" redis_database: "1" + platform_api_url: https://xs2hkhq07k.execute-api.us-east-1.amazonaws.com hostname: maestro.stg.dadosfera.ai diff --git a/deploy/helm-chart/values.yaml b/deploy/helm-chart/values.yaml index 2f703b7..30af31e 100644 --- a/deploy/helm-chart/values.yaml +++ b/deploy/helm-chart/values.yaml @@ -43,6 +43,7 @@ maestro: upload_file_agent_connection: cbc2f881-58c4-4d60-8003-0979b0b5b911 open_customer_id: f239718a-a271-4ef9-ae7e-02a2f0f3aa6e open_group_id: 401573bb-334f-44b2-b30e-88d4cea31ae9 + platform_api_url: https://oz8v2zid1e.execute-api.us-east-1.amazonaws.com dedicated_proxy: "" restricted_ip: "" redis_host: "aaapzppmlyamkocqwstpo7zvopczyyiyuy6xzm2g6c5k4mq3a66be4a-0.redis.sa-saopaulo-1.oci.oraclecloud.com" From bd231382ebf3f5567d7cd38a21c037090e0a7c22 Mon Sep 17 00:00:00 2001 From: Rafael Moraes Date: Tue, 16 Dec 2025 19:58:38 +0000 Subject: [PATCH 26/41] =?UTF-8?q?WIP:=20adiciona=20par=C3=A2metro=20asset?= =?UTF-8?q?=5Ftype=20no=20docsfera.json?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docsfera.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docsfera.json b/docsfera.json index 4d80f69..4f1c426 100644 --- a/docsfera.json +++ b/docsfera.json @@ -4483,6 +4483,14 @@ "schema": { "type": "string" } + }, + { + "name": "asset_type", + "required": true, + "in": "query", + "schema": { + "type": "string" + } } ], "responses": { From fc9c0b09910238b4bc6df67e47df0f4079465148 Mon Sep 17 00:00:00 2001 From: Rafael Moraes Date: Tue, 16 Dec 2025 19:54:03 +0000 Subject: [PATCH 27/41] WIP: Filtro Source - backend --- src/modules/catalog/catalog.service.ts | 304 +++++++++++++------------ 1 file changed, 153 insertions(+), 151 deletions(-) diff --git a/src/modules/catalog/catalog.service.ts b/src/modules/catalog/catalog.service.ts index c56313e..7660cd6 100644 --- a/src/modules/catalog/catalog.service.ts +++ b/src/modules/catalog/catalog.service.ts @@ -41,7 +41,6 @@ import { import { TypeParser } from 'src/utils/FileParser/parser-types'; import { ParserBuilder } from 'src/utils/FileParser/parser.builder'; - class CatalogService implements OnModuleInit { catalogReadService: ReadService.CatalogReadServices; catalogWriteService: WriteService.CatalogWriteServices; @@ -58,7 +57,6 @@ class CatalogService implements OnModuleInit { this.logger = dadosferaLogger.logger; } - onModuleInit() { this.catalogReadService = this.grpcClient.getService( @@ -74,81 +72,65 @@ class CatalogService implements OnModuleInit { ); } - _getNimbusUrl(body) { this.logger.debug(`Body: ${JSON.stringify(body)}`); const customer = body.info.customer.toLowerCase(); - if (process.env.ENV === 'prd') { return `https://nimbus-${customer}.dadosfera.ai`; } - return `https://nimbus-${customer}.${process.env.ENV.replace( 'local', 'stg', )}.dadosfera.ai`; } - async getPiiReporter(metadata: Metadata, type: TypeParser) { - this.logger.info('getPiiReporter: ' + type) + this.logger.info('getPiiReporter: ' + type); try { - const { - data - } = await lastValueFrom( - this.catalogWriteService.GetPiiReporter({}, metadata) - ) - this.logger.info("Finish grpc call") - + const { data } = await lastValueFrom( + this.catalogWriteService.GetPiiReporter({}, metadata), + ); + this.logger.info('Finish grpc call'); const parser = ParserBuilder.build(type); - - this.logger.info('parser file to: ' + type) - const file = await parser.parse(data) - this.logger.info('finish parser') + this.logger.info('parser file to: ' + type); + const file = await parser.parse(data); + this.logger.info('finish parser'); const mimeTypes: Record = { - 'csv': 'text/csv', - 'html': 'text/html', - 'pdf': 'application/pdf' - } - + csv: 'text/csv', + html: 'text/html', + pdf: 'application/pdf', + }; const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const filename = `relatorio-pii-${timestamp}.${type}`; - return { file, filename: filename, - type: mimeTypes[type] - } + type: mimeTypes[type], + }; } catch (error) { this.logger.error(error.message); throw error; } - - } - async createDataAsset(data: Messages.CreateDataAssetRequest, metadata) { this.logger.info('CatalogService - Manage Data assets permissions'); if (!data.embed) data.embed = undefined; - return lastValueFrom( this.catalogWriteService.CreateDataAsset(data, metadata), ); } - async managePermissions(data: Messages.ManagePermissionRequest, metadata) { this.logger.info('CatalogService - Manage Data assets permissions'); - return lastValueFrom( this.catalogWriteService.ManagePermission(data, metadata), ).catch((err) => { @@ -159,11 +141,9 @@ class CatalogService implements OnModuleInit { }); } - async revokePermissions(data: Messages.RevokePermissionRequest, metadata) { this.logger.info('CatalogService - Manage Data assets permissions'); - return lastValueFrom( this.catalogWriteService.RevokePermission(data, metadata), ).catch((err) => { @@ -174,11 +154,9 @@ class CatalogService implements OnModuleInit { }); } - async commentOnDataAsset(data: Messages.MakeACommentRequest, metadata) { this.logger.info('CatalogService - Manage Data assets permissions'); - return lastValueFrom( this.catalogWriteService.MakeAComment(data, metadata), ).catch((err) => { @@ -189,11 +167,9 @@ class CatalogService implements OnModuleInit { }); } - async deleteComment(data: Messages.UpdateACommentRequest, metadata) { this.logger.info('CatalogService - Manage Data assets permissions'); - return lastValueFrom( this.catalogWriteService.UpdateAComment(data, metadata), ).catch((err) => { @@ -204,11 +180,9 @@ class CatalogService implements OnModuleInit { }); } - async deleteDataAsset(data: Messages.DeleteDataAssetRequest, metadata) { this.logger.info('CatalogService - Manage Data assets permissions'); - return lastValueFrom( this.catalogWriteService.DeleteDataAsset(data, metadata), ).catch((err) => { @@ -219,61 +193,110 @@ class CatalogService implements OnModuleInit { }); } - async getUserRolesIds(userId: string) { const result = await this.userService.findOneById(userId).catch(() => null); - if (result) { return result.user.roles.map((role) => role.id); } - return []; } - async searchDataAssets( query: Record, metadata: Metadata, customer_id: string, ) { - this.logger.info('CatalogService - searchDataAssets'); + this.logger.info('CatalogService - searchDataAssets', { query }); - const { search, page, size, sort_by, order, owner, ...filters } = query; - - // Verificar se filters.owner existe - if (owner !== undefined && owner !== null) { + const { search, page, size, sort_by, order, ...filters } = query; + + this.logger.debug('Extracted filters:', { filters }); + + console.log('MAESTRO VAI CHAMAR PI-FACTORY COM (ANTES AJUSTE):', { + search, + page, + size, + sort_by, + order, + filters, + }); + + // 🔥 AJUSTE DO manually AQUI: converte '1'/'0' string em número ou remove + if ( + filters.manually !== undefined && + filters.manually !== null && + filters.manually !== '' + ) { + filters.manually = Number(filters.manually); // 1 ou 0 + } else { + delete filters.manually; + } + + console.log('MAESTRO VAI CHAMAR PI-FACTORY COM (DEPOIS AJUSTE):', { + search, + page, + size, + sort_by, + order, + filters, + }); + + if (filters.owner) { const { users: customer_users } = await this.userService.findAllUsersByCustomerId(customer_id); - let ownerValues: string[]; - if (Array.isArray(owner)) { - ownerValues = owner; - } else if (typeof owner === 'string' && owner.includes(',')) { - ownerValues = owner.split(',').map((o: string) => o.trim()); - } else { - ownerValues = [owner]; - } - - // Separar "sem owner" (marcador especial null) dos owners normais - const hasEmptyOwner = ownerValues.some(v => v === 'null'); - const normalOwners = ownerValues.filter(v => v !== 'null'); - - // Converter nomes de owners para IDs - const ownerIds = normalOwners.map((ownerValue: string) => { - const user = customer_users.find(u => - u.name === ownerValue || u.email === ownerValue || u.id === ownerValue - ); - return user ? user.id : ownerValue; + this.logger.info('Available users in database count:', { + count: customer_users.length, + }); + this.logger.info('First 5 users:', { + users: customer_users + .slice(0, 5) + .map((u) => ({ id: u.id, email: u.email, name: u.name })), }); - // Se tiver "sem owner", adicionar marcador especial - if (hasEmptyOwner) { - ownerIds.push('null'); - } + const ownerValues = Array.isArray(filters.owner) + ? filters.owner + : typeof filters.owner === 'string' && filters.owner.includes(',') + ? filters.owner.split(',').map((o: string) => o.trim()) + : [filters.owner]; + + this.logger.info('Owner values to convert:', { + ownerValues, + ownerFiltersOriginal: filters.owner, + }); + + const ownerIds = ownerValues + .map((ownerValue: string) => { + const normalizedOwner = ownerValue.replace(/\s/g, '+'); + const user = customer_users.find((u) => { + const isIdMatch = u.id === ownerValue; + const isEmailMatch = + u.email === ownerValue || u.email === normalizedOwner; + const isNameMatch = + u.name === ownerValue || u.name === normalizedOwner; + this.logger.info('Comparing:', { + userId: u.id, + userEmail: u.email, + userName: u.name, + filterValue: ownerValue, + normalizedFilter: normalizedOwner, + idMatch: isIdMatch, + emailMatch: isEmailMatch, + nameMatch: isNameMatch, + }); + return isIdMatch || isEmailMatch || isNameMatch; + }); + this.logger.info('Looking for owner result:', { + ownerValue, + found: !!user, + userId: user?.id, + }); + return user?.id || ownerValue; + }) + .filter((id: string) => id); - // Adicionar ao filters apenas se houver valores if (ownerIds.length > 0) { filters.owner = ownerIds; } @@ -293,20 +316,18 @@ class CatalogService implements OnModuleInit { ), ); + console.log('MAESTRO RECEBEU RESPOSTA DO PI-FACTORY'); const result = JSON.parse(data_assets); - const response = await this.getAssetsUsersAndRoles( result.data_assets, customer_id, ); - return { data_assets: response, total }; } - async downloadAssets( query: Record, metadata: Metadata, @@ -314,33 +335,27 @@ class CatalogService implements OnModuleInit { ) { const data = await this.searchDataAssets(query, metadata, customer_id); - - const formatData = data.data_assets.map(asset => ({ + const formatData = data.data_assets.map((asset) => ({ id: asset.id, display_name: asset.display_name, data_asset_type: asset.data_asset_type, created_at: asset.created_at, - tags: '[' + asset.tags.join(', ') + ']' - })) - + tags: '[' + asset.tags.join(', ') + ']', + })); const parser = ParserBuilder.build('csv'); - const file = await parser.parse(formatData); - const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const filename = `dadosfera_assets_${timestamp}.csv`; - return { file, - filename - } + filename, + }; } - async getOneDataAsset(data: { id: string; customer_id: string; @@ -361,11 +376,9 @@ class CatalogService implements OnModuleInit { }; asset = await this.getAssetsUsersAndRoles([asset], customer_id); - return { data_asset: asset[0] }; } - async getOneDataAssetByPipelineAndObject(data: { customer_id: string; pipeline: string; @@ -387,11 +400,9 @@ class CatalogService implements OnModuleInit { }; asset = await this.getAssetsUsersAndRoles([asset], customer_id); - return { data_asset: asset[0] }; } - async updateOneDataAsset(data: { data_asset_id: string; customer_id: string; @@ -400,7 +411,6 @@ class CatalogService implements OnModuleInit { }) { const { body, customer_id, data_asset_id, metadata } = data; - const { data_asset } = await lastValueFrom( this.catalogWriteService.UpdateDataAsset( { id: data_asset_id, changes: JSON.stringify(body) }, @@ -415,7 +425,6 @@ class CatalogService implements OnModuleInit { }; asset = await this.getAssetsUsersAndRoles([asset], customer_id); - return { data_asset: asset[0] }; } @@ -423,12 +432,11 @@ class CatalogService implements OnModuleInit { const { documentation } = await lastValueFrom( this.catalogReadService.GetDatasetDoc({ id }, metadata), ); - + const docs = JSON.parse(documentation); return docs; } - async getDatasetPreview(id: string, metadata: Metadata) { const { preview } = await lastValueFrom( this.catalogReadService.GetDatasetPreview( @@ -440,7 +448,6 @@ class CatalogService implements OnModuleInit { return result; } - async getDatasetColumnsMetadata(id: string, metadata: Metadata) { const { columns_metadata } = await lastValueFrom( this.catalogReadService.GetDatasetColumnsMetadata( @@ -456,7 +463,7 @@ class CatalogService implements OnModuleInit { if (body.asset_type === 'table' || body.asset_type === 'view') { return this.createDataDocsViaNimbus(body); } - + return this.createDataDocsViaGrpc(body, metadata); } @@ -470,7 +477,6 @@ class CatalogService implements OnModuleInit { return data; } - private async createDataDocsViaGrpc(body: CreateDataDocsDTO, metadata: Metadata) { this.logger.info('Creating data docs via gRPC for other asset types'); try { @@ -483,9 +489,8 @@ class CatalogService implements OnModuleInit { metadata, ), ); - + return response; - } catch (error) { this.logger.error('Error creating data asset docs:', error); throw new HttpException( @@ -498,7 +503,6 @@ class CatalogService implements OnModuleInit { async findAllTags(data, metadata) { this.logger.info('CatalogService - findAllCustomerTags'); - const response = await lastValueFrom( this.catalogReadService.GetCustomerTags(data, metadata), ) @@ -511,9 +515,9 @@ class CatalogService implements OnModuleInit { throw new Error(err); }); - return response; } + async getAssetsUsersAndRoles(data_assets: Array, customer_id: string) { const { users: customer_users } = await this.userService.findAllUsersByCustomerId(customer_id); @@ -526,17 +530,15 @@ class CatalogService implements OnModuleInit { (u) => u.id === data_asset.owner, )?.email; - const roles = []; const users = []; - const data_asset_roles = data_asset?.roles || [] + const data_asset_roles = data_asset?.roles || []; for (const role_id of data_asset_roles) { const role = customer_roles.find((r) => r.id === role_id); if (role) roles.push({ id: role.id, name: role.name }); } - - const data_asset_users = data_asset?.users || [] + const data_asset_users = data_asset?.users || []; for (const user_id of data_asset_users) { const user = customer_users.find((r) => r.id === user_id); if (user) users.push({ id: user.id, email: user.email }); @@ -550,7 +552,6 @@ class CatalogService implements OnModuleInit { }); } - async triggerCatalog(data: TriggerCatalogReq, metadata: Metadata) { const { session } = await lastValueFrom( this.catalogWriteService.TriggerDatasetCataloging(data, metadata), @@ -564,7 +565,6 @@ class CatalogService implements OnModuleInit { return res; } - async addRlsRule(data: AddRlsRuleRequest, metadata: Metadata) { const res = await lastValueFrom( this.catalogWriteService.AddRlsRule(data, metadata), @@ -572,7 +572,6 @@ class CatalogService implements OnModuleInit { return res; } - async removeRlsRule(id: number, metadata: Metadata) { const res = await lastValueFrom( this.catalogWriteService.RemoveRlsRule({ id }, metadata), @@ -580,14 +579,12 @@ class CatalogService implements OnModuleInit { return res; } - async batchRemoveRlsRule( query: BatchRemoveRlsRulesRequest, metadata: Metadata, ) { const { id_rls, nimbus_dashboard_id } = query; - if (id_rls && nimbus_dashboard_id) { throw new BadRequestException( "You can't delete using both parameters. Choose either 'id_rls' or 'nimbus_dashboard_id'", @@ -608,7 +605,6 @@ class CatalogService implements OnModuleInit { return 'OK'; } - async getRlsRules(data: GetRlsRulesRequest, metadata: Metadata) { const res = await lastValueFrom( this.catalogReadService.GetRlsRules(data, metadata), @@ -616,7 +612,6 @@ class CatalogService implements OnModuleInit { return res.rls_rules; } - async getOneRlsRule(id: number, metadata: Metadata) { const res = await lastValueFrom( this.catalogReadService.GetOneRlsRule({ id }, metadata), @@ -624,7 +619,6 @@ class CatalogService implements OnModuleInit { return res.rls_rule; } - async getNimbusDashboards( data: GetNimbusDashboardsRequest, metadata: Metadata, @@ -635,88 +629,99 @@ class CatalogService implements OnModuleInit { return res.dashboards; } - async createTableMetadata(body: any): Promise { const nimbusUrl = this._getNimbusUrl(body); - this.logger.info(`Nimbus URL: ${nimbusUrl}`, {...body.logMetadata}); - + this.logger.info(`Nimbus URL: ${nimbusUrl}`, { ...body.logMetadata }); const endpoint = `${nimbusUrl}/api/catalog/table-metadata/`; - - this.logger.info(`Creating table metadata for table ${body.table_metadata.table_name}`, {...body.logMetadata}); - this.logger.info(`Using endpoint: ${endpoint}`, {...body.logMetadata}); - this.logger.debug(`Payload: ${JSON.stringify(body.table_metadata)}`, {...body.logMetadata}); - + this.logger.info( + `Creating table metadata for table ${body.table_metadata.table_name}`, + { ...body.logMetadata }, + ); + this.logger.info(`Using endpoint: ${endpoint}`, { ...body.logMetadata }); + this.logger.debug(`Payload: ${JSON.stringify(body.table_metadata)}`, { + ...body.logMetadata, + }); try { - const { data, status } = await axios.post(endpoint, {...body.table_metadata}); - + const { data, status } = await axios.post(endpoint, { + ...body.table_metadata, + }); this.logger.info( `Table metadata created successfully with status ${status} for table ${body.table_metadata.table_name}`, - {...body.logMetadata}, + { ...body.logMetadata }, ); return data.id; } catch (error) { this.logger.error( `Failed to create table metadata for table ${body.table_metadata.table_name} failed with status ${ error.response?.status - } because of ${JSON.stringify(error.response?.data) || error.message}`, {...body.logMetadata}); + } because of ${JSON.stringify(error.response?.data) || error.message}`, + { ...body.logMetadata }, + ); throw new Error(error.response?.data?.message || error.message); } } - async createColumnMetadata(body: any): Promise { const nimbusUrl = this._getNimbusUrl(body); this.logger.info(`Nimbus URL: ${nimbusUrl}`, body.logMetadata); const endpoint = `${nimbusUrl}/api/catalog/column-metadata/`; - - - - try { - this.logger.info(`Creating column metadata for table ${body.column_metadata.table_name}`, {...body.logMetadata}); - this.logger.info(`Using endpoint: ${endpoint}`, {...body.logMetadata}); - this.logger.debug(`Payload: ${JSON.stringify(body.column_metadata)}`, {...body.logMetadata}); - const { data, status } = await axios.post(endpoint, body.column_metadata); - + this.logger.info( + `Creating column metadata for table ${body.column_metadata.table_name}`, + { ...body.logMetadata }, + ); + this.logger.info(`Using endpoint: ${endpoint}`, { ...body.logMetadata }); + this.logger.debug( + `Payload: ${JSON.stringify(body.column_metadata)}`, + { ...body.logMetadata }, + ); + const { data, status } = await axios.post( + endpoint, + body.column_metadata, + ); this.logger.info( `Column metadata created successfully with status ${status} for table ${body.column_metadata.table_name}`, - {...body.logMetadata}, + { ...body.logMetadata }, ); return data.map((column) => column.id); } catch (error) { this.logger.error( `Failed to create column metadata failed with status for table ${body.column_metadata.table_name} ${ error.response?.status - } because of ${error.response?.data || error.message}`, {...body.logMetadata}); + } because of ${error.response?.data || error.message}`, + { ...body.logMetadata }, + ); throw new Error(error.response?.data?.message || error.message); } } - async createDataPreview(body: any): Promise { const nimbusUrl = this._getNimbusUrl(body); - this.logger.info(`Nimbus URL: ${nimbusUrl}`, {...body.logMetadata}); + this.logger.info(`Nimbus URL: ${nimbusUrl}`, { ...body.logMetadata }); const endpoint = `${nimbusUrl}/api/catalog/data-preview/`; - - this.logger.info(`Creating data preview for table ${body.data_preview.table_name}`, {...body.logMetadata}); - this.logger.info(`Using endpoint: ${endpoint}`, {...body.logMetadata}); - this.logger.debug(`Payload: ${JSON.stringify(body.data_preview)}`, {...body.logMetadata}); - + this.logger.info( + `Creating data preview for table ${body.data_preview.table_name}`, + { ...body.logMetadata }, + ); + this.logger.info(`Using endpoint: ${endpoint}`, { ...body.logMetadata }); + this.logger.debug( + `Payload: ${JSON.stringify(body.data_preview)}`, + { ...body.logMetadata }, + ); try { const { data, status } = await axios.post(endpoint, body.data_preview); - this.logger.info( `Data preview created successfully with status ${status} for table ${body.data_preview.table_name}`, - {...body.logMetadata}, + { ...body.logMetadata }, ); return data.id; } catch (error) { @@ -724,17 +729,15 @@ class CatalogService implements OnModuleInit { `Failed to create data preview for table ${body.data_preview.table_name} failed with status ${ error.response?.status } because of ${error.response?.data || error.message}`, - {...body.logMetadata}, + { ...body.logMetadata }, ); throw new Error(error.response?.data?.message || error.message); } } - async catalogDatasetItem(table_metadata_id: number, metadata: Metadata) { const customer_name_raw = metadata.get('customer_name'); - const customer_name = customer_name_raw?.[0]?.toString(); if (!customer_name) { throw new BadRequestException('Customer name not found in metadata'); @@ -757,5 +760,4 @@ class CatalogService implements OnModuleInit { } } - export { CatalogService }; From ea44a1cbb681117940acfcfba8bc311c6837e078 Mon Sep 17 00:00:00 2001 From: Rafael Moraes Date: Tue, 16 Dec 2025 20:52:28 +0000 Subject: [PATCH 28/41] FEAT: Manual/Auto filter and UX Adjusments --- src/modules/catalog/catalog.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/modules/catalog/catalog.service.ts b/src/modules/catalog/catalog.service.ts index 7660cd6..89dcf84 100644 --- a/src/modules/catalog/catalog.service.ts +++ b/src/modules/catalog/catalog.service.ts @@ -223,7 +223,6 @@ class CatalogService implements OnModuleInit { filters, }); - // 🔥 AJUSTE DO manually AQUI: converte '1'/'0' string em número ou remove if ( filters.manually !== undefined && filters.manually !== null && From 2cc8f46418fdb4630da01acc2ec60d3faef17e0b Mon Sep 17 00:00:00 2001 From: Rafael Date: Tue, 16 Dec 2025 18:10:38 -0300 Subject: [PATCH 29/41] feat: add Elasticsearch access to Maestro deployment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add ELASTICSEARCH_URL and ELASTICSEARCH_API_KEY env vars - Pull credentials from {env}/microservices/elasticsearch secret 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- deploy/helm-chart/templates/deployment.yaml | 11 +++++++++++ deploy/helm-chart/templates/secret.yaml | 12 ++++++++++++ 2 files changed, 23 insertions(+) diff --git a/deploy/helm-chart/templates/deployment.yaml b/deploy/helm-chart/templates/deployment.yaml index 8614986..7312b30 100644 --- a/deploy/helm-chart/templates/deployment.yaml +++ b/deploy/helm-chart/templates/deployment.yaml @@ -128,3 +128,14 @@ spec: secretKeyRef: name: prd-{{ .Values.app_name }} key: AWS_DEFAULT_REGION + # Elasticsearch + - name: ELASTICSEARCH_URL + valueFrom: + secretKeyRef: + name: prd-{{ .Values.app_name }} + key: ELASTICSEARCH_URL + - name: ELASTICSEARCH_API_KEY + valueFrom: + secretKeyRef: + name: prd-{{ .Values.app_name }} + key: ELASTICSEARCH_API_KEY diff --git a/deploy/helm-chart/templates/secret.yaml b/deploy/helm-chart/templates/secret.yaml index 3bbf5ec..10ea9b7 100644 --- a/deploy/helm-chart/templates/secret.yaml +++ b/deploy/helm-chart/templates/secret.yaml @@ -38,3 +38,15 @@ spec: version: "AWSCURRENT" property: token + - secretKey: ELASTICSEARCH_URL + remoteRef: + key: {{ .Values.maestro.env }}/microservices/elasticsearch + version: "AWSCURRENT" + property: ELASTICSEARCH_URL + + - secretKey: ELASTICSEARCH_API_KEY + remoteRef: + key: {{ .Values.maestro.env }}/microservices/elasticsearch + version: "AWSCURRENT" + property: ELASTICSEARCH_API_KEY + From e03b9e7a140819e0113476ff2b4fd4c27fe2ac13 Mon Sep 17 00:00:00 2001 From: Rafael Date: Tue, 16 Dec 2025 18:23:19 -0300 Subject: [PATCH 30/41] UPDATE: force deployment --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 4d4a5b1..bb50d3a 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@

- # Maestro Maestro é a API principal da Dadosfera. É responsável pela comunicação do Frontend com nossos microsserviços. From e616061c211f7afb57de5e11fb053d2668234e3d Mon Sep 17 00:00:00 2001 From: Rafael Date: Tue, 16 Dec 2025 19:10:23 -0300 Subject: [PATCH 31/41] fix: normalize IDs in pipeline run routes before calling Platform API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add normalization for pipelineId and runId in getPipelineRuns, getPipelineRun, and getPipelineRunLogs - Remove unused createPipelineRun route 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../platform-api/platform-api.controller.ts | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/modules/platform-api/platform-api.controller.ts b/src/modules/platform-api/platform-api.controller.ts index 3be5530..1f1fc98 100644 --- a/src/modules/platform-api/platform-api.controller.ts +++ b/src/modules/platform-api/platform-api.controller.ts @@ -691,18 +691,6 @@ export class PlatformApiController { // ==================== PIPELINE RUN ROUTES ==================== - @Post('pipeline/pipeline_run') - @ApiOperation({ summary: 'Create a pipeline run' }) - @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE) - async createPipelineRun(@Body() body: any, @User() user: RequestUser) { - return this.platformApiService.proxy( - 'POST', - '/pipeline/pipeline_run', - user, - body, - ); - } - @Get('pipeline/:pipelineId/pipeline_run') @ApiOperation({ summary: 'Get pipeline runs for a pipeline' }) @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.GET) @@ -711,9 +699,10 @@ export class PlatformApiController { @User() user: RequestUser, @Query() query: Record, ) { + const normalizedId = this.normalizePipelineId(pipelineId); return this.platformApiService.proxy( 'GET', - `/pipeline/${pipelineId}/pipeline_run`, + `/pipeline/${normalizedId}/pipeline_run`, user, undefined, query, @@ -728,9 +717,11 @@ export class PlatformApiController { @Param('runId') runId: string, @User() user: RequestUser, ) { + const normalizedPipelineId = this.normalizePipelineId(pipelineId); + const normalizedRunId = this.normalizePipelineId(runId); return this.platformApiService.proxy( 'GET', - `/pipeline/${pipelineId}/pipeline_run/${runId}`, + `/pipeline/${normalizedPipelineId}/pipeline_run/${normalizedRunId}`, user, ); } @@ -743,9 +734,10 @@ export class PlatformApiController { @User() user: RequestUser, @Query() query: Record, ) { + const normalizedRunId = this.normalizePipelineId(runId); return this.platformApiService.proxy( 'GET', - `/pipeline/pipeline_run/${runId}/logs`, + `/pipeline/pipeline_run/${normalizedRunId}/logs`, user, undefined, query, From acb631e33dc342e4c6dce4a2fa08628f0196a69b Mon Sep 17 00:00:00 2001 From: Rafael Date: Tue, 16 Dec 2025 19:16:35 -0300 Subject: [PATCH 32/41] UPDATE: force deployment --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index bb50d3a..028ca81 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@

- # Maestro Maestro é a API principal da Dadosfera. É responsável pela comunicação do Frontend com nossos microsserviços. From 865140e6819ecc1c445b26f747114df4961d8a47 Mon Sep 17 00:00:00 2001 From: Rafael Date: Tue, 16 Dec 2025 21:05:21 -0300 Subject: [PATCH 33/41] fix: pass type field to Elasticsearch createPipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The type field was missing from the createPipeline call, causing ES documents to not have the type field set. Maps: jdbc->database, singer->application, s3->file 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/modules/platform-api/platform-api.controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/platform-api/platform-api.controller.ts b/src/modules/platform-api/platform-api.controller.ts index 1f1fc98..383647c 100644 --- a/src/modules/platform-api/platform-api.controller.ts +++ b/src/modules/platform-api/platform-api.controller.ts @@ -490,6 +490,7 @@ export class PlatformApiController { cron: body.cron, tables: inputId, properties, + type: this.mapConnectorToDynamoType(connectorType), }, connector, ); From fb521f53cda74f410e0ed404099b8eb85e2613fd Mon Sep 17 00:00:00 2001 From: Rafael Date: Tue, 16 Dec 2025 21:05:21 -0300 Subject: [PATCH 34/41] FIX: pass type field to Elasticsearch createPipeline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The type field was missing from the createPipeline call, causing ES documents to not have the type field set. Maps: jdbc->database, singer->application, s3->file 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- src/modules/platform-api/platform-api.controller.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/modules/platform-api/platform-api.controller.ts b/src/modules/platform-api/platform-api.controller.ts index 1f1fc98..383647c 100644 --- a/src/modules/platform-api/platform-api.controller.ts +++ b/src/modules/platform-api/platform-api.controller.ts @@ -490,6 +490,7 @@ export class PlatformApiController { cron: body.cron, tables: inputId, properties, + type: this.mapConnectorToDynamoType(connectorType), }, connector, ); From e99306adbaf1ce303802e5f4cdf912ead41467fc Mon Sep 17 00:00:00 2001 From: Rafael Date: Wed, 17 Dec 2025 13:45:18 -0300 Subject: [PATCH 35/41] fix: store reference_column as object with name and type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The protobuf definition expects reference_column to be an object with name and type fields, but it was being stored as just a string (column name). This caused pipeline fetching to fail with the error: ".NewTable.reference_column: object expected" Changes: - Update ReferenceColumn interface in DynamoDB service - Update extractTablesFromJobs to create reference_column object - Update syncJobInputToDynamoDB to handle reference_column object 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../platform-api/platform-api.controller.ts | 38 ++++++++++++------- src/services/dynamodb/dynamodb.service.ts | 11 ++++-- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/modules/platform-api/platform-api.controller.ts b/src/modules/platform-api/platform-api.controller.ts index 383647c..78b273e 100644 --- a/src/modules/platform-api/platform-api.controller.ts +++ b/src/modules/platform-api/platform-api.controller.ts @@ -22,7 +22,7 @@ import { User, RequestUser } from '../../decorators/user.decorator'; import { PlatformApiService } from './platform-api.service'; import { PERMISSIONS_GROUPS } from '../../authentication/permissions.enum'; import { ElasticsearchService } from '../../services/elasticsearch'; -import { DynamoDBService } from '../../services/dynamodb'; +import { DynamoDBService, ReferenceColumn } from '../../services/dynamodb'; import { CustomersService } from '../customers/customers.service'; import { validateCronAgainstScheduleLimit } from '../../utils/cron-validation'; @@ -155,7 +155,7 @@ export class PlatformApiController { * Extract and transform tables from jobs for DynamoDB input. * Maps connector-specific fields to a common table format. * - * - JDBC: load_type, table_name, column_include_list (columns), incremental_column_name (reference_column) + * - JDBC: load_type, table_name, column_include_list (columns), incremental_column_name/type (reference_column object) * - Singer: type maps replication_method (FULL_TABLE -> full_load, INCREMENTAL -> incremental), no columns * - S3: same mapping as Singer, no columns */ @@ -163,7 +163,7 @@ export class PlatformApiController { name: string; type: string; columns?: string[]; - reference_column?: string; + reference_column?: ReferenceColumn; }> { if (!jobs || jobs.length === 0) return []; @@ -171,7 +171,7 @@ export class PlatformApiController { name: string; type: string; columns?: string[]; - reference_column?: string; + reference_column?: ReferenceColumn; }> = []; for (const job of jobs) { @@ -179,12 +179,12 @@ export class PlatformApiController { if (!input) continue; if (connector === 'jdbc') { - // JDBC: table_name, load_type, column_include_list, incremental_column_name + // JDBC: table_name, load_type, column_include_list, incremental_column_name/type const table: { name: string; type: string; columns?: string[]; - reference_column?: string; + reference_column?: ReferenceColumn; } = { name: input.table_name || '', type: input.load_type || 'full_load', @@ -193,8 +193,11 @@ export class PlatformApiController { table.columns = input.column_include_list; } if (input.incremental_column_name) { - // reference_column is stored as a string (column name) - table.reference_column = input.incremental_column_name; + // reference_column is stored as an object with name and type + table.reference_column = { + name: input.incremental_column_name, + type: input.incremental_column_type || 'unknown', + }; } tables.push(table); } else if (connector === 'singer' || connector === 's3') { @@ -317,11 +320,11 @@ export class PlatformApiController { } // Build changes for DynamoDB table entry - // reference_column is stored as a string (column name), not an object + // reference_column is stored as an object with name and type const changes: { type?: string; columns?: string[]; - reference_column?: string | null; + reference_column?: ReferenceColumn | null; } = {}; @@ -332,8 +335,15 @@ export class PlatformApiController { changes.columns = body.column_include_list; } if ('incremental_column_name' in body) { - // reference_column is just the column name as a string - changes.reference_column = body.incremental_column_name || null; + // reference_column is stored as an object with name and type + if (body.incremental_column_name) { + changes.reference_column = { + name: body.incremental_column_name, + type: body.incremental_column_type || 'unknown', + }; + } else { + changes.reference_column = null; + } } // Update DynamoDB if there are changes @@ -466,11 +476,13 @@ export class PlatformApiController { }); } + const pipelineType = this.mapConnectorToDynamoType(connectorType); this.logger.info('Syncing pipeline to Elasticsearch', { customerName: user.customer_name, pipelineId: body.id, plugin, connector: connectorType, + type: pipelineType, properties, inputId, }); @@ -490,7 +502,7 @@ export class PlatformApiController { cron: body.cron, tables: inputId, properties, - type: this.mapConnectorToDynamoType(connectorType), + type: pipelineType, }, connector, ); diff --git a/src/services/dynamodb/dynamodb.service.ts b/src/services/dynamodb/dynamodb.service.ts index 099eb99..15dde41 100644 --- a/src/services/dynamodb/dynamodb.service.ts +++ b/src/services/dynamodb/dynamodb.service.ts @@ -11,6 +11,11 @@ import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { v4 as uuid } from 'uuid'; import { DYNAMODB_CONFIG } from './dynamodb.config'; +export interface ReferenceColumn { + name: string; + type: string; +} + export interface InputDocument { id: string; client_id: string; @@ -24,7 +29,7 @@ export interface InputDocument { name: string; type: string; columns?: string[]; - reference_column?: string; + reference_column?: ReferenceColumn; }>; credentials?: Record; } @@ -62,7 +67,7 @@ export class DynamoDBService { name: string; type: string; columns?: string[]; - reference_column?: string; + reference_column?: ReferenceColumn; }>; }, ): Promise { @@ -157,7 +162,7 @@ export class DynamoDBService { changes: { type?: string; columns?: string[]; - reference_column?: string | null; + reference_column?: ReferenceColumn | null; }, ): Promise { const dynamoTableName = DYNAMODB_CONFIG.inputsTable(); From 9c55c222309d401195cf75b746a9c28d3f13d36c Mon Sep 17 00:00:00 2001 From: Rafael Date: Wed, 17 Dec 2025 13:45:18 -0300 Subject: [PATCH 36/41] FIX: store reference_column as object with name and type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The protobuf definition expects reference_column to be an object with name and type fields, but it was being stored as just a string (column name). This caused pipeline fetching to fail with the error: ".NewTable.reference_column: object expected" Changes: - Update ReferenceColumn interface in DynamoDB service - Update extractTablesFromJobs to create reference_column object - Update syncJobInputToDynamoDB to handle reference_column object 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../platform-api/platform-api.controller.ts | 38 ++++++++++++------- src/services/dynamodb/dynamodb.service.ts | 11 ++++-- 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/modules/platform-api/platform-api.controller.ts b/src/modules/platform-api/platform-api.controller.ts index 383647c..78b273e 100644 --- a/src/modules/platform-api/platform-api.controller.ts +++ b/src/modules/platform-api/platform-api.controller.ts @@ -22,7 +22,7 @@ import { User, RequestUser } from '../../decorators/user.decorator'; import { PlatformApiService } from './platform-api.service'; import { PERMISSIONS_GROUPS } from '../../authentication/permissions.enum'; import { ElasticsearchService } from '../../services/elasticsearch'; -import { DynamoDBService } from '../../services/dynamodb'; +import { DynamoDBService, ReferenceColumn } from '../../services/dynamodb'; import { CustomersService } from '../customers/customers.service'; import { validateCronAgainstScheduleLimit } from '../../utils/cron-validation'; @@ -155,7 +155,7 @@ export class PlatformApiController { * Extract and transform tables from jobs for DynamoDB input. * Maps connector-specific fields to a common table format. * - * - JDBC: load_type, table_name, column_include_list (columns), incremental_column_name (reference_column) + * - JDBC: load_type, table_name, column_include_list (columns), incremental_column_name/type (reference_column object) * - Singer: type maps replication_method (FULL_TABLE -> full_load, INCREMENTAL -> incremental), no columns * - S3: same mapping as Singer, no columns */ @@ -163,7 +163,7 @@ export class PlatformApiController { name: string; type: string; columns?: string[]; - reference_column?: string; + reference_column?: ReferenceColumn; }> { if (!jobs || jobs.length === 0) return []; @@ -171,7 +171,7 @@ export class PlatformApiController { name: string; type: string; columns?: string[]; - reference_column?: string; + reference_column?: ReferenceColumn; }> = []; for (const job of jobs) { @@ -179,12 +179,12 @@ export class PlatformApiController { if (!input) continue; if (connector === 'jdbc') { - // JDBC: table_name, load_type, column_include_list, incremental_column_name + // JDBC: table_name, load_type, column_include_list, incremental_column_name/type const table: { name: string; type: string; columns?: string[]; - reference_column?: string; + reference_column?: ReferenceColumn; } = { name: input.table_name || '', type: input.load_type || 'full_load', @@ -193,8 +193,11 @@ export class PlatformApiController { table.columns = input.column_include_list; } if (input.incremental_column_name) { - // reference_column is stored as a string (column name) - table.reference_column = input.incremental_column_name; + // reference_column is stored as an object with name and type + table.reference_column = { + name: input.incremental_column_name, + type: input.incremental_column_type || 'unknown', + }; } tables.push(table); } else if (connector === 'singer' || connector === 's3') { @@ -317,11 +320,11 @@ export class PlatformApiController { } // Build changes for DynamoDB table entry - // reference_column is stored as a string (column name), not an object + // reference_column is stored as an object with name and type const changes: { type?: string; columns?: string[]; - reference_column?: string | null; + reference_column?: ReferenceColumn | null; } = {}; @@ -332,8 +335,15 @@ export class PlatformApiController { changes.columns = body.column_include_list; } if ('incremental_column_name' in body) { - // reference_column is just the column name as a string - changes.reference_column = body.incremental_column_name || null; + // reference_column is stored as an object with name and type + if (body.incremental_column_name) { + changes.reference_column = { + name: body.incremental_column_name, + type: body.incremental_column_type || 'unknown', + }; + } else { + changes.reference_column = null; + } } // Update DynamoDB if there are changes @@ -466,11 +476,13 @@ export class PlatformApiController { }); } + const pipelineType = this.mapConnectorToDynamoType(connectorType); this.logger.info('Syncing pipeline to Elasticsearch', { customerName: user.customer_name, pipelineId: body.id, plugin, connector: connectorType, + type: pipelineType, properties, inputId, }); @@ -490,7 +502,7 @@ export class PlatformApiController { cron: body.cron, tables: inputId, properties, - type: this.mapConnectorToDynamoType(connectorType), + type: pipelineType, }, connector, ); diff --git a/src/services/dynamodb/dynamodb.service.ts b/src/services/dynamodb/dynamodb.service.ts index 099eb99..15dde41 100644 --- a/src/services/dynamodb/dynamodb.service.ts +++ b/src/services/dynamodb/dynamodb.service.ts @@ -11,6 +11,11 @@ import { DadosferaLogger } from '@dadosfera/dadosfera-logs'; import { v4 as uuid } from 'uuid'; import { DYNAMODB_CONFIG } from './dynamodb.config'; +export interface ReferenceColumn { + name: string; + type: string; +} + export interface InputDocument { id: string; client_id: string; @@ -24,7 +29,7 @@ export interface InputDocument { name: string; type: string; columns?: string[]; - reference_column?: string; + reference_column?: ReferenceColumn; }>; credentials?: Record; } @@ -62,7 +67,7 @@ export class DynamoDBService { name: string; type: string; columns?: string[]; - reference_column?: string; + reference_column?: ReferenceColumn; }>; }, ): Promise { @@ -157,7 +162,7 @@ export class DynamoDBService { changes: { type?: string; columns?: string[]; - reference_column?: string | null; + reference_column?: ReferenceColumn | null; }, ): Promise { const dynamoTableName = DYNAMODB_CONFIG.inputsTable(); From bce73fb11f3b03f65f06df953f25ebfb627502ed Mon Sep 17 00:00:00 2001 From: Marcos Rodrigues Date: Wed, 17 Dec 2025 17:24:03 -0300 Subject: [PATCH 37/41] FEAT: update user route --- docsfera.json | 36 +++++++--------------------- package-lock.json | 9 +++---- package.json | 4 ++-- src/modules/roles/roles.service.ts | 38 +++++++++++++++++------------- src/modules/users/dtos/entities.ts | 6 +++++ src/modules/users/users.service.ts | 15 +++++++----- 6 files changed, 53 insertions(+), 55 deletions(-) diff --git a/docsfera.json b/docsfera.json index f1b040e..672c167 100644 --- a/docsfera.json +++ b/docsfera.json @@ -7225,33 +7225,6 @@ ] } }, - "/platform/pipeline/pipeline_run": { - "post": { - "operationId": "PlatformApiController_createPipelineRun", - "summary": "Create a pipeline run", - "parameters": [], - "responses": { - "201": { - "description": "", - "content": { - "application/json": { - "schema": { - "type": "object" - } - } - } - } - }, - "tags": [ - "Platform API" - ], - "security": [ - { - "access-token": [] - } - ] - } - }, "/platform/pipeline/{pipelineId}/pipeline_run": { "get": { "operationId": "PlatformApiController_getPipelineRuns", @@ -8523,6 +8496,15 @@ "hierarchy": { "type": "string" }, + "bio": { + "type": "string" + }, + "companySite": { + "type": "string" + }, + "companyName": { + "type": "string" + }, "roleNames": { "type": "array", "items": { diff --git a/package-lock.json b/package-lock.json index 5248e39..002cc39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@aws-sdk/signature-v4": "^3.370.0", "@dadosfera/dadosfera-logs": "^1.0.0-beta.4", "@dadosfera/protospack": "2.5.3", - "@dadosfera/protospack-v2": "3.38.0-beta.16", + "@dadosfera/protospack-v2": "file:../protospack-v2/dadosfera-protospack-v2-0.0.0.tgz", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", @@ -2970,9 +2970,10 @@ } }, "node_modules/@dadosfera/protospack-v2": { - "version": "3.38.0-beta.16", - "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.38.0-beta.16.tgz", - "integrity": "sha512-ijURcmETXdgVgXhIPxapK7w0z6begXm4WDZ6EJtUGg+oYlJd9QOxbwaD7akRzW8XTdccmIo0Vov065KB812J0A==", + "version": "0.0.0", + "resolved": "file:../protospack-v2/dadosfera-protospack-v2-0.0.0.tgz", + "integrity": "sha512-ncpMpqOES0IqEnYMigS2lFIqCVSg3gsWENQzCeEaEkpnxidIHthBniJlUfr3ihPBSGg79OvGM5Cq7Yvyru66Ag==", + "license": "ISC", "dependencies": { "@grpc/grpc-js": "^1.9.3", "rxjs": "^7.5.5" diff --git a/package.json b/package.json index 4a5420d..6e51bcb 100644 --- a/package.json +++ b/package.json @@ -30,12 +30,12 @@ "@aws-crypto/sha256-js": "^5.2.0", "@aws-sdk/client-dynamodb": "^3.414.0", "@aws-sdk/client-secrets-manager": "^3.414.0", - "@aws-sdk/lib-dynamodb": "^3.414.0", "@aws-sdk/credential-provider-node": "^3.940.0", + "@aws-sdk/lib-dynamodb": "^3.414.0", "@aws-sdk/signature-v4": "^3.370.0", "@dadosfera/dadosfera-logs": "^1.0.0-beta.4", "@dadosfera/protospack": "2.5.3", - "@dadosfera/protospack-v2": "3.38.0-beta.16", + "@dadosfera/protospack-v2": "file:../protospack-v2/dadosfera-protospack-v2-0.0.0.tgz", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", diff --git a/src/modules/roles/roles.service.ts b/src/modules/roles/roles.service.ts index bd41ca5..d0045e5 100644 --- a/src/modules/roles/roles.service.ts +++ b/src/modules/roles/roles.service.ts @@ -229,27 +229,33 @@ export class RolesService { const [roleTreated] = this.getRolesPermissionsName([role.role]); return { role: roleTreated }; } + getRolesPermissionsName(roles: GetRolesPermissionsName[]): RoleDto[] { const newRoles: RoleDto[] = []; for (const role of roles) { - const allPermissions = this.permissionsService.getAllPermissions( - this.language, - ); - const newPermissions = role.permissions.map((p) => { - const permission = allPermissions.find((per) => per.seqid === p.seqid); - return { - ...p, - name: permission.name, - id: p.seqid, - }; - }); - const newRole: RoleDto = { - ...role, - permissions: newPermissions, - isPublic: role.isPublic, - }; + const newRole: RoleDto = this.formatRole(role); newRoles.push(newRole); } return newRoles; } + + private formatRole(role: GetRolesPermissionsName): RoleDto { + const allPermissions = this.permissionsService.getAllPermissions( + this.language + ); + const newPermissions = role.permissions.map((p) => { + const permission = allPermissions.find((per) => per.seqid === p.seqid); + return { + ...p, + name: permission.name, + id: p.seqid, + }; + }); + + return { + ...role, + permissions: newPermissions, + isPublic: role.isPublic, + };; + } } diff --git a/src/modules/users/dtos/entities.ts b/src/modules/users/dtos/entities.ts index 8e8b852..ac35213 100644 --- a/src/modules/users/dtos/entities.ts +++ b/src/modules/users/dtos/entities.ts @@ -109,6 +109,12 @@ export class UpdateUserReq { @ApiPropertyOptional() hierarchy?: string; @ApiPropertyOptional() + bio?: string; + @ApiPropertyOptional() + companySite?: string; + @ApiPropertyOptional() + companyName?: string; + @ApiPropertyOptional() roleNames?: string[]; } diff --git a/src/modules/users/users.service.ts b/src/modules/users/users.service.ts index 3057010..065a768 100644 --- a/src/modules/users/users.service.ts +++ b/src/modules/users/users.service.ts @@ -148,20 +148,23 @@ export class UsersService implements OnModuleInit { } async updateUser(req: UpdateUserReq, id: string, customerId: string) { - const { department, hierarchy, jobTitle, name, roleNames, email } = req; + const { roleNames, ...updateUserDTO } = req; if (roleNames) { await this.setRoles({ roleNames, userId: id }, customerId); } const { user } = await lastValueFrom( this.usersClientService.UserUpdate({ - name, + department: updateUserDTO.department, + email: updateUserDTO.email, + hierarchy: updateUserDTO.hierarchy, + jobTitle: updateUserDTO.jobTitle, + name: updateUserDTO.name, + bio: updateUserDTO.bio, + companyName: updateUserDTO.companyName, + companySite: updateUserDTO.companySite, customerId, id, - department, - hierarchy, - jobTitle, - email, metabaseUserId: undefined, }), ); From 7f5d157739871680f2dec57a2c4fcd9eba3b34bf Mon Sep 17 00:00:00 2001 From: Marcos Rodrigues Date: Thu, 18 Dec 2025 09:52:11 -0300 Subject: [PATCH 38/41] FEAT: update protospack version --- package-lock.json | 76 ++++++++++++++++++++++++++--------------------- package.json | 2 +- 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/package-lock.json b/package-lock.json index 002cc39..7b0f852 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@aws-sdk/signature-v4": "^3.370.0", "@dadosfera/dadosfera-logs": "^1.0.0-beta.4", "@dadosfera/protospack": "2.5.3", - "@dadosfera/protospack-v2": "file:../protospack-v2/dadosfera-protospack-v2-0.0.0.tgz", + "@dadosfera/protospack-v2": "3.38.0-beta.17", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", @@ -377,6 +377,7 @@ "resolved": "https://registry.npmjs.org/@aws-sdk/client-dynamodb/-/client-dynamodb-3.946.0.tgz", "integrity": "sha512-sMmJRa2Y+jQ+qMzx7V3SBKsTSQzLBvSuVbMNM/YGSZkr849a4a6Frl4U8HfFj4O+o71dvQ6h5PP6vlc/fW672g==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", @@ -2412,6 +2413,7 @@ "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.2", @@ -2970,9 +2972,9 @@ } }, "node_modules/@dadosfera/protospack-v2": { - "version": "0.0.0", - "resolved": "file:../protospack-v2/dadosfera-protospack-v2-0.0.0.tgz", - "integrity": "sha512-ncpMpqOES0IqEnYMigS2lFIqCVSg3gsWENQzCeEaEkpnxidIHthBniJlUfr3ihPBSGg79OvGM5Cq7Yvyru66Ag==", + "version": "3.38.0-beta.17", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.38.0-beta.17.tgz", + "integrity": "sha512-3E6m0ZKy5QOfuuTig05RC7Kj+zrh1GqZlTp6aePECDTrIyLhp2GIrALqJuD3LEjT+LG6eA88X2b4iXfV17Qmkg==", "license": "ISC", "dependencies": { "@grpc/grpc-js": "^1.9.3", @@ -3822,6 +3824,7 @@ "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-9.4.3.tgz", "integrity": "sha512-Gd6D4IaYj01o14Bwv81ukidn4w3bPHCblMUq+SmUmWLyosK+XQmInCS09SbDDZyL8jy86PngtBLTdhJ2bXSUig==", "license": "MIT", + "peer": true, "dependencies": { "iterare": "1.2.1", "tslib": "2.5.3", @@ -3900,6 +3903,7 @@ "integrity": "sha512-Qi63+wi55Jh4sDyaj5Hhx2jOpKqT386aeo+VOKsxnd+Ql9VvkO/FjmuwBGUyzkJt29ENYc+P0Sx/k5LtstNpPQ==", "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "@nuxtjs/opencollective": "0.3.2", "fast-safe-stringify": "2.1.1", @@ -4037,6 +4041,7 @@ "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-9.4.3.tgz", "integrity": "sha512-FpdczWoRSC0zz2dNL9u2AQLXKXRVtq4HgHklAhbL59X0uy+mcxhlSThG7DHzDMkoSnuuHY8ojDVf7mDxk+GtCw==", "license": "MIT", + "peer": true, "dependencies": { "body-parser": "1.20.2", "cors": "2.8.5", @@ -4400,6 +4405,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -4559,6 +4565,7 @@ "resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.1.tgz", "integrity": "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==", "license": "MIT", + "peer": true, "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", @@ -5384,6 +5391,7 @@ "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -5464,6 +5472,7 @@ "integrity": "sha512-4dRxkS/AFX0c5XW6IPMNOydLn2tEhNhJV7DnYK+0bjoJZ+QTmfucBlihX7aoEsh/ocYtkLC73UbnBXBXIxsULA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "jest-diff": "^27.0.0", "pretty-format": "^27.0.0" @@ -5527,7 +5536,8 @@ "version": "16.18.126", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.126.tgz", "integrity": "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/@types/oauth": { "version": "0.9.6", @@ -5757,6 +5767,7 @@ "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", "dev": true, "license": "BSD-2-Clause", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "5.62.0", "@typescript-eslint/types": "5.62.0", @@ -6103,6 +6114,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -6199,6 +6211,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", @@ -6827,6 +6840,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -6933,6 +6947,7 @@ "resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-5.7.6.tgz", "integrity": "sha512-wBxnBHjDxF1RXpHCBD6HGvKER003Ts7IIm0CHpggliHzN1RZditb7rXoduE1rplc2DEFYKxhLKgFuchXMJje9w==", "license": "MIT", + "peer": true, "dependencies": { "eventemitter3": "^5.0.1", "lodash.clonedeep": "^4.5.0", @@ -7156,13 +7171,15 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/class-validator": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.1.tgz", "integrity": "sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ==", "license": "MIT", + "peer": true, "dependencies": { "@types/validator": "^13.11.8", "libphonenumber-js": "^1.10.53", @@ -7826,7 +7843,8 @@ "version": "0.0.1425554", "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1425554.tgz", "integrity": "sha512-uRfxR6Nlzdzt0ihVIkV+sLztKgs7rgquY/Mhcv1YNCWDh5IZgl5mnn2aeEnW5stYTE0wwiF4RYVz8eMEpV1SEw==", - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/dezalgo": { "version": "1.0.4", @@ -8103,7 +8121,6 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -8291,6 +8308,7 @@ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -8637,7 +8655,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -8684,7 +8701,6 @@ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "license": "MIT", - "peer": true, "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -8709,7 +8725,6 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -8719,7 +8734,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -8728,22 +8742,19 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/express/node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/express/node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "side-channel": "^1.0.6" }, @@ -8772,8 +8783,7 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/external-editor": { "version": "3.1.0", @@ -9008,7 +9018,6 @@ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "license": "MIT", - "peer": true, "dependencies": { "debug": "2.6.9", "encodeurl": "~2.0.0", @@ -9027,7 +9036,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -9036,8 +9044,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/find-up": { "version": "5.0.0", @@ -9831,6 +9838,7 @@ "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.6.1.tgz", "integrity": "sha512-UxC0Yv1Y4WRJiGQxQkP0hfdL0/5/6YvdfOOClRgJ0qppSarkhneSa6UvkMkms0AkdGimSH3Ikqm+6mkMmX7vGA==", "license": "MIT", + "peer": true, "dependencies": { "@ioredis/commands": "^1.1.1", "cluster-key-slot": "^1.1.0", @@ -10172,6 +10180,7 @@ "integrity": "sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@jest/core": "^27.5.1", "import-local": "^3.0.2", @@ -11461,7 +11470,6 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -12234,6 +12242,7 @@ "resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz", "integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==", "license": "MIT", + "peer": true, "dependencies": { "passport-strategy": "1.x.x", "pause": "0.0.1", @@ -12623,6 +12632,7 @@ "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, "license": "MIT", + "peer": true, "bin": { "prettier": "bin-prettier.js" }, @@ -13125,7 +13135,8 @@ "version": "0.1.14", "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/relative-microtime": { "version": "2.0.0", @@ -13312,6 +13323,7 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -13373,6 +13385,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -13416,7 +13429,6 @@ "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "license": "MIT", - "peer": true, "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -13441,7 +13453,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "license": "MIT", - "peer": true, "dependencies": { "ms": "2.0.0" } @@ -13450,15 +13461,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/send/node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -13467,8 +13476,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/serialize-javascript": { "version": "6.0.2", @@ -13484,7 +13492,6 @@ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "license": "MIT", - "peer": true, "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", @@ -14462,6 +14469,7 @@ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", @@ -14691,6 +14699,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -15018,7 +15027,6 @@ "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.0.tgz", "integrity": "sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==", "license": "MIT", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", diff --git a/package.json b/package.json index 6e51bcb..6bd76c9 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@aws-sdk/signature-v4": "^3.370.0", "@dadosfera/dadosfera-logs": "^1.0.0-beta.4", "@dadosfera/protospack": "2.5.3", - "@dadosfera/protospack-v2": "file:../protospack-v2/dadosfera-protospack-v2-0.0.0.tgz", + "@dadosfera/protospack-v2": "3.38.0-beta.17", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", From 61109f8ae9d58f31a90f4b91e4e6e13f8ddffd33 Mon Sep 17 00:00:00 2001 From: Marcos Rodrigues Date: Thu, 18 Dec 2025 10:24:29 -0300 Subject: [PATCH 39/41] FIX: send new user info --- docsfera.json | 36 ++++++++++++++++++++++++++++++ package-lock.json | 8 +++---- package.json | 2 +- src/modules/users/dtos/entities.ts | 6 +++++ 4 files changed, 47 insertions(+), 5 deletions(-) diff --git a/docsfera.json b/docsfera.json index 672c167..d79ce2d 100644 --- a/docsfera.json +++ b/docsfera.json @@ -8166,6 +8166,15 @@ "hierarchy": { "type": "string" }, + "bio": { + "type": "string" + }, + "companyName": { + "type": "string" + }, + "companySite": { + "type": "string" + }, "mfaStatus": { "type": "string" }, @@ -8196,6 +8205,9 @@ "jobTitle", "department", "hierarchy", + "bio", + "companyName", + "companySite", "mfaStatus", "createdAt", "updatedAt", @@ -8329,6 +8341,15 @@ "hierarchy": { "type": "string" }, + "bio": { + "type": "string" + }, + "companyName": { + "type": "string" + }, + "companySite": { + "type": "string" + }, "customer": { "$ref": "#/components/schemas/Customer" }, @@ -8356,6 +8377,9 @@ "jobTitle", "department", "hierarchy", + "bio", + "companyName", + "companySite", "mfaStatus", "createdAt", "updatedAt", @@ -8545,6 +8569,15 @@ "hierarchy": { "type": "string" }, + "bio": { + "type": "string" + }, + "companyName": { + "type": "string" + }, + "companySite": { + "type": "string" + }, "mfaStatus": { "type": "string" }, @@ -8568,6 +8601,9 @@ "jobTitle", "department", "hierarchy", + "bio", + "companyName", + "companySite", "mfaStatus", "createdAt", "updatedAt", diff --git a/package-lock.json b/package-lock.json index 7b0f852..6f67e12 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "@aws-sdk/signature-v4": "^3.370.0", "@dadosfera/dadosfera-logs": "^1.0.0-beta.4", "@dadosfera/protospack": "2.5.3", - "@dadosfera/protospack-v2": "3.38.0-beta.17", + "@dadosfera/protospack-v2": "3.38.0-beta.18", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", @@ -2972,9 +2972,9 @@ } }, "node_modules/@dadosfera/protospack-v2": { - "version": "3.38.0-beta.17", - "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.38.0-beta.17.tgz", - "integrity": "sha512-3E6m0ZKy5QOfuuTig05RC7Kj+zrh1GqZlTp6aePECDTrIyLhp2GIrALqJuD3LEjT+LG6eA88X2b4iXfV17Qmkg==", + "version": "3.38.0-beta.18", + "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.38.0-beta.18.tgz", + "integrity": "sha512-rD2UOKa0P17A6YXIpclXF7F9b86mhRNE7fImTrbFamOcHpGmXBBVU8U53mCfLzzOA+MdDZ4/1uDNurCyQCjsLQ==", "license": "ISC", "dependencies": { "@grpc/grpc-js": "^1.9.3", diff --git a/package.json b/package.json index 6bd76c9..0aaef32 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@aws-sdk/signature-v4": "^3.370.0", "@dadosfera/dadosfera-logs": "^1.0.0-beta.4", "@dadosfera/protospack": "2.5.3", - "@dadosfera/protospack-v2": "3.38.0-beta.17", + "@dadosfera/protospack-v2": "3.38.0-beta.18", "@grpc/grpc-js": "^1.9.3", "@grpc/proto-loader": "^0.7.9", "@nestjs/cli": "^9.5.0", diff --git a/src/modules/users/dtos/entities.ts b/src/modules/users/dtos/entities.ts index ac35213..0f0e5a3 100644 --- a/src/modules/users/dtos/entities.ts +++ b/src/modules/users/dtos/entities.ts @@ -38,6 +38,12 @@ export class User { department?: string; @ApiProperty() hierarchy?: string; + @ApiProperty() + bio?: string; + @ApiProperty() + companyName?: string; + @ApiProperty() + companySite?: string; @ApiPropertyOptional() customer?: Customer; @ApiProperty() From f61c241dde9eb102ca46819a992afc59b9b86ffd Mon Sep 17 00:00:00 2001 From: Rafael Date: Thu, 18 Dec 2025 10:30:09 -0300 Subject: [PATCH 40/41] FIX: inject customer_id in pipeline execute/pause/unpause routes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Users no longer need to provide customer_id in the request body for execute, pause, and unpause pipeline operations - it's now automatically injected from the authenticated user's session. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../platform-api/platform-api.controller.ts | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/modules/platform-api/platform-api.controller.ts b/src/modules/platform-api/platform-api.controller.ts index 78b273e..e0d59f2 100644 --- a/src/modules/platform-api/platform-api.controller.ts +++ b/src/modules/platform-api/platform-api.controller.ts @@ -630,21 +630,39 @@ export class PlatformApiController { @ApiOperation({ summary: 'Execute a pipeline' }) @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE) async executePipeline(@Body() body: any, @User() user: RequestUser) { - return this.platformApiService.proxy('POST', '/pipeline/execute', user, body); + // Inject customer_id (actually customer_name) into body for Platform-API + // Note: Platform-API was created before customer_id existed, so it expects customer_name in the customer_id field + const enrichedBody = { + ...body, + customer_id: user.customer_name, + }; + return this.platformApiService.proxy('POST', '/pipeline/execute', user, enrichedBody); } @Post('pipeline/pause') @ApiOperation({ summary: 'Pause a pipeline' }) @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE) async pausePipeline(@Body() body: any, @User() user: RequestUser) { - return this.platformApiService.proxy('POST', '/pipeline/pause', user, body); + // Inject customer_id (actually customer_name) into body for Platform-API + // Note: Platform-API was created before customer_id existed, so it expects customer_name in the customer_id field + const enrichedBody = { + ...body, + customer_id: user.customer_name, + }; + return this.platformApiService.proxy('POST', '/pipeline/pause', user, enrichedBody); } @Post('pipeline/unpause') @ApiOperation({ summary: 'Unpause a pipeline' }) @RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE) async unpausePipeline(@Body() body: any, @User() user: RequestUser) { - return this.platformApiService.proxy('POST', '/pipeline/unpause', user, body); + // Inject customer_id (actually customer_name) into body for Platform-API + // Note: Platform-API was created before customer_id existed, so it expects customer_name in the customer_id field + const enrichedBody = { + ...body, + customer_id: user.customer_name, + }; + return this.platformApiService.proxy('POST', '/pipeline/unpause', user, enrichedBody); } @Put('pipeline/:pipelineId/memory') From 8b93d4e97b5658be357116a3ebc0a84eaf3bc393 Mon Sep 17 00:00:00 2001 From: Marcos Rodrigues Date: Thu, 18 Dec 2025 12:32:53 -0300 Subject: [PATCH 41/41] FEAT: protected update route with admin seqid or if user request to equal a jwt user id claim --- docsfera.json | 3 --- src/modules/users/users.controller.ts | 5 ++++- src/modules/users/users.service.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docsfera.json b/docsfera.json index d79ce2d..68b2806 100644 --- a/docsfera.json +++ b/docsfera.json @@ -1321,9 +1321,6 @@ "Users" ], "security": [ - { - "access-token": [] - }, { "access-token": [] } diff --git a/src/modules/users/users.controller.ts b/src/modules/users/users.controller.ts index e565f18..b5cb32b 100644 --- a/src/modules/users/users.controller.ts +++ b/src/modules/users/users.controller.ts @@ -266,7 +266,6 @@ export class UsersController { } @Patch(':id') - @RequireAllPermissions(PERMISSIONS_GROUPS.USERS.permissions.ADMIN) @ApiOkResponse({ type: UpdateUserRes }) async updateUser( @User() user: RequestUser, @@ -274,6 +273,10 @@ export class UsersController { @Param('id') id: string, @Language() language: LanguageEnum, ) { + + if (user.user_id !== id || !user.permissions.includes(PERMISSIONS_GROUPS.USERS.permissions.ADMIN.seqid)) { + throw new ErrorBuilder(ErrorCodes.AUTH.FORBIDDEN); + } this.logger.info('updateUser', { user }); this.userService.setLanguage(language); return await this.userService.updateUser(body, id, user.customer_id); diff --git a/src/modules/users/users.service.ts b/src/modules/users/users.service.ts index 065a768..278188c 100644 --- a/src/modules/users/users.service.ts +++ b/src/modules/users/users.service.ts @@ -149,7 +149,7 @@ export class UsersService implements OnModuleInit { async updateUser(req: UpdateUserReq, id: string, customerId: string) { const { roleNames, ...updateUserDTO } = req; - if (roleNames) { + if (roleNames && roleNames.length > 0) { await this.setRoles({ roleNames, userId: id }, customerId); }