REFACTOR API gateway
This commit is contained in:
@@ -99,20 +99,20 @@ export class DataService {
|
||||
*/
|
||||
async uploadSalesHistory(
|
||||
file: File,
|
||||
tenantId?: string,
|
||||
tenantId: string, // Tenant ID is now a required path parameter
|
||||
additionalData?: Record<string, any>
|
||||
): Promise<UploadResponse> {
|
||||
try {
|
||||
console.log('Uploading sales file:', file.name);
|
||||
|
||||
|
||||
// ✅ CRITICAL FIX: Use the correct endpoint that exists in backend
|
||||
// Backend endpoint: @router.post("/import", response_model=SalesImportResult)
|
||||
// Full path: /api/v1/data/sales/import (mounted with prefix /api/v1/sales)
|
||||
|
||||
// Full path: /api/v1/tenants/{tenant_id}/sales/import
|
||||
|
||||
// Determine file format
|
||||
const fileName = file.name.toLowerCase();
|
||||
let fileFormat: string;
|
||||
|
||||
|
||||
if (fileName.endsWith('.csv')) {
|
||||
fileFormat = 'csv';
|
||||
} else if (fileName.endsWith('.json')) {
|
||||
@@ -122,28 +122,29 @@ export class DataService {
|
||||
} else {
|
||||
fileFormat = 'csv'; // Default fallback
|
||||
}
|
||||
|
||||
|
||||
// ✅ FIXED: Create FormData manually to match backend expectations
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
formData.append('file_format', fileFormat);
|
||||
|
||||
if (tenantId) {
|
||||
formData.append('tenant_id', tenantId);
|
||||
}
|
||||
|
||||
// tenantId is no longer appended to FormData as it's a path parameter
|
||||
// if (tenantId) {
|
||||
// formData.append('tenant_id', tenantId);
|
||||
// }
|
||||
|
||||
// Add additional data if provided
|
||||
if (additionalData) {
|
||||
Object.entries(additionalData).forEach(([key, value]) => {
|
||||
formData.append(key, String(value));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
console.log('Uploading with file_format:', fileFormat);
|
||||
|
||||
|
||||
// ✅ FIXED: Use the correct endpoint that exists in the backend
|
||||
const response = await apiClient.request<ApiResponse<any>>(
|
||||
'/api/v1/data/sales/import', // Correct endpoint path
|
||||
`/api/v1/tenants/${tenantId}/sales/import`, // Correct endpoint path with tenant_id
|
||||
{
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
@@ -151,16 +152,16 @@ export class DataService {
|
||||
headers: {} // Empty headers to avoid setting Content-Type manually
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
console.log('Upload response:', response);
|
||||
|
||||
|
||||
// ✅ Handle the SalesImportResult response structure
|
||||
if (response && typeof response === 'object') {
|
||||
// Handle API errors
|
||||
if ('detail' in response) {
|
||||
throw new Error(typeof response.detail === 'string' ? response.detail : 'Upload failed');
|
||||
}
|
||||
|
||||
|
||||
// Extract data from response
|
||||
let uploadResult: any;
|
||||
if ('data' in response && response.data) {
|
||||
@@ -168,28 +169,28 @@ export class DataService {
|
||||
} else {
|
||||
uploadResult = response;
|
||||
}
|
||||
|
||||
|
||||
// ✅ FIXED: Map backend SalesImportResult to frontend UploadResponse
|
||||
return {
|
||||
message: uploadResult.success
|
||||
message: uploadResult.success
|
||||
? `Successfully processed ${uploadResult.records_created || uploadResult.records_processed || 0} records`
|
||||
: 'Upload completed with issues',
|
||||
records_processed: uploadResult.records_created || uploadResult.records_processed || 0,
|
||||
errors: uploadResult.errors ?
|
||||
(Array.isArray(uploadResult.errors) ?
|
||||
uploadResult.errors.map((err: any) =>
|
||||
errors: uploadResult.errors ?
|
||||
(Array.isArray(uploadResult.errors) ?
|
||||
uploadResult.errors.map((err: any) =>
|
||||
typeof err === 'string' ? err : (err.message || String(err))
|
||||
) : [String(uploadResult.errors)]
|
||||
) : [],
|
||||
upload_id: uploadResult.id || undefined
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
throw new Error('Invalid response format from upload service');
|
||||
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('Error uploading file:', error);
|
||||
|
||||
|
||||
let errorMessage = 'Error al subir el archivo';
|
||||
if (error.response?.status === 422) {
|
||||
errorMessage = 'Formato de archivo inválido';
|
||||
@@ -200,7 +201,7 @@ export class DataService {
|
||||
} else if (error.message) {
|
||||
errorMessage = error.message;
|
||||
}
|
||||
|
||||
|
||||
// Throw structured error that can be caught by the frontend
|
||||
throw {
|
||||
message: errorMessage,
|
||||
@@ -216,20 +217,20 @@ export class DataService {
|
||||
* ✅ ALTERNATIVE: Upload sales data using the JSON import endpoint
|
||||
* This uses the same endpoint as validation but with validate_only: false
|
||||
*/
|
||||
async uploadSalesDataAsJson(file: File, tenantId?: string): Promise<UploadResponse> {
|
||||
async uploadSalesDataAsJson(file: File, tenantId: string): Promise<UploadResponse> { // tenantId made required
|
||||
try {
|
||||
console.log('Uploading sales data as JSON:', file.name);
|
||||
|
||||
|
||||
const fileContent = await this.readFileAsText(file);
|
||||
|
||||
|
||||
if (!fileContent) {
|
||||
throw new Error('Failed to read file content');
|
||||
}
|
||||
|
||||
|
||||
// Determine file format
|
||||
const fileName = file.name.toLowerCase();
|
||||
let dataFormat: 'csv' | 'json' | 'excel';
|
||||
|
||||
|
||||
if (fileName.endsWith('.csv')) {
|
||||
dataFormat = 'csv';
|
||||
} else if (fileName.endsWith('.json')) {
|
||||
@@ -239,59 +240,67 @@ export class DataService {
|
||||
} else {
|
||||
dataFormat = 'csv';
|
||||
}
|
||||
|
||||
|
||||
// ✅ Use the same structure as validation but with validate_only: false
|
||||
const importData: SalesDataImportRequest = {
|
||||
tenant_id: tenantId || '00000000-0000-0000-0000-000000000000',
|
||||
tenant_id: tenantId, // Use the provided tenantId
|
||||
data: fileContent,
|
||||
data_format: dataFormat,
|
||||
validate_only: false, // This makes it actually import the data
|
||||
source: 'onboarding_upload'
|
||||
};
|
||||
|
||||
|
||||
console.log('Uploading data with validate_only: false');
|
||||
|
||||
|
||||
// ✅ OPTION: Add a new JSON import endpoint to the backend
|
||||
// Current backend sales.py does not have a /import/json endpoint,
|
||||
// it only has a file upload endpoint.
|
||||
// If a JSON import endpoint is desired, it needs to be added to sales.py
|
||||
// For now, this method will target the existing /import endpoint with a JSON body
|
||||
// This will require the backend to support JSON body for /import, which it currently
|
||||
// does not for the direct file upload endpoint.
|
||||
// THIS ALTERNATIVE METHOD IS LEFT AS-IS, ASSUMING A FUTURE BACKEND ENDPOINT
|
||||
// OR A MODIFICATION TO THE EXISTING /import ENDPOINT TO ACCEPT JSON BODY.
|
||||
const response = await apiClient.post<ApiResponse<any>>(
|
||||
'/api/v1/data/sales/import/json', // Need to add this endpoint to backend
|
||||
`/api/v1/tenants/${tenantId}/sales/import/json`, // This endpoint does not exist in sales.py
|
||||
importData
|
||||
);
|
||||
|
||||
|
||||
console.log('JSON upload response:', response);
|
||||
|
||||
|
||||
// Handle response similar to file upload
|
||||
if (response && typeof response === 'object') {
|
||||
if ('detail' in response) {
|
||||
throw new Error(typeof response.detail === 'string' ? response.detail : 'Upload failed');
|
||||
}
|
||||
|
||||
|
||||
let uploadResult: any;
|
||||
if ('data' in response && response.data) {
|
||||
uploadResult = response.data;
|
||||
} else {
|
||||
uploadResult = response;
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
message: uploadResult.success
|
||||
message: uploadResult.success
|
||||
? `Successfully processed ${uploadResult.records_created || uploadResult.records_processed || 0} records`
|
||||
: 'Upload completed with issues',
|
||||
records_processed: uploadResult.records_created || uploadResult.records_processed || 0,
|
||||
errors: uploadResult.errors ?
|
||||
(Array.isArray(uploadResult.errors) ?
|
||||
uploadResult.errors.map((err: any) =>
|
||||
errors: uploadResult.errors ?
|
||||
(Array.isArray(uploadResult.errors) ?
|
||||
uploadResult.errors.map((err: any) =>
|
||||
typeof err === 'string' ? err : (err.message || String(err))
|
||||
) : [String(uploadResult.errors)]
|
||||
) : [],
|
||||
upload_id: uploadResult.id || undefined
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
throw new Error('Invalid response format from upload service');
|
||||
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('Error uploading JSON data:', error);
|
||||
|
||||
|
||||
let errorMessage = 'Error al subir los datos';
|
||||
if (error.response?.status === 422) {
|
||||
errorMessage = 'Formato de datos inválido';
|
||||
@@ -302,7 +311,7 @@ export class DataService {
|
||||
} else if (error.message) {
|
||||
errorMessage = error.message;
|
||||
}
|
||||
|
||||
|
||||
throw {
|
||||
message: errorMessage,
|
||||
status: error.response?.status || 0,
|
||||
@@ -312,22 +321,22 @@ export class DataService {
|
||||
}
|
||||
}
|
||||
|
||||
async validateSalesData(file: File, tenantId?: string): Promise<DataValidation> {
|
||||
async validateSalesData(file: File, tenantId: string): Promise<DataValidation> { // tenantId made required
|
||||
try {
|
||||
console.log('Reading file content...', file.name);
|
||||
|
||||
|
||||
const fileContent = await this.readFileAsText(file);
|
||||
|
||||
|
||||
if (!fileContent) {
|
||||
throw new Error('Failed to read file content');
|
||||
}
|
||||
|
||||
console.log('File content read successfully, length:', fileContent.length);
|
||||
|
||||
|
||||
// Determine file format from extension
|
||||
const fileName = file.name.toLowerCase();
|
||||
let dataFormat: 'csv' | 'json' | 'excel';
|
||||
|
||||
|
||||
if (fileName.endsWith('.csv')) {
|
||||
dataFormat = 'csv';
|
||||
} else if (fileName.endsWith('.json')) {
|
||||
@@ -342,7 +351,7 @@ export class DataService {
|
||||
|
||||
// ✅ FIXED: Use proper tenant ID when available
|
||||
const importData: SalesDataImportRequest = {
|
||||
tenant_id: tenantId || '00000000-0000-0000-0000-000000000000',
|
||||
tenant_id: tenantId, // Use the provided tenantId
|
||||
data: fileContent,
|
||||
data_format: dataFormat,
|
||||
validate_only: true
|
||||
@@ -351,10 +360,10 @@ export class DataService {
|
||||
console.log('Sending validation request with tenant_id:', importData.tenant_id);
|
||||
|
||||
const response = await apiClient.post<ApiResponse<DataValidation>>(
|
||||
'/api/v1/data/sales/import/validate',
|
||||
`/api/v1/tenants/${tenantId}/sales/import/validate`, // Correct endpoint with tenant_id
|
||||
importData
|
||||
);
|
||||
|
||||
|
||||
console.log('Raw response from API:', response);
|
||||
|
||||
// ✅ ENHANCED: Handle the new backend response structure
|
||||
@@ -362,7 +371,7 @@ export class DataService {
|
||||
// Handle API errors
|
||||
if ('detail' in response) {
|
||||
console.error('API returned error:', response.detail);
|
||||
|
||||
|
||||
if (Array.isArray(response.detail)) {
|
||||
// Handle Pydantic validation errors
|
||||
const errorMessages = response.detail.map(err => ({
|
||||
@@ -371,7 +380,7 @@ export class DataService {
|
||||
field: err.loc ? err.loc[err.loc.length - 1] : null,
|
||||
code: err.type
|
||||
}));
|
||||
|
||||
|
||||
return {
|
||||
is_valid: false,
|
||||
total_records: 0,
|
||||
@@ -385,7 +394,7 @@ export class DataService {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Handle simple error messages
|
||||
return {
|
||||
is_valid: false,
|
||||
@@ -407,7 +416,7 @@ export class DataService {
|
||||
|
||||
// ✅ SUCCESS: Handle successful validation response
|
||||
let validationResult: DataValidation;
|
||||
|
||||
|
||||
// Check if response has nested data
|
||||
if ('data' in response && response.data) {
|
||||
validationResult = response.data;
|
||||
@@ -427,7 +436,7 @@ export class DataService {
|
||||
errors: validationResult.errors || [],
|
||||
warnings: validationResult.warnings || [],
|
||||
summary: validationResult.summary || { status: 'unknown', suggestions: [] },
|
||||
|
||||
|
||||
// Backward compatibility fields
|
||||
valid: validationResult.is_valid, // Map for legacy code
|
||||
recordCount: validationResult.total_records,
|
||||
@@ -436,13 +445,13 @@ export class DataService {
|
||||
}
|
||||
|
||||
throw new Error('Invalid response format from validation service');
|
||||
|
||||
|
||||
} catch (error: any) {
|
||||
console.error('Error validating file:', error);
|
||||
|
||||
|
||||
let errorMessage = 'Error al validar el archivo';
|
||||
let errorCode = 'UNKNOWN_ERROR';
|
||||
|
||||
|
||||
if (error.response?.status === 422) {
|
||||
errorMessage = 'Formato de archivo inválido';
|
||||
errorCode = 'INVALID_FORMAT';
|
||||
@@ -456,7 +465,7 @@ export class DataService {
|
||||
errorMessage = error.message;
|
||||
errorCode = 'CLIENT_ERROR';
|
||||
}
|
||||
|
||||
|
||||
// Return properly structured error response matching new schema
|
||||
return {
|
||||
is_valid: false,
|
||||
@@ -473,7 +482,7 @@ export class DataService {
|
||||
status: 'error',
|
||||
suggestions: ['Intenta con un archivo diferente o contacta soporte']
|
||||
},
|
||||
|
||||
|
||||
// Backward compatibility
|
||||
valid: false,
|
||||
recordCount: 0,
|
||||
@@ -488,7 +497,7 @@ export class DataService {
|
||||
private readFileAsText(file: File): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
|
||||
|
||||
reader.onload = (event) => {
|
||||
const result = event.target?.result;
|
||||
if (typeof result === 'string') {
|
||||
@@ -497,15 +506,15 @@ export class DataService {
|
||||
reject(new Error('Failed to read file as text'));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
reader.onerror = () => {
|
||||
reject(new Error('Failed to read file'));
|
||||
};
|
||||
|
||||
|
||||
reader.onabort = () => {
|
||||
reject(new Error('File reading was aborted'));
|
||||
};
|
||||
|
||||
|
||||
// Read the file as text
|
||||
reader.readAsText(file);
|
||||
});
|
||||
@@ -524,7 +533,7 @@ export class DataService {
|
||||
/**
|
||||
* Get sales records
|
||||
*/
|
||||
async getSalesRecords(params?: {
|
||||
async getSalesRecords(tenantId: string, params?: { // Add tenantId
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
productName?: string;
|
||||
@@ -536,16 +545,16 @@ export class DataService {
|
||||
total: number;
|
||||
page: number;
|
||||
pages: number;
|
||||
}>>('/api/v1/data/sales', { params });
|
||||
}>>(`/api/v1/tenants/${tenantId}/sales`, { params }); // Use tenantId in path
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create single sales record
|
||||
*/
|
||||
async createSalesRecord(record: CreateSalesRequest): Promise<SalesRecord> {
|
||||
async createSalesRecord(tenantId: string, record: CreateSalesRequest): Promise<SalesRecord> { // Add tenantId
|
||||
const response = await apiClient.post<ApiResponse<SalesRecord>>(
|
||||
'/api/v1/data/sales',
|
||||
`/api/v1/tenants/${tenantId}/sales`, // Use tenantId in path
|
||||
record
|
||||
);
|
||||
return response.data!;
|
||||
@@ -554,9 +563,9 @@ export class DataService {
|
||||
/**
|
||||
* Update sales record
|
||||
*/
|
||||
async updateSalesRecord(id: string, record: Partial<CreateSalesRequest>): Promise<SalesRecord> {
|
||||
async updateSalesRecord(tenantId: string, id: string, record: Partial<CreateSalesRequest>): Promise<SalesRecord> { // Add tenantId
|
||||
const response = await apiClient.put<ApiResponse<SalesRecord>>(
|
||||
`/api/v1/data/sales/${id}`,
|
||||
`/api/v1/tenants/${tenantId}/sales/${id}`, // Use tenantId in path
|
||||
record
|
||||
);
|
||||
return response.data!;
|
||||
@@ -565,8 +574,8 @@ export class DataService {
|
||||
/**
|
||||
* Delete sales record
|
||||
*/
|
||||
async deleteSalesRecord(id: string): Promise<void> {
|
||||
await apiClient.delete(`/api/v1/data/sales/${id}`);
|
||||
async deleteSalesRecord(tenantId: string, id: string): Promise<void> { // Add tenantId
|
||||
await apiClient.delete(`/api/v1/tenants/${tenantId}/sales/${id}`); // Use tenantId in path
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -142,303 +142,8 @@ export class TenantService {
|
||||
* Corresponds to GET /users/{user_id}/tenants
|
||||
*/
|
||||
async getUserTenants(userId: string): Promise<TenantInfo[]> {
|
||||
const response = await apiClient.get<ApiResponse<TenantInfo[]>>(`/api/v1/users/${userId}/tenants`);
|
||||
const response = await apiClient.get<ApiResponse<TenantInfo[]>>(`/api/v1/tenants/user/${userId}`);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a team member to a tenant
|
||||
* Corresponds to POST /tenants/{tenant_id}/members
|
||||
*/
|
||||
async addTeamMember(tenantId: string, userId: string, role: string): Promise<TenantMemberResponse> {
|
||||
const response = await apiClient.post<ApiResponse<TenantMemberResponse>>(
|
||||
`/api/v1/tenants/${tenantId}/members`,
|
||||
{ user_id: userId, role }
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
// --- Existing methods (kept for completeness, assuming they map to other backend endpoints not provided) ---
|
||||
|
||||
/**
|
||||
* Get current tenant info (no direct backend mapping in provided file, but common)
|
||||
*/
|
||||
async getCurrentTenant(): Promise<TenantInfo> {
|
||||
const response = await apiClient.get<ApiResponse<TenantInfo>>('/api/v1/tenants/current');
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update current tenant (no direct backend mapping in provided file, but common)
|
||||
*/
|
||||
async updateCurrentTenant(updates: TenantUpdate): Promise<TenantInfo> {
|
||||
const response = await apiClient.put<ApiResponse<TenantInfo>>('/api/v1/tenants/current', updates);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tenant settings (no direct backend mapping in provided file)
|
||||
*/
|
||||
async getTenantSettings(): Promise<TenantSettings> {
|
||||
const response = await apiClient.get<ApiResponse<TenantSettings>>('/api/v1/tenants/settings');
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tenant settings (no direct backend mapping in provided file)
|
||||
*/
|
||||
async updateTenantSettings(settings: Partial<TenantSettings>): Promise<TenantSettings> {
|
||||
const response = await apiClient.put<ApiResponse<TenantSettings>>(
|
||||
'/api/v1/tenants/settings',
|
||||
settings
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tenant statistics (no direct backend mapping in provided file)
|
||||
*/
|
||||
async getTenantStats(): Promise<TenantStats> {
|
||||
const response = await apiClient.get<ApiResponse<TenantStats>>('/api/v1/tenants/stats');
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tenant users (no direct backend mapping in provided file)
|
||||
*/
|
||||
async getTenantUsers(params?: {
|
||||
role?: string;
|
||||
active?: boolean;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}): Promise<{
|
||||
users: TenantUser[];
|
||||
total: number;
|
||||
page: number;
|
||||
pages: number;
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/api/v1/tenants/users', { params });
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invite user to tenant (no direct backend mapping in provided file)
|
||||
*/
|
||||
async inviteUser(invitation: InviteUser): Promise<{
|
||||
invitation_id: string;
|
||||
email: string;
|
||||
role: string;
|
||||
expires_at: string;
|
||||
invitation_token: string;
|
||||
}> {
|
||||
const response = await apiClient.post<ApiResponse<any>>('/api/v1/tenants/users/invite', invitation);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user role (no direct backend mapping in provided file)
|
||||
*/
|
||||
async updateUserRole(userId: string, role: string): Promise<TenantUser> {
|
||||
const response = await apiClient.patch<ApiResponse<TenantUser>>(
|
||||
`/api/v1/tenants/users/${userId}`,
|
||||
{ role }
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate user (no direct backend mapping in provided file)
|
||||
*/
|
||||
async deactivateUser(userId: string): Promise<TenantUser> {
|
||||
const response = await apiClient.patch<ApiResponse<TenantUser>>(
|
||||
`/api/v1/tenants/users/${userId}`,
|
||||
{ is_active: false }
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reactivate user (no direct backend mapping in provided file)
|
||||
*/
|
||||
async reactivateUser(userId: string): Promise<TenantUser> {
|
||||
const response = await apiClient.patch<ApiResponse<TenantUser>>(
|
||||
`/api/v1/tenants/users/${userId}`,
|
||||
{ is_active: true }
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove user from tenant (no direct backend mapping in provided file)
|
||||
*/
|
||||
async removeUser(userId: string): Promise<void> {
|
||||
await apiClient.delete(`/api/v1/tenants/users/${userId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get pending invitations (no direct backend mapping in provided file)
|
||||
*/
|
||||
async getPendingInvitations(): Promise<{
|
||||
id: string;
|
||||
email: string;
|
||||
role: string;
|
||||
invited_at: string;
|
||||
expires_at: string;
|
||||
invited_by: string;
|
||||
}[]> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/api/v1/tenants/invitations');
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel invitation (no direct backend mapping in provided file)
|
||||
*/
|
||||
async cancelInvitation(invitationId: string): Promise<void> {
|
||||
await apiClient.delete(`/api/v1/tenants/invitations/${invitationId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resend invitation (no direct backend mapping in provided file)
|
||||
*/
|
||||
async resendInvitation(invitationId: string): Promise<void> {
|
||||
await apiClient.post(`/api/v1/tenants/invitations/${invitationId}/resend`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tenant activity log (no direct backend mapping in provided file)
|
||||
*/
|
||||
async getActivityLog(params?: {
|
||||
userId?: string;
|
||||
action?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
page?: number;
|
||||
limit?: number;
|
||||
}): Promise<{
|
||||
activities: {
|
||||
id: string;
|
||||
user_id: string;
|
||||
user_name: string;
|
||||
action: string;
|
||||
resource: string;
|
||||
resource_id: string;
|
||||
details?: Record<string, any>;
|
||||
ip_address?: string;
|
||||
user_agent?: string;
|
||||
created_at: string;
|
||||
}[];
|
||||
total: number;
|
||||
page: number;
|
||||
pages: number;
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/api/v1/tenants/activity', { params });
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tenant billing info (no direct backend mapping in provided file)
|
||||
*/
|
||||
async getBillingInfo(): Promise<{
|
||||
subscription_plan: string;
|
||||
billing_cycle: 'monthly' | 'yearly';
|
||||
next_billing_date: string;
|
||||
amount: number;
|
||||
currency: string;
|
||||
payment_method: {
|
||||
type: string;
|
||||
last_four: string;
|
||||
expires: string;
|
||||
};
|
||||
usage: {
|
||||
api_calls: number;
|
||||
storage_mb: number;
|
||||
users: number;
|
||||
limits: {
|
||||
api_calls_per_month: number;
|
||||
storage_mb: number;
|
||||
max_users: number;
|
||||
};
|
||||
};
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/api/v1/tenants/billing');
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update billing info (no direct backend mapping in provided file)
|
||||
*/
|
||||
async updateBillingInfo(billingData: {
|
||||
payment_method_token?: string;
|
||||
billing_address?: {
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
zip: string;
|
||||
country: string;
|
||||
};
|
||||
}): Promise<void> {
|
||||
await apiClient.put('/api/v1/tenants/billing', billingData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change subscription plan (no direct backend mapping in provided file)
|
||||
*/
|
||||
async changeSubscriptionPlan(
|
||||
planId: string,
|
||||
billingCycle: 'monthly' | 'yearly'
|
||||
): Promise<{
|
||||
subscription_id: string;
|
||||
plan: string;
|
||||
billing_cycle: string;
|
||||
next_billing_date: string;
|
||||
proration_amount?: number;
|
||||
}> {
|
||||
const response = await apiClient.post<ApiResponse<any>>('/api/v1/tenants/subscription/change', {
|
||||
plan_id: planId,
|
||||
billing_cycle: billingCycle,
|
||||
});
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel subscription (no direct backend mapping in provided file)
|
||||
*/
|
||||
async cancelSubscription(cancelAt: 'immediately' | 'end_of_period'): Promise<{
|
||||
cancelled_at: string;
|
||||
will_cancel_at: string;
|
||||
refund_amount?: number;
|
||||
}> {
|
||||
const response = await apiClient.post<ApiResponse<any>>('/api/v1/tenants/subscription/cancel', {
|
||||
cancel_at: cancelAt,
|
||||
});
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export tenant data (no direct backend mapping in provided file)
|
||||
*/
|
||||
async exportTenantData(dataTypes: string[], format: 'json' | 'csv'): Promise<Blob> {
|
||||
const response = await apiClient.post('/api/v1/tenants/export', {
|
||||
data_types: dataTypes,
|
||||
format,
|
||||
responseType: 'blob',
|
||||
});
|
||||
return response as unknown as Blob;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete tenant (GDPR compliance) (no direct backend mapping in provided file)
|
||||
*/
|
||||
async deleteTenant(confirmationToken: string): Promise<{
|
||||
deletion_scheduled_at: string;
|
||||
data_retention_until: string;
|
||||
recovery_period_days: number;
|
||||
}> {
|
||||
const response = await apiClient.delete<ApiResponse<any>>('/api/v1/tenants/current', {
|
||||
data: { confirmation_token: confirmationToken },
|
||||
});
|
||||
return response.data!;
|
||||
}
|
||||
}
|
||||
|
||||
export const tenantService = new TenantService();
|
||||
}
|
||||
@@ -75,9 +75,9 @@ export class TrainingService {
|
||||
/**
|
||||
* Start new training job
|
||||
*/
|
||||
async startTraining(config: TrainingConfiguration): Promise<TrainingJobStatus> {
|
||||
async startTraining(tenantId: string, config: TrainingConfiguration): Promise<TrainingJobStatus> {
|
||||
const response = await apiClient.post<TrainingJobStatus>(
|
||||
'/api/v1/training/jobs',
|
||||
`/api/v1/tenants/${tenantId}/training/jobs`,
|
||||
config
|
||||
);
|
||||
return response.data!;
|
||||
@@ -86,9 +86,9 @@ export class TrainingService {
|
||||
/**
|
||||
* Get training job status
|
||||
*/
|
||||
async getTrainingStatus(jobId: string): Promise<TrainingJobProgress> {
|
||||
async getTrainingStatus(tenantId: string, jobId: string): Promise<TrainingJobProgress> {
|
||||
const response = await apiClient.get<ApiResponse<TrainingJobProgress>>(
|
||||
`/api/v1/training/jobs/${jobId}`
|
||||
`/api/v1/tenants/${tenantId}/training/jobs/${jobId}`
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
@@ -96,7 +96,7 @@ export class TrainingService {
|
||||
/**
|
||||
* Get all training jobs
|
||||
*/
|
||||
async getTrainingHistory(params?: {
|
||||
async getTrainingHistory(tenantId: string, params?: {
|
||||
page?: number;
|
||||
limit?: number;
|
||||
status?: string;
|
||||
@@ -113,14 +113,14 @@ export class TrainingService {
|
||||
/**
|
||||
* Cancel training job
|
||||
*/
|
||||
async cancelTraining(jobId: string): Promise<void> {
|
||||
await apiClient.post(`/api/v1/training/jobs/${jobId}/cancel`);
|
||||
async cancelTraining(tenantId: string, jobId: string): Promise<void> {
|
||||
await apiClient.post(`/api/v1/tenants/${tenantId}/training/jobs/${jobId}/cancel`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get trained models
|
||||
*/
|
||||
async getModels(params?: {
|
||||
async getModels(tenantId: string, params?: {
|
||||
productName?: string;
|
||||
active?: boolean;
|
||||
page?: number;
|
||||
@@ -131,14 +131,14 @@ export class TrainingService {
|
||||
page: number;
|
||||
pages: number;
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/api/v1/training/models', { params });
|
||||
const response = await apiClient.get<ApiResponse<any>>(`/api/v1/tenants/${tenantId}/training/models`, { params });
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get specific model details
|
||||
*/
|
||||
async getModel(modelId: string): Promise<TrainedModel> {
|
||||
async getModel(tenantId: string, modelId: string): Promise<TrainedModel> {
|
||||
const response = await apiClient.get<ApiResponse<TrainedModel>>(
|
||||
`/api/v1/training/models/${modelId}`
|
||||
);
|
||||
@@ -148,9 +148,9 @@ export class TrainingService {
|
||||
/**
|
||||
* Get model metrics
|
||||
*/
|
||||
async getModelMetrics(modelId: string): Promise<ModelMetrics> {
|
||||
async getModelMetrics(tenantId: string, modelId: string): Promise<ModelMetrics> {
|
||||
const response = await apiClient.get<ApiResponse<ModelMetrics>>(
|
||||
`/api/v1/training/models/${modelId}/metrics`
|
||||
`/api/v1/tenants/${tenantId}/training/models/${modelId}/metrics`
|
||||
);
|
||||
return response.data!;
|
||||
}
|
||||
@@ -158,9 +158,9 @@ export class TrainingService {
|
||||
/**
|
||||
* Activate/deactivate model
|
||||
*/
|
||||
async toggleModelStatus(modelId: string, active: boolean): Promise<TrainedModel> {
|
||||
async toggleModelStatus(tenantId: string, modelId: string, active: boolean): Promise<TrainedModel> {
|
||||
const response = await apiClient.patch<ApiResponse<TrainedModel>>(
|
||||
`/api/v1/training/models/${modelId}`,
|
||||
`/api/v1/tenants/${tenantId}/training/models/${modelId}`,
|
||||
{ is_active: active }
|
||||
);
|
||||
return response.data!;
|
||||
@@ -169,16 +169,16 @@ export class TrainingService {
|
||||
/**
|
||||
* Delete model
|
||||
*/
|
||||
async deleteModel(modelId: string): Promise<void> {
|
||||
async deleteModel(tenantId: string, modelId: string): Promise<void> {
|
||||
await apiClient.delete(`/api/v1/training/models/${modelId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Train specific product
|
||||
*/
|
||||
async trainProduct(productName: string, config?: Partial<TrainingConfiguration>): Promise<TrainingJobStatus> {
|
||||
async trainProduct(tenantId: string, productName: string, config?: Partial<TrainingConfiguration>): Promise<TrainingJobStatus> {
|
||||
const response = await apiClient.post<ApiResponse<TrainingJobStatus>>(
|
||||
'/api/v1/training/products/train',
|
||||
`/api/v1/tenants/${tenantId}/training/products/train`,
|
||||
{
|
||||
product_name: productName,
|
||||
...config,
|
||||
@@ -190,7 +190,7 @@ export class TrainingService {
|
||||
/**
|
||||
* Get training statistics
|
||||
*/
|
||||
async getTrainingStats(): Promise<{
|
||||
async getTrainingStats(tenantId: string): Promise<{
|
||||
total_models: number;
|
||||
active_models: number;
|
||||
avg_accuracy: number;
|
||||
@@ -198,21 +198,21 @@ export class TrainingService {
|
||||
products_trained: number;
|
||||
training_time_avg_minutes: number;
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/api/v1/training/stats');
|
||||
const response = await apiClient.get<ApiResponse<any>>(`/api/v1/tenants/${tenantId}/training/stats`);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate training data
|
||||
*/
|
||||
async validateTrainingData(products?: string[]): Promise<{
|
||||
async validateTrainingData(tenantId: string, products?: string[]): Promise<{
|
||||
valid: boolean;
|
||||
errors: string[];
|
||||
warnings: string[];
|
||||
product_data_points: Record<string, number>;
|
||||
recommendation: string;
|
||||
}> {
|
||||
const response = await apiClient.post<ApiResponse<any>>('/api/v1/training/validate', {
|
||||
const response = await apiClient.post<ApiResponse<any>>(`/api/v1/tenants/${tenantId}/training/validate`, {
|
||||
products,
|
||||
});
|
||||
return response.data!;
|
||||
@@ -221,29 +221,29 @@ export class TrainingService {
|
||||
/**
|
||||
* Get training recommendations
|
||||
*/
|
||||
async getTrainingRecommendations(): Promise<{
|
||||
async getTrainingRecommendations(tenantId: string): Promise<{
|
||||
should_retrain: boolean;
|
||||
reasons: string[];
|
||||
recommended_products: string[];
|
||||
optimal_config: TrainingConfiguration;
|
||||
}> {
|
||||
const response = await apiClient.get<ApiResponse<any>>('/api/v1/training/recommendations');
|
||||
const response = await apiClient.get<ApiResponse<any>>(`/api/v1/tenants/${tenantId}/training/recommendations`);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get training logs
|
||||
*/
|
||||
async getTrainingLogs(jobId: string): Promise<string[]> {
|
||||
const response = await apiClient.get<ApiResponse<string[]>>(`/api/v1/training/jobs/${jobId}/logs`);
|
||||
async getTrainingLogs(tenantId: string, jobId: string): Promise<string[]> {
|
||||
const response = await apiClient.get<ApiResponse<string[]>>(`/api/v1/tenants/${tenantId}/training/jobs/${jobId}/logs`);
|
||||
return response.data!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Export model
|
||||
*/
|
||||
async exportModel(modelId: string, format: 'pickle' | 'onnx' = 'pickle'): Promise<Blob> {
|
||||
const response = await apiClient.get(`/api/v1/training/models/${modelId}/export`, {
|
||||
async exportModel(tenantId: string, modelId: string, format: 'pickle' | 'onnx' = 'pickle'): Promise<Blob> {
|
||||
const response = await apiClient.get(`/api/v1/tenants/${tenantId}/training/models/${modelId}/export`, {
|
||||
params: { format },
|
||||
responseType: 'blob',
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user