REFACTOR ALL APIs
This commit is contained in:
@@ -1,7 +1,15 @@
|
||||
// ================================================================
|
||||
// frontend/src/api/services/recipes.ts
|
||||
// ================================================================
|
||||
/**
|
||||
* Recipes service - API communication layer
|
||||
* Handles all recipe-related HTTP requests using the API client
|
||||
* Mirrors backend endpoints exactly for tenant-dependent operations
|
||||
* Recipes Service - Complete backend alignment
|
||||
*
|
||||
* Backend API structure (3-tier architecture):
|
||||
* - ATOMIC: recipes.py, recipe_quality_configs.py
|
||||
* - OPERATIONS: recipe_operations.py (duplicate, activate, feasibility)
|
||||
*
|
||||
* Last Updated: 2025-10-05
|
||||
* Status: ✅ Complete - Zero drift with backend
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client/apiClient';
|
||||
@@ -18,53 +26,20 @@ import type {
|
||||
RecipeQualityConfigurationUpdate,
|
||||
} from '../types/recipes';
|
||||
|
||||
/**
|
||||
* Recipes API service
|
||||
* All methods return promises that resolve to the response data
|
||||
* Follows tenant-dependent routing pattern: /tenants/{tenant_id}/recipes
|
||||
*/
|
||||
export class RecipesService {
|
||||
/**
|
||||
* Get tenant-scoped base URL for recipes
|
||||
*/
|
||||
private getBaseUrl(tenantId: string): string {
|
||||
return `/tenants/${tenantId}/recipes`;
|
||||
}
|
||||
private readonly baseUrl = '/tenants';
|
||||
|
||||
// ===================================================================
|
||||
// ATOMIC: Recipes CRUD
|
||||
// Backend: services/recipes/app/api/recipes.py
|
||||
// ===================================================================
|
||||
|
||||
/**
|
||||
* Create a new recipe
|
||||
* POST /tenants/{tenant_id}/recipes
|
||||
*/
|
||||
async createRecipe(tenantId: string, recipeData: RecipeCreate): Promise<RecipeResponse> {
|
||||
const baseUrl = this.getBaseUrl(tenantId);
|
||||
return apiClient.post<RecipeResponse>(baseUrl, recipeData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recipe by ID with ingredients
|
||||
* GET /tenants/{tenant_id}/recipes/{recipe_id}
|
||||
*/
|
||||
async getRecipe(tenantId: string, recipeId: string): Promise<RecipeResponse> {
|
||||
const baseUrl = this.getBaseUrl(tenantId);
|
||||
return apiClient.get<RecipeResponse>(`${baseUrl}/${recipeId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing recipe
|
||||
* PUT /tenants/{tenant_id}/recipes/{recipe_id}
|
||||
*/
|
||||
async updateRecipe(tenantId: string, recipeId: string, recipeData: RecipeUpdate): Promise<RecipeResponse> {
|
||||
const baseUrl = this.getBaseUrl(tenantId);
|
||||
return apiClient.put<RecipeResponse>(`${baseUrl}/${recipeId}`, recipeData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a recipe
|
||||
* DELETE /tenants/{tenant_id}/recipes/{recipe_id}
|
||||
*/
|
||||
async deleteRecipe(tenantId: string, recipeId: string): Promise<{ message: string }> {
|
||||
const baseUrl = this.getBaseUrl(tenantId);
|
||||
return apiClient.delete<{ message: string }>(`${baseUrl}/${recipeId}`);
|
||||
return apiClient.post<RecipeResponse>(`${this.baseUrl}/${tenantId}/recipes`, recipeData);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -72,7 +47,6 @@ export class RecipesService {
|
||||
* GET /tenants/{tenant_id}/recipes
|
||||
*/
|
||||
async searchRecipes(tenantId: string, params: RecipeSearchParams = {}): Promise<RecipeResponse[]> {
|
||||
const baseUrl = this.getBaseUrl(tenantId);
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
// Add all non-empty parameters to the query string
|
||||
@@ -83,7 +57,7 @@ export class RecipesService {
|
||||
});
|
||||
|
||||
const queryString = searchParams.toString();
|
||||
const url = queryString ? `${baseUrl}?${queryString}` : baseUrl;
|
||||
const url = queryString ? `${this.baseUrl}/${tenantId}/recipes?${queryString}` : `${this.baseUrl}/${tenantId}/recipes`;
|
||||
|
||||
return apiClient.get<RecipeResponse[]>(url);
|
||||
}
|
||||
@@ -97,81 +71,63 @@ export class RecipesService {
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate an existing recipe
|
||||
* POST /tenants/{tenant_id}/recipes/{recipe_id}/duplicate
|
||||
* Get recipe by ID with ingredients
|
||||
* GET /tenants/{tenant_id}/recipes/{recipe_id}
|
||||
*/
|
||||
async duplicateRecipe(tenantId: string, recipeId: string, duplicateData: RecipeDuplicateRequest): Promise<RecipeResponse> {
|
||||
const baseUrl = this.getBaseUrl(tenantId);
|
||||
return apiClient.post<RecipeResponse>(`${baseUrl}/${recipeId}/duplicate`, duplicateData);
|
||||
async getRecipe(tenantId: string, recipeId: string): Promise<RecipeResponse> {
|
||||
return apiClient.get<RecipeResponse>(`${this.baseUrl}/${tenantId}/recipes/${recipeId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate a recipe for production
|
||||
* POST /tenants/{tenant_id}/recipes/{recipe_id}/activate
|
||||
* Update an existing recipe
|
||||
* PUT /tenants/{tenant_id}/recipes/{recipe_id}
|
||||
*/
|
||||
async activateRecipe(tenantId: string, recipeId: string): Promise<RecipeResponse> {
|
||||
const baseUrl = this.getBaseUrl(tenantId);
|
||||
return apiClient.post<RecipeResponse>(`${baseUrl}/${recipeId}/activate`);
|
||||
async updateRecipe(tenantId: string, recipeId: string, recipeData: RecipeUpdate): Promise<RecipeResponse> {
|
||||
return apiClient.put<RecipeResponse>(`${this.baseUrl}/${tenantId}/recipes/${recipeId}`, recipeData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if recipe can be produced with current inventory
|
||||
* GET /tenants/{tenant_id}/recipes/{recipe_id}/feasibility
|
||||
* Delete a recipe
|
||||
* DELETE /tenants/{tenant_id}/recipes/{recipe_id}
|
||||
*/
|
||||
async checkRecipeFeasibility(tenantId: string, recipeId: string, batchMultiplier: number = 1.0): Promise<RecipeFeasibilityResponse> {
|
||||
const baseUrl = this.getBaseUrl(tenantId);
|
||||
const params = new URLSearchParams({ batch_multiplier: String(batchMultiplier) });
|
||||
return apiClient.get<RecipeFeasibilityResponse>(`${baseUrl}/${recipeId}/feasibility?${params}`);
|
||||
async deleteRecipe(tenantId: string, recipeId: string): Promise<{ message: string }> {
|
||||
return apiClient.delete<{ message: string }>(`${this.baseUrl}/${tenantId}/recipes/${recipeId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recipe statistics for dashboard
|
||||
* GET /tenants/{tenant_id}/recipes/statistics/dashboard
|
||||
*/
|
||||
async getRecipeStatistics(tenantId: string): Promise<RecipeStatisticsResponse> {
|
||||
const baseUrl = this.getBaseUrl(tenantId);
|
||||
return apiClient.get<RecipeStatisticsResponse>(`${baseUrl}/statistics/dashboard`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of recipe categories used by tenant
|
||||
* GET /tenants/{tenant_id}/recipes/categories/list
|
||||
*/
|
||||
async getRecipeCategories(tenantId: string): Promise<RecipeCategoriesResponse> {
|
||||
const baseUrl = this.getBaseUrl(tenantId);
|
||||
return apiClient.get<RecipeCategoriesResponse>(`${baseUrl}/categories/list`);
|
||||
}
|
||||
|
||||
// Quality Configuration Methods
|
||||
// ===================================================================
|
||||
// ATOMIC: Quality Configuration CRUD
|
||||
// Backend: services/recipes/app/api/recipe_quality_configs.py
|
||||
// ===================================================================
|
||||
|
||||
/**
|
||||
* Get quality configuration for a recipe
|
||||
* GET /tenants/{tenant_id}/recipes/{recipe_id}/quality-configuration
|
||||
*/
|
||||
async getRecipeQualityConfiguration(
|
||||
tenantId: string,
|
||||
recipeId: string
|
||||
): Promise<RecipeQualityConfiguration> {
|
||||
const baseUrl = this.getBaseUrl(tenantId);
|
||||
return apiClient.get<RecipeQualityConfiguration>(`${baseUrl}/${recipeId}/quality-configuration`);
|
||||
return apiClient.get<RecipeQualityConfiguration>(`${this.baseUrl}/${tenantId}/recipes/${recipeId}/quality-configuration`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update quality configuration for a recipe
|
||||
* PUT /tenants/{tenant_id}/recipes/{recipe_id}/quality-configuration
|
||||
*/
|
||||
async updateRecipeQualityConfiguration(
|
||||
tenantId: string,
|
||||
recipeId: string,
|
||||
qualityConfig: RecipeQualityConfigurationUpdate
|
||||
): Promise<RecipeQualityConfiguration> {
|
||||
const baseUrl = this.getBaseUrl(tenantId);
|
||||
return apiClient.put<RecipeQualityConfiguration>(
|
||||
`${baseUrl}/${recipeId}/quality-configuration`,
|
||||
`${this.baseUrl}/${tenantId}/recipes/${recipeId}/quality-configuration`,
|
||||
qualityConfig
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add quality templates to a recipe stage
|
||||
* POST /tenants/{tenant_id}/recipes/{recipe_id}/quality-configuration/stages/{stage}/templates
|
||||
*/
|
||||
async addQualityTemplatesToStage(
|
||||
tenantId: string,
|
||||
@@ -179,15 +135,15 @@ export class RecipesService {
|
||||
stage: string,
|
||||
templateIds: string[]
|
||||
): Promise<{ message: string }> {
|
||||
const baseUrl = this.getBaseUrl(tenantId);
|
||||
return apiClient.post<{ message: string }>(
|
||||
`${baseUrl}/${recipeId}/quality-configuration/stages/${stage}/templates`,
|
||||
`${this.baseUrl}/${tenantId}/recipes/${recipeId}/quality-configuration/stages/${stage}/templates`,
|
||||
templateIds
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a quality template from a recipe stage
|
||||
* DELETE /tenants/{tenant_id}/recipes/{recipe_id}/quality-configuration/stages/{stage}/templates/{template_id}
|
||||
*/
|
||||
async removeQualityTemplateFromStage(
|
||||
tenantId: string,
|
||||
@@ -195,13 +151,58 @@ export class RecipesService {
|
||||
stage: string,
|
||||
templateId: string
|
||||
): Promise<{ message: string }> {
|
||||
const baseUrl = this.getBaseUrl(tenantId);
|
||||
return apiClient.delete<{ message: string }>(
|
||||
`${baseUrl}/${recipeId}/quality-configuration/stages/${stage}/templates/${templateId}`
|
||||
`${this.baseUrl}/${tenantId}/recipes/${recipeId}/quality-configuration/stages/${stage}/templates/${templateId}`
|
||||
);
|
||||
}
|
||||
|
||||
// ===================================================================
|
||||
// OPERATIONS: Recipe Management
|
||||
// Backend: services/recipes/app/api/recipe_operations.py
|
||||
// ===================================================================
|
||||
|
||||
/**
|
||||
* Duplicate an existing recipe
|
||||
* POST /tenants/{tenant_id}/recipes/{recipe_id}/duplicate
|
||||
*/
|
||||
async duplicateRecipe(tenantId: string, recipeId: string, duplicateData: RecipeDuplicateRequest): Promise<RecipeResponse> {
|
||||
return apiClient.post<RecipeResponse>(`${this.baseUrl}/${tenantId}/recipes/${recipeId}/duplicate`, duplicateData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate a recipe for production
|
||||
* POST /tenants/{tenant_id}/recipes/{recipe_id}/activate
|
||||
*/
|
||||
async activateRecipe(tenantId: string, recipeId: string): Promise<RecipeResponse> {
|
||||
return apiClient.post<RecipeResponse>(`${this.baseUrl}/${tenantId}/recipes/${recipeId}/activate`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if recipe can be produced with current inventory
|
||||
* GET /tenants/{tenant_id}/recipes/{recipe_id}/feasibility
|
||||
*/
|
||||
async checkRecipeFeasibility(tenantId: string, recipeId: string, batchMultiplier: number = 1.0): Promise<RecipeFeasibilityResponse> {
|
||||
const params = new URLSearchParams({ batch_multiplier: String(batchMultiplier) });
|
||||
return apiClient.get<RecipeFeasibilityResponse>(`${this.baseUrl}/${tenantId}/recipes/${recipeId}/feasibility?${params}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recipe statistics for dashboard
|
||||
* GET /tenants/{tenant_id}/recipes/statistics/dashboard
|
||||
*/
|
||||
async getRecipeStatistics(tenantId: string): Promise<RecipeStatisticsResponse> {
|
||||
return apiClient.get<RecipeStatisticsResponse>(`${this.baseUrl}/${tenantId}/recipes/statistics/dashboard`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of recipe categories used by tenant
|
||||
* GET /tenants/{tenant_id}/recipes/categories/list
|
||||
*/
|
||||
async getRecipeCategories(tenantId: string): Promise<RecipeCategoriesResponse> {
|
||||
return apiClient.get<RecipeCategoriesResponse>(`${this.baseUrl}/${tenantId}/recipes/categories/list`);
|
||||
}
|
||||
}
|
||||
|
||||
// Create and export singleton instance
|
||||
export const recipesService = new RecipesService();
|
||||
export default recipesService;
|
||||
export default recipesService;
|
||||
|
||||
Reference in New Issue
Block a user