Compare commits

..
Author SHA1 Message Date
Rafael 10c4ed179b UPDATE: add toleration for spot instances 2025-01-22 11:24:01 -03:00
Rafael e55a1cb657 CI: removed deploy-k8s workflow 2025-01-20 18:47:44 -03:00
Rafael 3850d4b8ae UPDATE: blank commit to force deployment 2025-01-20 18:47:13 -03:00
Rafael 2a6677e4d9 UPDATE: decrease the number of replicas for stg environment 2025-01-20 18:41:01 -03:00
Marcos Rodrigues Silva 74e62c001d Merge pull request #239 from dadosfera/feature/white-label-app
FIX: env fixed and add logger for the process.env
2025-01-17 08:40:08 -03:00
Marcos Rodrigues Silva 41bf9d4856 Merge pull request #238 from dadosfera/feature/white-label-app
FIX: change env for stg
2025-01-16 18:47:21 -03:00
Marcos Rodrigues Silva 104787bce8 Merge pull request #237 from dadosfera/feature/white-label-app
Feature/white label app
2025-01-15 11:04:20 -03:00
Marcos Rodrigues Silva 7aadf4d9d1 Merge pull request #236 from dadosfera/feature/white-label-app
FIX: add header content type and change status code to 200
2025-01-09 08:40:59 -03:00
Marcos Rodrigues Silva c51f262474 Merge pull request #235 from dadosfera/feature/white-label-app
FIX:  param to language and message in json
2025-01-09 08:15:46 -03:00
Marcos Rodrigues Silva 66f33a2246 Merge pull request #234 from dadosfera/feature/white-label-app
FIX: remove language param
2025-01-08 18:15:41 -03:00
Marcos Rodrigues Silva 8b7342652e Merge pull request #233 from dadosfera/feature/white-label-app
FEAT: add language by query
2025-01-08 15:34:00 -03:00
Marcos Rodrigues Silva f7d0128d99 Merge pull request #232 from dadosfera/feature/white-label-app
FIX: remove origin decorator
2025-01-06 16:23:46 -03:00
Marcos Rodrigues Silva cf5e36db00 Merge pull request #231 from dadosfera/feature/white-label-app
Feature/white label app
2025-01-06 15:38:40 -03:00
Marcos Rodrigues Silva 56a063b644 Merge pull request #230 from dadosfera/feature/white-label-app
Feature/white label app
2024-12-26 16:01:47 -03:00
14 changed files with 107 additions and 168 deletions
-73
View File
@@ -1,73 +0,0 @@
name: Deploy K8S Modifications
on:
push:
branches:
- main
- beta
jobs:
extract_environment:
runs-on: ubuntu-22.04
outputs:
environment: ${{ steps.extract_environment.outputs.environment }}
steps:
- name: Extract Environment
run: |
if [ ${GITHUB_REF} == "refs/heads/main" ]; then
echo "environment=prd" >> $GITHUB_OUTPUT
elif [ ${GITHUB_REF} == "refs/heads/beta" ]; then
echo "environment=stg" >> $GITHUB_OUTPUT
fi
id: extract_environment
helmfile-deploy:
needs: [extract_environment]
runs-on: [self-hosted, "prd-azure"]
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Helm
uses: azure/setup-helm@v1
with:
version: 'v3.9.0'
- name: Install Azure ClI
run: |
curl -sL https://aka.ms/InstallAzureCLIDeb | bash
- uses: azure/login@v2
with:
creds: '{"clientId":"${{ secrets.ARM_CLIENT_ID }}","clientSecret":"${{ secrets.ARM_CLIENT_SECRET }}","subscriptionId":"${{ secrets.ARM_SUBSCRIPTION_ID }}","tenantId":"${{ secrets.ARM_TENANT_ID }}"}'
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.8'
- name: Install Helmfile
run: |
wget 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
mv helmfile /usr/local/bin/
helmfile --version
- name: Install Helm Diff Plugin
run: helm plugin install https://github.com/databus23/helm-diff || true
- name: Setup kubectl
uses: azure/setup-kubectl@v1
with:
version: 'v1.30.1'
- name: Authenticate with cluster
env:
CLUSTER_NAME: platform-${{ needs.extract_environment.outputs.environment }}
run: az aks get-credentials --resource-group dadosfera-prd --name ${CLUSTER_NAME} --overwrite-existing
- name: Run Helmfile Apply
env:
ENV: ${{ needs.extract_environment.outputs.environment }}
run: helmfile -f helmfiles/${ENV}.yaml sync
+1
View File
@@ -2,6 +2,7 @@
<image src="./assets/maestro.svg" style="width:10rem"> <image src="./assets/maestro.svg" style="width:10rem">
</p> </p>
# Maestro # Maestro
Maestro é a API principal da Dadosfera. É responsável pela comunicação do Frontend com nossos microsserviços. Maestro é a API principal da Dadosfera. É responsável pela comunicação do Frontend com nossos microsserviços.
+5 -9
View File
@@ -5619,9 +5619,6 @@
"items": { "items": {
"type": "string" "type": "string"
} }
},
"themeEnabled": {
"type": "boolean"
} }
}, },
"required": [ "required": [
@@ -5630,8 +5627,7 @@
"name", "name",
"tier", "tier",
"scheduleLimit", "scheduleLimit",
"links", "links"
"themeEnabled"
] ]
}, },
"AuthUser": { "AuthUser": {
@@ -8378,18 +8374,18 @@
"textColor": { "textColor": {
"type": "string" "type": "string"
}, },
"logo": { "logoWhite": {
"type": "string" "type": "string"
}, },
"logoLogin": { "logoBlack": {
"type": "string" "type": "string"
} }
}, },
"required": [ "required": [
"backgroundColor", "backgroundColor",
"textColor", "textColor",
"logo", "logoWhite",
"logoLogin" "logoBlack"
] ]
}, },
"CustomerThemeResponse": { "CustomerThemeResponse": {
+2 -1
View File
@@ -18,4 +18,5 @@ charts:
value: b3e3dfe5-b992-4586-a73c-c0b0c00f615d value: b3e3dfe5-b992-4586-a73c-c0b0c00f615d
- name: maestro.open_group_id - name: maestro.open_group_id
value: e3f98a2f-7748-4981-8505-7695c8ca8218 value: e3f98a2f-7748-4981-8505-7695c8ca8218
- name: replicaCount
value: 1
+5 -4
View File
@@ -36,6 +36,11 @@ spec:
operator: In operator: In
values: values:
- backend - backend
tolerations:
- key: "kubernetes.azure.com/scalesetpriority"
operator: "Equal"
value: "spot"
effect: "NoSchedule"
containers: containers:
- name: maestro - name: maestro
@@ -90,10 +95,6 @@ spec:
value: {{ .Values.maestro.tr_factory_url }} value: {{ .Values.maestro.tr_factory_url }}
- name: UPLOAD_FILE_AGENT_CONNECTION - name: UPLOAD_FILE_AGENT_CONNECTION
value: {{ .Values.maestro.upload_file_agent_connection }} value: {{ .Values.maestro.upload_file_agent_connection }}
- name: OPEN_CUSTOMER_ID
value: {{ .Values.maestro.open_customer_id }}
- name: OPEN_GROUP_ID
value: {{ .Values.maestro.open_group_id }}
- name: JWT_PRIVATE_KEY - name: JWT_PRIVATE_KEY
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
+4 -4
View File
@@ -12,7 +12,7 @@
"@aws-sdk/client-secrets-manager": "^3.414.0", "@aws-sdk/client-secrets-manager": "^3.414.0",
"@dadosfera/dadosfera-logs": "^1.0.0-beta.4", "@dadosfera/dadosfera-logs": "^1.0.0-beta.4",
"@dadosfera/protospack": "2.5.3", "@dadosfera/protospack": "2.5.3",
"@dadosfera/protospack-v2": "3.37.0-beta.5", "@dadosfera/protospack-v2": "3.37.0-beta.3",
"@grpc/grpc-js": "^1.9.3", "@grpc/grpc-js": "^1.9.3",
"@grpc/proto-loader": "^0.7.9", "@grpc/proto-loader": "^0.7.9",
"@nestjs/cli": "^9.5.0", "@nestjs/cli": "^9.5.0",
@@ -1403,9 +1403,9 @@
} }
}, },
"node_modules/@dadosfera/protospack-v2": { "node_modules/@dadosfera/protospack-v2": {
"version": "3.37.0-beta.5", "version": "3.37.0-beta.3",
"resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.37.0-beta.5.tgz", "resolved": "https://dadosfera-611330257153.d.codeartifact.us-east-1.amazonaws.com/npm/dadosfera-npm/@dadosfera/protospack-v2/-/protospack-v2-3.37.0-beta.3.tgz",
"integrity": "sha512-dtHMzMU2Qa9c6DpygWr7fib19l1zqbYgBi2wGkiJ6uMAvl1CGu9V0p+JA1FwGTQ0QRy7EkkRVi6X6sH5u/ICpA==", "integrity": "sha512-9YSgzkXVqe9QsBxJjfxYxttAiyCXxgq49Xjs3OvnTLCXRDn3XtTlPaC+yHrTEUgCkQyhlDZuswe6f6oBImqIRQ==",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@grpc/grpc-js": "^1.9.3", "@grpc/grpc-js": "^1.9.3",
+1 -1
View File
@@ -30,7 +30,7 @@
"@aws-sdk/client-secrets-manager": "^3.414.0", "@aws-sdk/client-secrets-manager": "^3.414.0",
"@dadosfera/dadosfera-logs": "^1.0.0-beta.4", "@dadosfera/dadosfera-logs": "^1.0.0-beta.4",
"@dadosfera/protospack": "2.5.3", "@dadosfera/protospack": "2.5.3",
"@dadosfera/protospack-v2": "3.37.0-beta.5", "@dadosfera/protospack-v2": "3.37.0-beta.3",
"@grpc/grpc-js": "^1.9.3", "@grpc/grpc-js": "^1.9.3",
"@grpc/proto-loader": "^0.7.9", "@grpc/proto-loader": "^0.7.9",
"@nestjs/cli": "^9.5.0", "@nestjs/cli": "^9.5.0",
+32
View File
@@ -0,0 +1,32 @@
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { SetMetadata } from '@nestjs/common';
export const SetOrigin = (origin: string) => SetMetadata('allowedOrigin', origin);
@Injectable()
export class CORSGuard implements CanActivate {
constructor(private reflector: Reflector) {}
canActivate(context: ExecutionContext): boolean {
const response = context.switchToHttp().getResponse();
const request = context.switchToHttp().getRequest();
const origin = request.headers.origin;
// Obter a origem permitida através do decorador
const allowedOrigin = this.reflector.get<string>('allowedOrigin', context.getHandler());
if (process.env.ENV === "local") {
return true;
}
// Verifica se a origem da requisição é permitida
if (allowedOrigin && origin !== allowedOrigin) {
throw new ForbiddenException('Acesso não permitido pela política CORS');
}
response.setHeader('Access-Control-Allow-Origin', allowedOrigin);
return true;
}
}
-2
View File
@@ -77,8 +77,6 @@ export class AuthCustomer {
scheduleLimit: string; scheduleLimit: string;
@ApiProperty() @ApiProperty()
links: Link[]; links: Link[];
@ApiProperty()
themeEnabled: boolean;
} }
export class AuthSignInReq implements AuthSignInRequest { export class AuthSignInReq implements AuthSignInRequest {
+22 -12
View File
@@ -1,5 +1,5 @@
import DadosferaLogger from '@dadosfera/dadosfera-logs'; import DadosferaLogger from '@dadosfera/dadosfera-logs';
import { Body, Controller, ForbiddenException, Header, HttpCode, Inject, Param, Post, Query, Req, UseFilters, UseGuards, UseInterceptors } from '@nestjs/common'; import { Body, Controller, Header, HttpCode, Inject, Param, Post, Query, Req, UseFilters, UseGuards } from '@nestjs/common';
import { ApiCreatedResponse, ApiHeaders, ApiOkResponse, ApiTags } from '@nestjs/swagger'; import { ApiCreatedResponse, ApiHeaders, ApiOkResponse, ApiTags } from '@nestjs/swagger';
import { ApiInternalOnlyController } from 'src/decorators/swagger.decorator'; import { ApiInternalOnlyController } from 'src/decorators/swagger.decorator';
import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter'; import { GrpcToHttpExceptionFilter } from 'src/error/grpc-to-http-exception.filter';
@@ -8,6 +8,7 @@ import { UsersService } from '../users/users.service';
import { Language } from 'src/decorators/language.decorator'; import { Language } from 'src/decorators/language.decorator';
import { OpenDataService } from './open-data.service'; import { OpenDataService } from './open-data.service';
import { CreateUserOpenDataDTO, WordpressForm } from './dto/wordpres-form'; import { CreateUserOpenDataDTO, WordpressForm } from './dto/wordpres-form';
import { CORSGuard, SetOrigin } from 'src/decorators/set-origin.decorator';
import { Metadata } from '@grpc/grpc-js'; import { Metadata } from '@grpc/grpc-js';
import { PackTheMetadata } from 'src/utils/ PackTheMetadata'; import { PackTheMetadata } from 'src/utils/ PackTheMetadata';
import { request } from 'http'; import { request } from 'http';
@@ -30,6 +31,8 @@ export class OpenDataController {
} }
@Post("/sharing-ocean-data") @Post("/sharing-ocean-data")
// @SetOrigin('devsbm.dadosfera.io')
// @UseGuards(CORSGuard)
@HttpCode(200) @HttpCode(200)
@Header('content-type', 'application/json') @Header('content-type', 'application/json')
@ApiOkResponse() @ApiOkResponse()
@@ -41,16 +44,22 @@ export class OpenDataController {
@Req() @Req()
request: Request, request: Request,
) { ) {
if (!request.headers['user-agent'].includes('WordPress/6.7.1; https://devsbm.dadosfera.io')) { this.logger.info('createUser for open data'
throw new ForbiddenException(); + JSON.stringify({
} origin: request.headers.origin,
this.logger.info('createUser for open data'); language,
body
}));
this.logger.info('user-agent', request.headers['user-agent']); // "401573bb-334f-44b2-b30e-88d4cea31ae9"
// const OPENDATA_PUBLIC_USERS_GROUP_ID = process.env.OPEN_GROUP_ID;
// ""f239718a-a271-4ef9-ae7e-02a2f0f3aa6e""
// const OPENDATA_CUSTOMER_ID = process.env.OPEN_CUSTOMER_ID;
const OPENDATA_CUSTOMER_ID = "b3e3dfe5-b992-4586-a73c-c0b0c00f615d";
const OPENDATA_CUSTOMER_ID = process.env.OPEN_CUSTOMER_ID; this.logger.info("OPENDATA_CUSTOMER_ID: " + process.env.OPEN_CUSTOMER_ID)
const OPENDATA_GROUP_ID = process.env.OPEN_GROUP_ID; this.logger.info("OPEN_GROUP_ID: " + process.env.OPEN_GROUP_ID)
const roles = [process.env.OPEN_GROUP_ID]; const roles = ["e3f98a2f-7748-4981-8505-7695c8ca8218"];
const metadata = PackTheMetadata({ const metadata = PackTheMetadata({
language: language || 'en-us' language: language || 'en-us'
}); });
@@ -68,6 +77,7 @@ export class OpenDataController {
this.logger.error('user data ' + e.message); this.logger.error('user data ' + e.message);
} }
const user: CreateUserOpenDataDTO = { const user: CreateUserOpenDataDTO = {
email: data["email"], email: data["email"],
enquiryType: data["enquiry_type"], enquiryType: data["enquiry_type"],
@@ -75,11 +85,11 @@ export class OpenDataController {
lastName: data["last_name"], lastName: data["last_name"],
organization: data["organization"] organization: data["organization"]
} }
this.logger.info(`user request to group ${OPENDATA_CUSTOMER_ID} with role ${OPENDATA_GROUP_ID}`); this.logger.info('user request' + JSON.stringify({ user, roles, customer: OPENDATA_CUSTOMER_ID }));
try { try {
const id = await this.openDataService.createUser(OPENDATA_CUSTOMER_ID, user, roles, metadata); await this.openDataService.createUser(OPENDATA_CUSTOMER_ID, user, roles, metadata);
this.logger.info('user created with id: '+ id); this.logger.info('user created with sucessfull data');
return { return {
success: true, success: true,
status: 'success', status: 'success',
+2 -2
View File
@@ -40,10 +40,10 @@ export class OpenDataService implements OnModuleInit {
} }
try { try {
const { user } = await lastValueFrom( await lastValueFrom(
this.usersClientService.SimpleUserCreate(body, metadata), this.usersClientService.SimpleUserCreate(body, metadata),
); );
return user.id; return "User created";
} catch(err) { } catch(err) {
return err; return err;
} }
+2 -2
View File
@@ -27,9 +27,9 @@ export class CustomerTheme implements Theme {
@ApiProperty() @ApiProperty()
textColor: string; textColor: string;
@ApiProperty() @ApiProperty()
logo: string; logoWhite: string;
@ApiProperty() @ApiProperty()
logoLogin: string; logoBlack: string;
} }
export class CustomerThemeResponse { export class CustomerThemeResponse {
+13 -15
View File
@@ -59,22 +59,22 @@ export class ThemeController {
id, id,
})); }));
const logo = files.find(file => file.fieldname === 'logo'); const logoWhite = files.find(file => file.fieldname === 'logoWhite');
const logoLogin = files.find(file => file.fieldname === 'logoLogin'); const logoBlack = files.find(file => file.fieldname === 'logoBlack');
this.validFileSize(logo); this.validFileSize(logoWhite);
this.validFileSize(logoLogin); this.validFileSize(logoBlack);
this.validMimeType(logo); this.validMimeType(logoWhite);
this.validMimeType(logoLogin); this.validMimeType(logoBlack);
try { try {
const theme = await this.themeService.createThemeByCustomer(id, { const theme = await this.themeService.createThemeByCustomer(id, {
...data, ...data,
logo, logoWhite,
logoLogin logoBlack
}); });
this.logger.info('saveCustomertheme' + JSON.stringify(theme)); this.logger.info('saveCustomertheme' + JSON.stringify(theme));
return theme; return { theme: theme };
} catch (err) { } catch (err) {
if (err.details === ErrorCodes.CUSTOMER.NOT_FOUND) { if (err.details === ErrorCodes.CUSTOMER.NOT_FOUND) {
this.logger.error('Error - saveCustomertheme - Expect CUSTOMER.NOT_FOUND'); this.logger.error('Error - saveCustomertheme - Expect CUSTOMER.NOT_FOUND');
@@ -95,10 +95,10 @@ export class ThemeController {
} }
private validMimeType(file: Express.Multer.File) { private validMimeType(file: Express.Multer.File) {
const mimeTypesValid = ['image/jpeg', 'image/jpg', 'image/png', 'image/svg+xml']; const mimeTypesValid = ['image/jpeg', 'image/jpg', 'image/png'];
if (file && !mimeTypesValid.includes(file.mimetype)) { if (file && !mimeTypesValid.includes(file.mimetype)) {
throw new HttpException(`O Arquivo ${file.fieldname} deve ser jpeg, jpg, png ou svg`, HttpStatus.BAD_REQUEST); throw new HttpException(`O Arquivo ${file.fieldname} deve ser jpeg, jpg ou png`, HttpStatus.BAD_REQUEST);
} }
} }
@@ -109,10 +109,8 @@ export class ThemeController {
try { try {
const data = await this.themeService.getThemeByCustomer(id); const data = await this.themeService.getThemeByCustomer(id);
this.logger.info('Success - getCustomerTheme'+ JSON.stringify(data)); this.logger.info('Success - getCustomerTheme with id'+ id);
if (data?.theme) return data; return data;
return { theme: null };
}catch (err) { }catch (err) {
if (err.details === ErrorCodes.CUSTOMER.NOT_FOUND) { if (err.details === ErrorCodes.CUSTOMER.NOT_FOUND) {
this.logger.error('Error - getCustomerTheme - Expect CUSTOMER.NOT_FOUND'); this.logger.error('Error - getCustomerTheme - Expect CUSTOMER.NOT_FOUND');
+18 -43
View File
@@ -21,10 +21,11 @@ import { resolve } from 'path';
import { Readable } from 'stream'; import { Readable } from 'stream';
type Files = { type Files = {
logo: Express.Multer.File, logoWhite: Express.Multer.File,
logoLogin: Express.Multer.File, logoBlack: Express.Multer.File,
} }
// This function will accept any string, which may result in a bug.
@Injectable() @Injectable()
export class ThemeService implements OnModuleInit { export class ThemeService implements OnModuleInit {
private themeService: ThemeProtoService; private themeService: ThemeProtoService;
@@ -62,12 +63,12 @@ export class ThemeService implements OnModuleInit {
chunk: Buffer.alloc(0) chunk: Buffer.alloc(0)
}) })
if(theme.logo) { if(theme.logoBlack) {
await this.sendFile(theme.logo, customerThemeRequest$); await this.sendFile(theme.logoBlack, customerThemeRequest$);
} }
if(theme.logoLogin) { if(theme.logoWhite) {
await this.sendFile(theme.logoLogin, customerThemeRequest$); await this.sendFile(theme.logoWhite, customerThemeRequest$);
} }
customerThemeRequest$.complete(); customerThemeRequest$.complete();
@@ -94,20 +95,18 @@ export class ThemeService implements OnModuleInit {
} }
private async sendFile(file: Express.Multer.File, stream$: ReplaySubject<ThemeRequest>) { private async sendFile(file: Express.Multer.File, stream$: ReplaySubject<ThemeRequest>) {
const chunkSize = 4 * 1024 * 1024; const bufferStream = new Readable({
const bufferStream = new CustomBufferStream(file.buffer, chunkSize); highWaterMark: 1024 * 1024, // 1 MB por chunk
const parseMimitypeForExtension = { read() {}
'image/jpeg': '.jpeg', });
'image/jpg': '.jpg', bufferStream.push(file.buffer);
'image/png': '.png', bufferStream.push(null);
'image/svg+xml': '.svg',
}
const extension = parseMimitypeForExtension[file.mimetype];
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
bufferStream.on('data', (chunk) => { bufferStream.on('data', (chunk) => {
const filename = file.fieldname.concat(extension); console.log('enviando chunk', file.fieldname)
const mimetype = file.mimetype.split('/')[1]; // example image/jpeg
const filename = file.fieldname.concat(".", mimetype);
stream$.next({ stream$.next({
customerId: '', customerId: '',
displayName: '', displayName: '',
@@ -120,39 +119,15 @@ export class ThemeService implements OnModuleInit {
}); });
bufferStream.on('end', () => { bufferStream.on('end', () => {
console.log('terminou de enviar')
resolve(file.filename) resolve(file.filename)
}); });
bufferStream.on('error', (err) => { bufferStream.on('error', (err) => {
console.error('Erro no stream:', err);
reject(err); reject(err);
}); });
}); });
} }
} }
class CustomBufferStream extends Readable {
buffer: Buffer;
offset: number;
chunkSize: number;
constructor(buffer: Buffer, chunkSize: number) {
super({ highWaterMark: chunkSize }); // Configura o tamanho do chunk
this.buffer = buffer;
this.offset = 0;
this.chunkSize = chunkSize;
}
_read() {
if (this.offset < this.buffer.length) {
const end = Math.min(this.offset + this.chunkSize, this.buffer.length);
const copiedBuf = Uint8Array.prototype.slice.call(this.buffer);
const chunk = copiedBuf.slice(this.offset, end);
this.offset = end;
this.push(chunk);
} else {
this.push(null);
}
}
}