feat: add domain models and repositories for user, spool presets, and filaments
- Introduced SpoolPreset and User domain models with necessary DTOs and utility functions. - Created AuthRepository, FilamentRepository, and SpoolPresetRepository interfaces for authentication and data management. - Implemented UI components for filament display, including ColorSwatch, FilamentCard, and StockBar. - Developed layout components such as Header and Screen for consistent app structure. - Added reusable UI components like Badge, Button, Card, and Input for better user interaction. - Established global constants and theme settings for consistent styling across the application. - Implemented utility functions for filament calculations and formatting. - Created Zustand stores for managing authentication, filament, and preset states. - Configured TypeScript settings for improved development experience.
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import axios, { type AxiosInstance, type InternalAxiosRequestConfig } from 'axios';
|
||||
import * as SecureStore from 'expo-secure-store';
|
||||
import { API_BASE_URL, SECURE_STORE_ACCESS_TOKEN, SECURE_STORE_REFRESH_TOKEN } from '@shared/constants';
|
||||
|
||||
/**
|
||||
* Cliente HTTP centralizado.
|
||||
*
|
||||
* - Injeta o Bearer token automaticamente em todas as requisições.
|
||||
* - Intercepta 401 para tentar refresh do token automaticamente.
|
||||
* - Garante que todas as chamadas apontem para a base URL da API.
|
||||
*/
|
||||
const httpClient: AxiosInstance = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
timeout: 15_000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// ── Interceptor de request: anexa Bearer token ──────────────
|
||||
httpClient.interceptors.request.use(async (config: InternalAxiosRequestConfig) => {
|
||||
const token = await SecureStore.getItemAsync(SECURE_STORE_ACCESS_TOKEN);
|
||||
if (token && config.headers) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
// ── Interceptor de response: refresh automático em 401 ──────
|
||||
let isRefreshing = false;
|
||||
let pendingQueue: Array<{
|
||||
resolve: (token: string) => void;
|
||||
reject: (err: unknown) => void;
|
||||
}> = [];
|
||||
|
||||
function processQueue(error: unknown, token: string | null): void {
|
||||
pendingQueue.forEach(({ resolve, reject }) => {
|
||||
if (error) reject(error);
|
||||
else if (token) resolve(token);
|
||||
});
|
||||
pendingQueue = [];
|
||||
}
|
||||
|
||||
httpClient.interceptors.response.use(
|
||||
(response) => response,
|
||||
async (error) => {
|
||||
const originalRequest = error.config;
|
||||
|
||||
if (error.response?.status === 401 && !originalRequest._retry) {
|
||||
if (isRefreshing) {
|
||||
return new Promise((resolve, reject) => {
|
||||
pendingQueue.push({ resolve, reject });
|
||||
}).then((token) => {
|
||||
originalRequest.headers.Authorization = `Bearer ${token}`;
|
||||
return httpClient(originalRequest);
|
||||
});
|
||||
}
|
||||
|
||||
originalRequest._retry = true;
|
||||
isRefreshing = true;
|
||||
|
||||
try {
|
||||
const refreshToken = await SecureStore.getItemAsync(SECURE_STORE_REFRESH_TOKEN);
|
||||
if (!refreshToken) throw new Error('No refresh token');
|
||||
|
||||
const { data } = await axios.post(`${API_BASE_URL}/auth/refresh`, { refreshToken });
|
||||
|
||||
await SecureStore.setItemAsync(SECURE_STORE_ACCESS_TOKEN, data.access_token);
|
||||
await SecureStore.setItemAsync(SECURE_STORE_REFRESH_TOKEN, data.refresh_token);
|
||||
|
||||
processQueue(null, data.access_token);
|
||||
originalRequest.headers.Authorization = `Bearer ${data.access_token}`;
|
||||
|
||||
return httpClient(originalRequest);
|
||||
} catch (refreshError) {
|
||||
processQueue(refreshError, null);
|
||||
// Limpa tokens inválidos — a store de auth detecta e redireciona para login
|
||||
await SecureStore.deleteItemAsync(SECURE_STORE_ACCESS_TOKEN);
|
||||
await SecureStore.deleteItemAsync(SECURE_STORE_REFRESH_TOKEN);
|
||||
return Promise.reject(refreshError);
|
||||
} finally {
|
||||
isRefreshing = false;
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
|
||||
export { httpClient };
|
||||
Reference in New Issue
Block a user