Refactor API structure and integrate new modular API client

- Replaced legacy apiService with a new modular api client structure.
- Updated AuthContext, OAuthButtons, and AuthCallbackPage to use the new api client.
- Created separate services for auth, sounds, playlists, and users.
- Implemented centralized API configuration and error handling.
- Added support for OAuth providers and token exchange.
- Introduced a Toaster component for notifications in App.
- Updated API endpoints and request handling for better maintainability.
This commit is contained in:
JSC
2025-07-26 19:21:36 +02:00
parent 57429f9414
commit 6ce83c8317
15 changed files with 1055 additions and 236 deletions

214
src/lib/api/README.md Normal file
View File

@@ -0,0 +1,214 @@
# API Library
A generic, maintainable API client library for the Soundboard application.
## Features
- **Generic HTTP Client**: Supports all REST methods with proper TypeScript typing
- **Automatic Token Refresh**: Handles JWT token refresh automatically
- **Error Handling**: Comprehensive error classes for different scenarios
- **Modular Services**: Separate services for different API domains
- **Configuration Management**: Centralized API configuration
- **Backward Compatibility**: Legacy API service for existing code
## Usage
### New API (Recommended)
```typescript
import { api } from '@/lib/api'
// Authentication
const user = await api.auth.login({ email: 'user@example.com', password: 'password' })
const currentUser = await api.auth.getMe()
await api.auth.logout()
// Sounds
const sounds = await api.sounds.list({ page: 1, size: 20 })
const sound = await api.sounds.get(1)
const newSound = await api.sounds.create({ title: 'My Sound', file: audioFile })
// Playlists
const playlists = await api.playlists.list()
const playlist = await api.playlists.create({ name: 'My Playlist' })
// Users (admin only)
const users = await api.users.list()
```
### Alternative Import Style
```typescript
import { authService, soundsService } from '@/lib/api'
// Direct service imports
const user = await authService.login(credentials)
const sounds = await soundsService.list()
```
### Direct Client Usage
```typescript
import { apiClient } from '@/lib/api'
// Generic HTTP requests
const data = await apiClient.get<MyType>('/api/v1/custom-endpoint')
const result = await apiClient.post<ResponseType>('/api/v1/data', requestData)
```
## Services
### AuthService (`api.auth`)
- `login(credentials)` - Authenticate user
- `register(userData)` - Register new user
- `getMe()` - Get current user
- `logout()` - Sign out user
- `getOAuthUrl(provider)` - Get OAuth authorization URL
- `getOAuthProviders()` - Get available OAuth providers
- `exchangeOAuthToken(request)` - Exchange OAuth code for auth cookies
### SoundsService (`api.sounds`)
- `list(params?)` - Get paginated sounds
- `get(id)` - Get specific sound
- `create(data)` - Create new sound
- `update(id, data)` - Update sound
- `delete(id)` - Delete sound
- `upload(file, metadata?)` - Upload sound file
### PlaylistsService (`api.playlists`)
- `list(params?)` - Get paginated playlists
- `get(id)` - Get specific playlist
- `create(data)` - Create new playlist
- `update(id, data)` - Update playlist
- `delete(id)` - Delete playlist
- `addSound(playlistId, soundData)` - Add sound to playlist
- `removeSound(playlistId, soundId)` - Remove sound from playlist
### UsersService (`api.users`)
- `list(params?)` - Get paginated users (admin only)
- `get(id)` - Get specific user
- `update(id, data)` - Update user
- `delete(id)` - Delete user (admin only)
- `changePassword(userId, data)` - Change user password
- `uploadAvatar(userId, file)` - Upload user avatar
## Error Handling
The library provides specific error classes for different scenarios:
```typescript
import {
ApiError,
NetworkError,
TimeoutError,
ValidationError,
AuthenticationError,
AuthorizationError,
NotFoundError,
ServerError
} from '@/lib/api'
try {
await api.auth.login(credentials)
} catch (error) {
if (error instanceof AuthenticationError) {
// Handle login failure
} else if (error instanceof ValidationError) {
// Handle validation errors
console.log(error.fields) // Field-specific errors
} else if (error instanceof NetworkError) {
// Handle network issues
}
}
```
## Configuration
API configuration is centralized in `config.ts`:
```typescript
export const API_CONFIG = {
BASE_URL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000',
TIMEOUT: 30000,
RETRY_ATTEMPTS: 1,
ENDPOINTS: {
AUTH: { /* ... */ },
SOUNDS: { /* ... */ },
// ...
}
}
```
## Request Configuration
All API methods accept optional configuration:
```typescript
// Custom timeout
await api.sounds.list({}, { timeout: 60000 })
// Skip authentication
await api.auth.getOAuthProviders({}, { skipAuth: true })
// Custom headers
await api.sounds.create(data, {
headers: { 'X-Custom-Header': 'value' }
})
// Query parameters
await api.sounds.list({}, {
params: { search: 'query', page: 1 }
})
```
## File Structure
```
src/lib/api/
├── index.ts # Main exports
├── client.ts # Core HTTP client
├── config.ts # API configuration
├── types.ts # TypeScript types
├── errors.ts # Error classes
├── services/
│ ├── auth.ts # Authentication service
│ ├── sounds.ts # Sounds service
│ ├── playlists.ts # Playlists service
│ └── users.ts # Users service
└── README.md # This file
```
## Migration Guide
If migrating from an older API structure:
1. **Use the main API object:**
```typescript
import { api } from '@/lib/api'
// Authentication
const user = await api.auth.login(credentials)
// Resources
const sounds = await api.sounds.list()
const playlists = await api.playlists.list()
```
2. **Or import services directly:**
```typescript
import { authService, soundsService } from '@/lib/api'
const user = await authService.login(credentials)
const sounds = await soundsService.list()
```
3. **For custom requests, use the client:**
```typescript
import { apiClient } from '@/lib/api'
const data = await apiClient.get<MyType>('/api/v1/custom-endpoint')
```