Compare commits

...
Author SHA1 Message Date
marcos-silva-rodrigues d4ba45fd03 FIX: storage url 2026-02-20 14:28:55 -03:00
marcos-silva-rodrigues 82f1035a5e FIX: update storage port 2026-02-20 14:28:36 -03:00
marcos-silva-rodrigues 9a217dff57 FIX: storage service dns 2026-02-02 16:37:01 -03:00
marcos-silva-rodrigues cd4382c1ff FIX: send token by storage 2026-02-02 15:37:13 -03:00
marcos-silva-rodrigues 0e038d0b12 FIX: storage api 2026-02-02 14:41:53 -03:00
marcos-silva-rodrigues c97a02cb17 FIX: remove / 2026-02-02 14:27:08 -03:00
marcos-silva-rodrigues 878ec977b8 FIX: maestro proxy urls 2026-02-02 14:25:13 -03:00
Marcos Rodrigues Silva 8006867bc2 Merge pull request #436 from dadosfera/beta
Beta
2026-01-26 18:03:42 -03:00
Marcos Rodrigues Silva 784b0ef090 Merge pull request #435 from dadosfera/fix/validation-pipe
FIX: Update ValidationPipe for class-validator 0.14.0+ compatibility
2026-01-21 14:46:57 -03:00
RafaelandClaude b736cddf07 FIX: Update ValidationPipe for class-validator 0.14.0+ compatibility
This fix addresses a breaking change introduced in class-validator 0.14.0
where the default for forbidUnknownValues changed from false to true.

Issue:
- POST /connections was returning 400 "an unknown value was passed to the
  validate function" errors
- This occurred because CreateConnectionDto and UpdateConnectionDto have no
  validation decorators, causing class-validator 0.14.0+ to treat them as
  "unknown values"
- Extra fields (like connector_version) in request payloads would fail
  validation

Solution:
- Set forbidUnknownValues: false to allow DTOs without validation decorators
- Set whitelist: true to automatically strip extra properties not defined
  in DTOs
- This maintains backward compatibility while adding security by removing
  unexpected fields

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-21 14:43:43 -03:00
Rafael Santana 3d6328fb0b Merge pull request #434 from dadosfera/fix/class-validator-0.14-compatibility
UPDATE: updating /me to get api key
2026-01-20 17:53:09 -03:00
Rafael 4246c7495e UPDATE: updating /me to get api key 2026-01-20 17:52:34 -03:00
vinicius gadea 3d40746ccb Merge pull request #433 from dadosfera/feat/storage-explorer-api-integration
FEAT: integrate Storage Explorer API with new endpoints and permissions
2026-01-14 09:37:53 -03:00
Rafael Santana 121e30ce45 Merge pull request #432 from dadosfera/fix/class-validator-0.14-compatibility
FIX: Update ValidationPipe for class-validator 0.14.0+ compatibility
2026-01-12 16:30:41 -03:00
RafaelandClaude 85c8a4937d FIX: Update ValidationPipe for class-validator 0.14.0+ compatibility
This fix addresses a breaking change introduced in class-validator 0.14.0
where the default for forbidUnknownValues changed from false to true.

Issue:
- POST /connections was returning 400 "an unknown value was passed to the
  validate function" errors
- This occurred because CreateConnectionDto and UpdateConnectionDto have no
  validation decorators, causing class-validator 0.14.0+ to treat them as
  "unknown values"
- Extra fields (like connector_version) in request payloads would fail
  validation

Solution:
- Set forbidUnknownValues: false to allow DTOs without validation decorators
- Set whitelist: true to automatically strip extra properties not defined
  in DTOs
