mirror of
https://github.com/dadosfera/maestro.git
synced 2026-10-25 02:09:08 +00:00
179 lines
4.9 KiB
TypeScript
179 lines
4.9 KiB
TypeScript
import { Injectable, Inject, HttpException } from '@nestjs/common';
|
|
import axios, { AxiosResponse, Method } from 'axios';
|
|
import { DadosferaLogger } from '@dadosfera/dadosfera-logs';
|
|
|
|
import { RequestUser } from '../../decorators/user.decorator';
|
|
import { STORAGE_EXPLORER_CONFIG } from './storage-explorer.config';
|
|
|
|
@Injectable()
|
|
export class StorageExplorerService {
|
|
private logger: any;
|
|
|
|
constructor(
|
|
@Inject(DadosferaLogger) dadosferaLogger: DadosferaLogger,
|
|
) {
|
|
this.logger = dadosferaLogger.logger;
|
|
}
|
|
|
|
async proxy(
|
|
method: string,
|
|
path: string,
|
|
user: RequestUser,
|
|
body?: any,
|
|
query?: Record<string, any>
|
|
): Promise<any> {
|
|
// Validate customer_id is present for multi-tenant isolation
|
|
if (!user.customer_id) {
|
|
throw new HttpException('Customer ID is required for storage operations', 400);
|
|
}
|
|
|
|
// Get customer-specific storage-explorer URL
|
|
const baseUrl = STORAGE_EXPLORER_CONFIG.getUrl(user.customer_name);
|
|
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<string, string> = {
|
|
'content-type': 'application/json',
|
|
};
|
|
|
|
this.logger.info('Proxying request to storage-explorer', {
|
|
method: method.toUpperCase(),
|
|
path,
|
|
customer_id: user.customer_id,
|
|
storage_url: baseUrl,
|
|
user_id: user.user_id,
|
|
});
|
|
|
|
try {
|
|
const response: AxiosResponse = await axios({
|
|
method: method as Method,
|
|
url: url.href,
|
|
headers,
|
|
data: body,
|
|
timeout: STORAGE_EXPLORER_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('Storage Explorer API proxy error', {
|
|
error: error.message,
|
|
status: error.response?.status,
|
|
path,
|
|
storage_url: baseUrl,
|
|
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('Storage Explorer API service unavailable', 503);
|
|
}
|
|
|
|
if (error.code === 'ETIMEDOUT' || error.code === 'ECONNABORTED') {
|
|
throw new HttpException('Storage Explorer API request timeout', 504);
|
|
}
|
|
|
|
throw new HttpException('Internal server error', 500);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Proxy with file upload support (multipart/form-data)
|
|
*/
|
|
async proxyFormData(
|
|
method: string,
|
|
path: string,
|
|
user: RequestUser,
|
|
formData: any,
|
|
query?: Record<string, any>,
|
|
): Promise<any> {
|
|
// Validate customer_id is present for multi-tenant isolation
|
|
if (!user.customer_id) {
|
|
throw new HttpException('Customer ID is required for storage operations', 400);
|
|
}
|
|
|
|
// Get customer-specific storage-explorer URL
|
|
const baseUrl = STORAGE_EXPLORER_CONFIG.getUrl(user.customer_name);
|
|
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<string, string> = {
|
|
// Let axios set Content-Type for multipart/form-data with boundary
|
|
...formData.getHeaders?.(),
|
|
};
|
|
|
|
this.logger.info('Proxying form data request to storage-explorer', {
|
|
method: method.toUpperCase(),
|
|
path,
|
|
customer_id: user.customer_id,
|
|
storage_url: baseUrl,
|
|
user_id: user.user_id,
|
|
});
|
|
|
|
try {
|
|
const response: AxiosResponse = await axios({
|
|
method: method as Method,
|
|
url: url.href,
|
|
headers,
|
|
data: formData,
|
|
timeout: STORAGE_EXPLORER_CONFIG.timeout,
|
|
maxContentLength: Infinity,
|
|
maxBodyLength: Infinity,
|
|
validateStatus: () => true,
|
|
});
|
|
|
|
if (response.status >= 400) {
|
|
throw new HttpException(response.data, response.status);
|
|
}
|
|
|
|
return response.data;
|
|
} catch (error) {
|
|
this.logger.error('Storage Explorer API form data proxy error', {
|
|
error: error.message,
|
|
status: error.response?.status,
|
|
path,
|
|
storage_url: baseUrl,
|
|
method: method.toUpperCase(),
|
|
});
|
|
|
|
if (error instanceof HttpException) {
|
|
throw error;
|
|
}
|
|
|
|
if (error.response) {
|
|
throw new HttpException(error.response.data, error.response.status);
|
|
}
|
|
|
|
throw new HttpException('Internal server error', 500);
|
|
}
|
|
}
|
|
}
|