From 58335b4a43c6aed16f6d6ff9bfe8fddbbed5f7bf Mon Sep 17 00:00:00 2001 From: Your Name Date: Fri, 18 Jul 2025 09:01:35 +0000 Subject: [PATCH] feat: Migrate ApiRequestService to React with Axios Co-authored-by: aider (gemini/gemini-2.5-pro) --- logs/phase2_api_service_migration.log | 31 ++++++++ migration2react/src/services/apiService.ts | 85 ++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 logs/phase2_api_service_migration.log create mode 100644 migration2react/src/services/apiService.ts diff --git a/logs/phase2_api_service_migration.log b/logs/phase2_api_service_migration.log new file mode 100644 index 0000000..9efced1 --- /dev/null +++ b/logs/phase2_api_service_migration.log @@ -0,0 +1,31 @@ +# Log: API Service Migration + +**Task**: Migrate Angular's `ApiRequestService` to a reusable React service using `axios`. + +**Analysis of `api-request.service.ts`**: +- Uses `appConfig` to get `baseApiPath`. This path includes the protocol, host, and a trailing slash (e.g., `http://localhost:3000/`). +- Handles setting `Content-Type: application/json` headers. +- Has commented-out logic to add an `Authorization` token from `userInfoService`. +- Provides wrappers for GET, POST, PUT, DELETE methods. +- Includes a special method `postFormData` for `multipart/form-data` uploads. +- Basic error handling in `handleError`. + +**Analysis of `helper.ts` and other services**: +- Some services (`forgotpass.service`, `user-info.service`) import a `baseUrl` from `helper.ts`. +- `helper.ts` gets its value from `environment.backendUrl`. +- The provided `environment.ts` lacks `backendUrl`, but has `apiUrl: 'http://localhost:3000'`. +- **Assumption**: `backendUrl` and `apiUrl` should be treated as the same value. +- Services using `baseUrl` manually add a `/` separator, e.g., `${baseUrl}/api/...`. This is consistent with `baseApiPath` from `appConfig` which already includes the trailing slash. + +**Migration Plan**: +1. Create `migration2react/src/services/apiService.ts`. +2. Use `axios.create()` to set up a default instance with `baseURL` derived from our new `apiConfig.ts`. +3. Implement an `axios` request interceptor to attach the Authorization token. This will replicate the logic from `userInfoService` and `apiRequestService` to get the token from `sessionStorage`. +4. Implement an `axios` response interceptor for global error handling, improving upon the `handleError` method. +5. Export the configured `axios` instance as the default module export. +6. Create and export a separate `postFormData` function for handling file uploads, as its headers and configuration differ. + +**Result**: +- A single, configured `axios` instance (`apiService`) that can be imported and used for all standard API calls. +- A separate utility function (`postFormData`) for specific multipart requests. +- This centralizes API configuration and handling in a standard React pattern. diff --git a/migration2react/src/services/apiService.ts b/migration2react/src/services/apiService.ts new file mode 100644 index 0000000..d2606e5 --- /dev/null +++ b/migration2react/src/services/apiService.ts @@ -0,0 +1,85 @@ +import axios, { AxiosRequestConfig } from 'axios'; +import { BASE_API_PATH } from '../config/apiConfig'; + +const apiService = axios.create({ + baseURL: BASE_API_PATH, + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + }, +}); + +// Request interceptor to add the auth token to headers +// This mirrors the logic in `UserInfoService.getStoredToken` and `ApiRequestService.getHeaders` +apiService.interceptors.request.use((config) => { + // In Angular, user info is stored in sessionStorage under "currentUser" + const currentUserStr = sessionStorage.getItem('currentUser'); + if (currentUserStr) { + const currentUser = JSON.parse(currentUserStr); + if (currentUser && currentUser.token) { + if (config.headers) { + // The Angular service just adds the token, not "Bearer". We will follow that. + config.headers['Authorization'] = currentUser.token; + } + } + } + return config; +}, (error) => { + return Promise.reject(error); +}); + +// Response interceptor for global error handling +// This is a more robust version of `ApiRequestService.handleError` +apiService.interceptors.response.use( + (response) => response, + (error) => { + if (error.response) { + console.error("API Error Response:", error.response); + const { status } = error.response; + let errorMessage = 'Server Error'; + + switch (status) { + case 401: + errorMessage = 'Forbidden. Please check your credentials or login again.'; + // In a real app, you'd likely redirect to the login page here. + // e.g., window.location.href = '/login'; + break; + case 404: + errorMessage = 'The requested resource was not found.'; + break; + case 408: + errorMessage = 'The request timed out.'; + break; + case 500: + errorMessage = 'An internal server error occurred.'; + break; + default: + errorMessage = error.response.data?.message || 'An unexpected error occurred.'; + } + return Promise.reject(new Error(errorMessage)); + } else if (error.request) { + console.error("API Error Request (no response received):", error.request); + return Promise.reject(new Error('Network error. Please check your connection and try again.')); + } else { + console.error('API Setup Error:', error.message); + return Promise.reject(new Error(error.message)); + } + } +); + +export default apiService; + +/** + * A dedicated function for POSTing multipart/form-data. + * This is based on `ApiRequestService.postFormData`. + * @param url The endpoint URL (relative to base path). + * @param formData The FormData object to send. + * @returns A promise with the axios response. + */ +export const postFormData = (url: string, formData: FormData) => { + return apiService.post(url, formData, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); +};