- This maintains backward compatibility while adding security by removing
  unexpected fields

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-12 16:22:25 -03:00
Marcos Rodrigues Silva 007f3911ff Merge pull request #429 from dadosfera/beta
Beta
2026-01-05 17:43:34 -03:00
10 changed files with 474 additions and 308 deletions
+1
View File
@@ -9,6 +9,7 @@ maestro:
cookie_secret: "ff7bc13823edb2ae50d248e5780bddc9d4b31c36"
redis_database: "1"
platform_api_url: https://xs2hkhq07k.execute-api.us-east-1.amazonaws.com
storage_explorer_api_url: "http://storage-explorer-{customer}.data-apps.svc.cluster.local:8000/api"
hostname: maestro.stg.dadosfera.ai
+1 -1
View File
@@ -47,7 +47,7 @@ maestro:
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
storage_explorer_api_url: "https://storage-explorer-{customer_id}.prd.dadosfera.ai/api"
storage_explorer_api_url: "https://storage-explorer-{customer}.dadosfera.ai/api"
dedicated_proxy: ""
restricted_ip: ""
redis_host: "aaapzppmlyamkocqwstpo7zvopczyyiyuy6xzm2g6c5k4mq3a66be4a-0.redis.sa-saopaulo-1.oci.oraclecloud.com"
+344 -296
View File
@@ -715,6 +715,158 @@
]
}
},
"/api-key": {
"post": {
"operationId": "ApiKeyController_create",
"parameters": [
{
"name": "dadosfera-lang",
"in": "header",
"required": false,
"schema": {
"enum": [
"pt-br",
"en-us"
],
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateApiKeyDto"
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateApiKeyResponseDto"
}
}
}
},
"default": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateApiKeyResponseDto"
}
}
}
}
},
"tags": [
"ApiKey"
],
"security": [
{
"access-token": []
}
]
},
"get": {
"operationId": "ApiKeyController_findAll",
"parameters": [
{
"name": "dadosfera-lang",
"in": "header",
"required": false,
"schema": {
"enum": [
"pt-br",
"en-us"
],
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ApiKeyBaseResponseDto"
}
}
}
}
},
"default": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ApiKeyBaseResponseDto"
}
}
}
}
}
},
"tags": [
"ApiKey"
],
"security": [
{
"access-token": []
}
]
}
},
"/api-key/{id}": {
"delete": {
"operationId": "ApiKeyController_remove",
"parameters": [
{
"name": "dadosfera-lang",
"in": "header",
"required": false,
"schema": {
"enum": [
"pt-br",
"en-us"
],
"type": "string"
}
},
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"tags": [
"ApiKey"
],
"security": [
{
"access-token": []
}
]
}
},
"/connections": {
"post": {
"operationId": "ConnectionController_createConnection",
@@ -6584,158 +6736,6 @@
]
}
},
"/api-key": {
"post": {
"operationId": "ApiKeyController_create",
"parameters": [
{
"name": "dadosfera-lang",
"in": "header",
"required": false,
"schema": {
"enum": [
"pt-br",
"en-us"
],
"type": "string"
}
}
],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateApiKeyDto"
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateApiKeyResponseDto"
}
}
}
},
"default": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/CreateApiKeyResponseDto"
}
}
}
}
},
"tags": [
"ApiKey"
],
"security": [
{
"access-token": []
}
]
},
"get": {
"operationId": "ApiKeyController_findAll",
"parameters": [
{
"name": "dadosfera-lang",
"in": "header",
"required": false,
"schema": {
"enum": [
"pt-br",
"en-us"
],
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ApiKeyBaseResponseDto"
}
}
}
}
},
"default": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ApiKeyBaseResponseDto"
}
}
}
}
}
},
"tags": [
"ApiKey"
],
"security": [
{
"access-token": []
}
]
}
},
"/api-key/{id}": {
"delete": {
"operationId": "ApiKeyController_remove",
"parameters": [
{
"name": "dadosfera-lang",
"in": "header",
"required": false,
"schema": {
"enum": [
"pt-br",
"en-us"
],
"type": "string"
}
},
{
"name": "id",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": ""
}
},
"tags": [
"ApiKey"
],
"security": [
{
"access-token": []
}
]
}
},
"/identity-providers": {
"post": {
"operationId": "IdentityProviderController_addIdentityProvider",
@@ -7275,6 +7275,100 @@
]
}
},
"/platform/pipelines/catalog/tables": {
"get": {
"operationId": "PlatformApiController_getAvailableTables",
"summary": "Get all tables available",
"parameters": [],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"Platform API"
],
"security": [
{
"access-token": []
}
]
}
},
"/platform/pipelines/catalog/schemas": {
"get": {
"operationId": "PlatformApiController_getAvailableSchemas",
"summary": "Get all schemas available",
"parameters": [],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"Platform API"
],
"security": [
{
"access-token": []
}
]
}
},
"/platform/pipelines/catalog/tables/validate": {
"post": {
"operationId": "PlatformApiController_validateTableAndSchema",
"summary": "Validate tables and schemas",
"parameters": [],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
},
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"Platform API"
],
"security": [
{
"access-token": []
}
]
}
},
"/platform/pipeline/{pipelineId}/pipeline_run": {
"get": {
"operationId": "PlatformApiController_getPipelineRuns",
@@ -8440,36 +8534,6 @@
]
}
},
"/storage-explorer/storage/link-to-table": {
"post": {
"operationId": "StorageExplorerController_linkFileToTable",
"summary": "Link an existing file in storage to a table",
"parameters": [],
"responses": {
"201": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
},
"tags": [
"Storage Explorer"
],
"security": [
{
"access-token": []
},
{
"access-token": []
}
]
}
},
"/storage-explorer/storage/metadata": {
"get": {
"operationId": "StorageExplorerController_getFileMetadata",
@@ -8593,6 +8657,104 @@
"accessToken"
]
},
"CreateApiKeyDto": {
"type": "object",
"properties": {
"permissions": {
"description": "Array of permission IDs",
"type": "array",
"items": {
"type": "number"
}
}
},
"required": [
"permissions"
]
},
"PermissionDto": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"name": {
"type": "string"
}
},
"required": [
"id",
"name"
]
},
"CreateApiKeyResponseDto": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"key_mask": {
"type": "string"
},
"permissions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PermissionDto"
}
},
"created_at": {
"type": "string",
"format": "date-time"
},
"created_by": {
"type": "string"
},
"key": {
"type": "string"
}
},
"required": [
"id",
"key_mask",
"permissions",
"created_at",
"created_by",
"key"
]
},
"ApiKeyBaseResponseDto": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"key_mask": {
"type": "string"
},
"permissions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PermissionDto"
}
},
"created_at": {
"type": "string",
"format": "date-time"
},
"created_by": {
"type": "string"
}
},
"required": [
"id",
"key_mask",
"permissions",
"created_at",
"created_by"
]
},
"CreateConnectionDto": {
"type": "object",
"properties": {
@@ -8816,37 +8978,6 @@
"message"
]
},
"PermissionDto": {
"type": "object",
"properties": {
"id": {
"type": "number"
},
"seqid": {
"type": "number"
},
"name": {
"type": "string"
},
"claim": {
"type": "string"
},
"createdAt": {
"type": "string"
},
"updatedAt": {
"type": "string"
}
},
"required": [
"id",
"seqid",
"name",
"claim",
"createdAt",
"updatedAt"
]
},
"Customer": {
"type": "object",
"properties": {
@@ -11330,89 +11461,6 @@
"publicKey"
]
},
"CreateApiKeyDto": {
"type": "object",
"properties": {
"permissions": {
"description": "Array of permission IDs",
"type": "array",
"items": {
"type": "number"
}
}
},
"required": [
"permissions"
]
},
"CreateApiKeyResponseDto": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"key_mask": {
"type": "string"
},
"permissions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PermissionDto"
}
},
"created_at": {
"type": "string",
"format": "date-time"
},
"created_by": {
"type": "string"
},
"key": {
"type": "string"
}
},
"required": [
"id",
"key_mask",
"permissions",
"created_at",
"created_by",
"key"
]
},
"ApiKeyBaseResponseDto": {
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"key_mask": {
"type": "string"
},
"permissions": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PermissionDto"
}
},
"created_at": {
"type": "string",
"format": "date-time"
},
"created_by": {
"type": "string"
}
},
"required": [
"id",
"key_mask",
"permissions",
"created_at",
"created_by"
]
},
"CreateIdentityProvider": {
"type": "object",
"properties": {
+23 -2
View File
@@ -55,6 +55,7 @@ import jwt, { JwtPayload } from 'jsonwebtoken';
import { LanguageEnum } from 'src/utils/languages.enum';
import { Language } from 'src/decorators/language.decorator';
import { ApiInternalOnlyEndpoint } from 'src/decorators/swagger.decorator';
import { ApiKeyService } from 'src/modules/api-key/api-key.service';
type CookiesValues = {
accessToken?: string;
@@ -74,6 +75,7 @@ export class AuthController {
@Inject(DadosferaLogger)
dadosferaLogger: DadosferaLogger,
private authClient: AuthClientService,
private apiKeyService: ApiKeyService,
) {
this.logger = dadosferaLogger.logger;
@@ -477,10 +479,29 @@ export class AuthController {
async getMe(@Req() req: Request, @Res() res: Response) {
this.logger.info('GET /auth/me ')
// Check for API key header first
const apiKey = req.get('X-Api-key');
if (apiKey) {
this.logger.info('Authenticating via X-Api-key header');
const { api_key } = await this.apiKeyService.get(apiKey);
const userDto = {
id: api_key.user_id,
name: api_key.username,
customer: {
id: api_key.customer_id,
name: api_key.customer_name,
tier: api_key.customer_tier,
}
};
return res.status(200).json(userDto);
}
// Get token and headers
const accessToken = req.cookies['ddf-auth'];
const refreshToken = req.cookies['ddf-refresh-auth'];
const userId = req.cookies['ddf-user-id'];
const refreshToken = req.cookies['ddf-refresh-auth'];
const userId = req.cookies['ddf-user-id'];
const resourceHost = req.headers["host"]
const hasUserSession = Boolean(accessToken) && Boolean(userId);
+2 -1
View File
@@ -8,10 +8,11 @@ import { AuthClientService } from './auth.service';
import { DucClient } from '../duc/client.config';
import { GoogleLoginStrategy } from './passport-strategies/google-strategy';
import { getOauthSecrets } from 'src/utils/OauthSecrets';
import { ApiKeyModule } from '../api-key/api-key.module';
const client = new DucClient();
@Module({
imports: [ClientsModule.register([client.providerOptions])],
imports: [ClientsModule.register([client.providerOptions]), ApiKeyModule],
controllers: [AuthController],
providers: [
AuthClientService,
@@ -26,6 +26,14 @@ import { DynamoDBService, ReferenceColumn } from '../../services/dynamodb';
import { CustomersService } from '../customers/customers.service';
import { validateCronAgainstScheduleLimit } from '../../utils/cron-validation';
type ValidateTablesDTO = {
tables: Array<{
table_schema: string,
table_name: string
}>
}
@ApiTags('Platform API')
@Controller('platform')
export class PlatformApiController {
@@ -720,6 +728,57 @@ export class PlatformApiController {
);
}
// ==================== Catalog ROUTES ====================
@Get('pipelines/catalog/tables')
@ApiOperation({ summary: 'Get all tables available' })
@RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.GET)
async getAvailableTables(
@User() user: RequestUser,
@Query() query: Record<string, string>,
) {
return this.platformApiService.proxy(
'GET',
'/catalog/tables',
user,
undefined,
query,
);
}
@Get('pipelines/catalog/schemas')
@ApiOperation({ summary: 'Get all schemas available' })
@RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.GET)
async getAvailableSchemas(
@User() user: RequestUser,
@Query() query: Record<string, string>,
) {
return this.platformApiService.proxy(
'GET',
'/catalog/schemas',
user,
undefined,
query,
);
}
@Post('pipelines/catalog/tables/validate')
@ApiOperation({ summary: 'Validate tables and schemas' })
@RequireAllPermissions(PERMISSIONS_GROUPS.PIPELINE.permissions.UPDATE)
async validateTableAndSchema(
@User() user: RequestUser,
@Query() query: Record<string, string>,
@Body() validateTablesDto: ValidateTablesDTO[]
) {
return this.platformApiService.proxy(
'POST',
'/catalog/tables/validate',
user,
validateTablesDto,
query,
);
}
// ==================== PIPELINE RUN ROUTES ====================
@Get('pipeline/:pipelineId/pipeline_run')
@@ -1,13 +1,13 @@
export const STORAGE_EXPLORER_CONFIG = {
getUrl: (customerId: string): string => {
getUrl: (customerName: string): string => {
const urlTemplate = process.env.STORAGE_EXPLORER_API_URL;
if (!urlTemplate) {
throw new Error('STORAGE_EXPLORER_API_URL environment variable is not set');
}
// Replace {customer_id} placeholder with actual customer ID
// For local: http://172.17.0.1:8000/api (no placeholder)
// For prod: https://storage-explorer-{customer_id}.stg.dadosfera.ai/api
return urlTemplate.replace('{customer_id}', customerId);
// For prod: https://storage-explorer-{customer_id}.dadosfera.ai/api
return urlTemplate.replace('{customer}', customerName);
},
timeout: parseInt(process.env.STORAGE_EXPLORER_TIMEOUT || '30000', 10),
};
@@ -3,13 +3,13 @@ import {
Get,
Post,
Put,
Delete,
Param,
Body,
Query,
Inject,
UseInterceptors,
UploadedFiles,
Headers,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiConsumes } from '@nestjs/swagger';
import { FilesInterceptor } from '@nestjs/platform-express';
@@ -47,11 +47,13 @@ export class StorageExplorerController {
async validateTableName(
@Body() body: any,
@User() user: RequestUser,
) {
return this.storageExplorerService.proxy(
'POST',
'/tables/validate-name',
user,
body,
);
}
@@ -62,11 +64,13 @@ export class StorageExplorerController {
async createTable(
@Body() body: any,
@User() user: RequestUser,
) {
return this.storageExplorerService.proxy(
'POST',
'/tables/',
user,
body,
);
}
@@ -77,11 +81,13 @@ export class StorageExplorerController {
async listTables(
@Query('page') page: number,
@User() user: RequestUser,
) {
return this.storageExplorerService.proxy(
'GET',
'/tables/',
user,
undefined,
{ page },
);
@@ -93,6 +99,7 @@ export class StorageExplorerController {
async getTable(
@Param('tableId') tableId: string,
@User() user: RequestUser,
) {
return this.storageExplorerService.proxy(
'GET',
@@ -108,11 +115,13 @@ export class StorageExplorerController {
@Param('tableId') tableId: string,
@Param('datasetId') datasetId: string,
@User() user: RequestUser,
) {
return this.storageExplorerService.proxy(
'POST',
`/tables/${tableId}/datasets/${datasetId}`,
user,
);
}
@@ -122,11 +131,13 @@ export class StorageExplorerController {
async getTableDatasets(
@Param('tableId') tableId: string,
@User() user: RequestUser,
) {
return this.storageExplorerService.proxy(
'GET',
`/tables/${tableId}/datasets`,
user,
);
}
@@ -136,11 +147,13 @@ export class StorageExplorerController {
async getTableSchema(
@Param('tableId') tableId: string,
@User() user: RequestUser,
) {
return this.storageExplorerService.proxy(
'GET',
`/tables/${tableId}/schema`,
user,
);
}
@@ -156,6 +169,7 @@ export class StorageExplorerController {
'POST',
`/tables/${tableId}/validate-compatibility/${datasetId}`,
user,
);
}
@@ -170,11 +184,13 @@ export class StorageExplorerController {
@Param('datasetId') datasetId: string,
@Query('limit') limit: number,
@User() user: RequestUser,
) {
return this.storageExplorerService.proxy(
'GET',
`/datasets/${datasetId}/preview`,
user,
undefined,
{ limit },
);
@@ -187,11 +203,13 @@ export class StorageExplorerController {
@Param('datasetId') datasetId: string,
@Query('force_refresh') forceRefresh: boolean,
@User() user: RequestUser,
) {
return this.storageExplorerService.proxy(
'GET',
`/datasets/${datasetId}/schema`,
user,
undefined,
{ force_refresh: forceRefresh },
);
@@ -203,11 +221,13 @@ export class StorageExplorerController {
async listDatasetsByUpload(
@Param('uploadId') uploadId: string,
@User() user: RequestUser,
) {
return this.storageExplorerService.proxy(
'GET',
`/datasets/upload/${uploadId}`,
user,
);
}
@@ -218,11 +238,13 @@ export class StorageExplorerController {
@Param('datasetId') datasetId: string,
@Body() body: any,
@User() user: RequestUser,
) {
return this.storageExplorerService.proxy(
'PUT',
`/datasets/${datasetId}/refresh-schema`,
user,
body,
);
}
@@ -239,11 +261,13 @@ export class StorageExplorerController {
@Query('limit') limit: number,
@Query('folder_path') folderPath: string,
@User() user: RequestUser,
) {
return this.storageExplorerService.proxy(
'GET',
'/storage/uploads/history',
user,
undefined,
{ page, limit, folder_path: folderPath },
);
@@ -255,11 +279,13 @@ export class StorageExplorerController {
async browseStorage(
@Query('path') path: string,
@User() user: RequestUser,
) {
return this.storageExplorerService.proxy(
'GET',
'/storage/browse',
user,
undefined,
{ path },
);
@@ -274,6 +300,7 @@ export class StorageExplorerController {
@UploadedFiles() files: Array<Express.Multer.File>,
@Body('folder_path') folderPath: string,
@User() user: RequestUser,
) {
// Create FormData to forward files to storage-explorer API
const formData = new FormData();
@@ -297,6 +324,7 @@ export class StorageExplorerController {
'POST',
'/storage/upload/batch',
user,
formData,
);
}
@@ -307,11 +335,13 @@ export class StorageExplorerController {
async createFolder(
@Body() body: any,
@User() user: RequestUser,
) {
return this.storageExplorerService.proxy(
'POST',
'/storage/folder/create',
user,
body,
);
}
@@ -322,11 +352,13 @@ export class StorageExplorerController {
async downloadFile(
@Query('file_path') filePath: string,
@User() user: RequestUser,
) {
return this.storageExplorerService.proxy(
'GET',
'/storage/download',
user,
undefined,
{ file_path: filePath },
);
@@ -338,6 +370,7 @@ export class StorageExplorerController {
async getFileMetadata(
@Query('file_path') filePath: string,
@User() user: RequestUser,
) {
return this.storageExplorerService.proxy(
'GET',
@@ -20,7 +20,7 @@ export class StorageExplorerService {
path: string,
user: RequestUser,
body?: any,
query?: Record<string, any>,
query?: Record<string, any>
): Promise<any> {
// Validate customer_id is present for multi-tenant isolation
if (!user.customer_id) {
@@ -28,7 +28,7 @@ export class StorageExplorerService {
}
// Get customer-specific storage-explorer URL
const baseUrl = STORAGE_EXPLORER_CONFIG.getUrl(user.customer_id);
const baseUrl = STORAGE_EXPLORER_CONFIG.getUrl(user.customer_name);
const url = new URL(`${baseUrl}${path}`);
// Add query params
@@ -113,7 +113,7 @@ export class StorageExplorerService {
}
// Get customer-specific storage-explorer URL
const baseUrl = STORAGE_EXPLORER_CONFIG.getUrl(user.customer_id);
const baseUrl = STORAGE_EXPLORER_CONFIG.getUrl(user.customer_name);
const url = new URL(`${baseUrl}${path}`);
// Add query params
+4 -1
View File
@@ -14,7 +14,10 @@ export class ValidationPipe implements PipeTransform<any> {
return value;
}
const object = plainToInstance(metatype, value);
const errors = await validate(object);
const errors = await validate(object, {
forbidUnknownValues: false,
whitelist: true,
});
if (errors.length > 0) {
const errorMessages = errors.map((err) => err.constraints);
throw new BadRequestException(errorMessages);