feat: update API client and remove unused services; enhance error handling and configuration
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -12,6 +12,11 @@ dist
|
|||||||
dist-ssr
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
|
# Environment variables
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
|||||||
69
README.md
69
README.md
@@ -1,69 +0,0 @@
|
|||||||
# React + TypeScript + Vite
|
|
||||||
|
|
||||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
|
||||||
|
|
||||||
Currently, two official plugins are available:
|
|
||||||
|
|
||||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
|
|
||||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
|
||||||
|
|
||||||
## Expanding the ESLint configuration
|
|
||||||
|
|
||||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
|
||||||
|
|
||||||
```js
|
|
||||||
export default tseslint.config([
|
|
||||||
globalIgnores(['dist']),
|
|
||||||
{
|
|
||||||
files: ['**/*.{ts,tsx}'],
|
|
||||||
extends: [
|
|
||||||
// Other configs...
|
|
||||||
|
|
||||||
// Remove tseslint.configs.recommended and replace with this
|
|
||||||
...tseslint.configs.recommendedTypeChecked,
|
|
||||||
// Alternatively, use this for stricter rules
|
|
||||||
...tseslint.configs.strictTypeChecked,
|
|
||||||
// Optionally, add this for stylistic rules
|
|
||||||
...tseslint.configs.stylisticTypeChecked,
|
|
||||||
|
|
||||||
// Other configs...
|
|
||||||
],
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
// other options...
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// eslint.config.js
|
|
||||||
import reactX from 'eslint-plugin-react-x'
|
|
||||||
import reactDom from 'eslint-plugin-react-dom'
|
|
||||||
|
|
||||||
export default tseslint.config([
|
|
||||||
globalIgnores(['dist']),
|
|
||||||
{
|
|
||||||
files: ['**/*.{ts,tsx}'],
|
|
||||||
extends: [
|
|
||||||
// Other configs...
|
|
||||||
// Enable lint rules for React
|
|
||||||
reactX.configs['recommended-typescript'],
|
|
||||||
// Enable lint rules for React DOM
|
|
||||||
reactDom.configs.recommended,
|
|
||||||
],
|
|
||||||
languageOptions: {
|
|
||||||
parserOptions: {
|
|
||||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
// other options...
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
```
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
import { cva, VariantProps } from "class-variance-authority"
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import { PanelLeftIcon } from "lucide-react"
|
import { PanelLeftIcon } from "lucide-react"
|
||||||
|
|
||||||
import { useIsMobile } from "@/hooks/use-mobile"
|
import { useIsMobile } from "@/hooks/use-mobile"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useTheme } from "next-themes"
|
import { useTheme } from "next-themes"
|
||||||
import { Toaster as Sonner, ToasterProps } from "sonner"
|
import { Toaster as Sonner, type ToasterProps } from "sonner"
|
||||||
|
|
||||||
const Toaster = ({ ...props }: ToasterProps) => {
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
const { theme = "system" } = useTheme()
|
const { theme = "system" } = useTheme()
|
||||||
|
|||||||
@@ -36,23 +36,13 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const login = async (credentials: LoginRequest) => {
|
const login = async (credentials: LoginRequest) => {
|
||||||
try {
|
const user = await api.auth.login(credentials)
|
||||||
const user = await api.auth.login(credentials)
|
setUser(user)
|
||||||
setUser(user)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Login failed:', error)
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const register = async (data: RegisterRequest) => {
|
const register = async (data: RegisterRequest) => {
|
||||||
try {
|
const user = await api.auth.register(data)
|
||||||
const user = await api.auth.register(data)
|
setUser(user)
|
||||||
setUser(user)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Registration failed:', error)
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
@@ -62,7 +52,6 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
|||||||
|
|
||||||
const value: AuthContextType = {
|
const value: AuthContextType = {
|
||||||
user,
|
user,
|
||||||
token: user ? 'cookie-based' : null,
|
|
||||||
login,
|
login,
|
||||||
register,
|
register,
|
||||||
logout,
|
logout,
|
||||||
|
|||||||
@@ -1,214 +0,0 @@
|
|||||||
# 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')
|
|
||||||
```
|
|
||||||
@@ -81,7 +81,7 @@ export class BaseApiClient implements ApiClient {
|
|||||||
throw createApiError(retryResponse, errorData)
|
throw createApiError(retryResponse, errorData)
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.safeParseJSON(retryResponse)
|
return await this.safeParseJSON(retryResponse) as T
|
||||||
} catch (refreshError) {
|
} catch (refreshError) {
|
||||||
this.handleAuthenticationFailure()
|
this.handleAuthenticationFailure()
|
||||||
throw refreshError
|
throw refreshError
|
||||||
@@ -97,7 +97,7 @@ export class BaseApiClient implements ApiClient {
|
|||||||
return {} as T
|
return {} as T
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.safeParseJSON(response)
|
return await this.safeParseJSON(response) as T
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
clearTimeout(timeoutId)
|
clearTimeout(timeoutId)
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// API Configuration
|
// API Configuration
|
||||||
export const API_CONFIG = {
|
export const API_CONFIG = {
|
||||||
BASE_URL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000',
|
BASE_URL: 'http://localhost:8000',
|
||||||
TIMEOUT: 30000, // 30 seconds
|
TIMEOUT: 30000, // 30 seconds
|
||||||
RETRY_ATTEMPTS: 1,
|
RETRY_ATTEMPTS: 1,
|
||||||
|
|
||||||
@@ -17,27 +17,6 @@ export const API_CONFIG = {
|
|||||||
OAUTH_CALLBACK: (provider: string) => `/api/v1/auth/${provider}/callback`,
|
OAUTH_CALLBACK: (provider: string) => `/api/v1/auth/${provider}/callback`,
|
||||||
EXCHANGE_OAUTH_TOKEN: '/api/v1/auth/exchange-oauth-token',
|
EXCHANGE_OAUTH_TOKEN: '/api/v1/auth/exchange-oauth-token',
|
||||||
},
|
},
|
||||||
SOUNDS: {
|
|
||||||
LIST: '/api/v1/sounds',
|
|
||||||
CREATE: '/api/v1/sounds',
|
|
||||||
GET: (id: string | number) => `/api/v1/sounds/${id}`,
|
|
||||||
UPDATE: (id: string | number) => `/api/v1/sounds/${id}`,
|
|
||||||
DELETE: (id: string | number) => `/api/v1/sounds/${id}`,
|
|
||||||
UPLOAD: '/api/v1/sounds/upload',
|
|
||||||
},
|
|
||||||
PLAYLISTS: {
|
|
||||||
LIST: '/api/v1/playlists',
|
|
||||||
CREATE: '/api/v1/playlists',
|
|
||||||
GET: (id: string | number) => `/api/v1/playlists/${id}`,
|
|
||||||
UPDATE: (id: string | number) => `/api/v1/playlists/${id}`,
|
|
||||||
DELETE: (id: string | number) => `/api/v1/playlists/${id}`,
|
|
||||||
},
|
|
||||||
USERS: {
|
|
||||||
LIST: '/api/v1/users',
|
|
||||||
GET: (id: string | number) => `/api/v1/users/${id}`,
|
|
||||||
UPDATE: (id: string | number) => `/api/v1/users/${id}`,
|
|
||||||
DELETE: (id: string | number) => `/api/v1/users/${id}`,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
|||||||
@@ -6,22 +6,13 @@ export * from './errors'
|
|||||||
|
|
||||||
// Services
|
// Services
|
||||||
export * from './services/auth'
|
export * from './services/auth'
|
||||||
export * from './services/sounds'
|
|
||||||
export * from './services/playlists'
|
|
||||||
export * from './services/users'
|
|
||||||
|
|
||||||
// Main API object for convenient access
|
// Main API object for convenient access
|
||||||
import { authService } from './services/auth'
|
import { authService } from './services/auth'
|
||||||
import { soundsService } from './services/sounds'
|
|
||||||
import { playlistsService } from './services/playlists'
|
|
||||||
import { usersService } from './services/users'
|
|
||||||
import { apiClient } from './client'
|
import { apiClient } from './client'
|
||||||
|
|
||||||
export const api = {
|
export const api = {
|
||||||
auth: authService,
|
auth: authService,
|
||||||
sounds: soundsService,
|
|
||||||
playlists: playlistsService,
|
|
||||||
users: usersService,
|
|
||||||
client: apiClient,
|
client: apiClient,
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
|
|||||||
@@ -1,94 +0,0 @@
|
|||||||
import { apiClient } from '../client'
|
|
||||||
import { API_CONFIG } from '../config'
|
|
||||||
import type { PaginatedResponse } from '../types'
|
|
||||||
import type { Sound } from './sounds'
|
|
||||||
|
|
||||||
export interface Playlist {
|
|
||||||
id: number
|
|
||||||
name: string
|
|
||||||
description?: string
|
|
||||||
user_id: number
|
|
||||||
is_public: boolean
|
|
||||||
sounds: Sound[]
|
|
||||||
created_at: string
|
|
||||||
updated_at: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreatePlaylistRequest {
|
|
||||||
name: string
|
|
||||||
description?: string
|
|
||||||
is_public?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpdatePlaylistRequest {
|
|
||||||
name?: string
|
|
||||||
description?: string
|
|
||||||
is_public?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlaylistsListParams {
|
|
||||||
page?: number
|
|
||||||
size?: number
|
|
||||||
search?: string
|
|
||||||
user_id?: number
|
|
||||||
is_public?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AddSoundToPlaylistRequest {
|
|
||||||
sound_id: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export class PlaylistsService {
|
|
||||||
/**
|
|
||||||
* Get list of playlists with pagination
|
|
||||||
*/
|
|
||||||
async list(params?: PlaylistsListParams): Promise<PaginatedResponse<Playlist>> {
|
|
||||||
return apiClient.get<PaginatedResponse<Playlist>>(API_CONFIG.ENDPOINTS.PLAYLISTS.LIST, {
|
|
||||||
params: params as Record<string, string | number | boolean | undefined>
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a specific playlist by ID
|
|
||||||
*/
|
|
||||||
async get(id: string | number): Promise<Playlist> {
|
|
||||||
return apiClient.get<Playlist>(API_CONFIG.ENDPOINTS.PLAYLISTS.GET(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new playlist
|
|
||||||
*/
|
|
||||||
async create(data: CreatePlaylistRequest): Promise<Playlist> {
|
|
||||||
return apiClient.post<Playlist>(API_CONFIG.ENDPOINTS.PLAYLISTS.CREATE, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update an existing playlist
|
|
||||||
*/
|
|
||||||
async update(id: string | number, data: UpdatePlaylistRequest): Promise<Playlist> {
|
|
||||||
return apiClient.patch<Playlist>(API_CONFIG.ENDPOINTS.PLAYLISTS.UPDATE(id), data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a playlist
|
|
||||||
*/
|
|
||||||
async delete(id: string | number): Promise<void> {
|
|
||||||
return apiClient.delete<void>(API_CONFIG.ENDPOINTS.PLAYLISTS.DELETE(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a sound to a playlist
|
|
||||||
*/
|
|
||||||
async addSound(playlistId: string | number, data: AddSoundToPlaylistRequest): Promise<Playlist> {
|
|
||||||
return apiClient.post<Playlist>(`${API_CONFIG.ENDPOINTS.PLAYLISTS.GET(playlistId)}/sounds`, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a sound from a playlist
|
|
||||||
*/
|
|
||||||
async removeSound(playlistId: string | number, soundId: string | number): Promise<Playlist> {
|
|
||||||
return apiClient.delete<Playlist>(`${API_CONFIG.ENDPOINTS.PLAYLISTS.GET(playlistId)}/sounds/${soundId}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const playlistsService = new PlaylistsService()
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
import { apiClient } from '../client'
|
|
||||||
import { API_CONFIG } from '../config'
|
|
||||||
import type { PaginatedResponse } from '../types'
|
|
||||||
|
|
||||||
export interface Sound {
|
|
||||||
id: number
|
|
||||||
title: string
|
|
||||||
description?: string
|
|
||||||
file_url: string
|
|
||||||
duration: number
|
|
||||||
file_size: number
|
|
||||||
mime_type: string
|
|
||||||
play_count: number
|
|
||||||
user_id: number
|
|
||||||
created_at: string
|
|
||||||
updated_at: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateSoundRequest {
|
|
||||||
title: string
|
|
||||||
description?: string
|
|
||||||
file: File
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpdateSoundRequest {
|
|
||||||
title?: string
|
|
||||||
description?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SoundsListParams {
|
|
||||||
page?: number
|
|
||||||
size?: number
|
|
||||||
search?: string
|
|
||||||
user_id?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SoundsService {
|
|
||||||
/**
|
|
||||||
* Get list of sounds with pagination
|
|
||||||
*/
|
|
||||||
async list(params?: SoundsListParams): Promise<PaginatedResponse<Sound>> {
|
|
||||||
return apiClient.get<PaginatedResponse<Sound>>(API_CONFIG.ENDPOINTS.SOUNDS.LIST, {
|
|
||||||
params: params as Record<string, string | number | boolean | undefined>
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a specific sound by ID
|
|
||||||
*/
|
|
||||||
async get(id: string | number): Promise<Sound> {
|
|
||||||
return apiClient.get<Sound>(API_CONFIG.ENDPOINTS.SOUNDS.GET(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new sound
|
|
||||||
*/
|
|
||||||
async create(data: CreateSoundRequest): Promise<Sound> {
|
|
||||||
const formData = new FormData()
|
|
||||||
formData.append('title', data.title)
|
|
||||||
if (data.description) {
|
|
||||||
formData.append('description', data.description)
|
|
||||||
}
|
|
||||||
formData.append('file', data.file)
|
|
||||||
|
|
||||||
return apiClient.post<Sound>(API_CONFIG.ENDPOINTS.SOUNDS.CREATE, formData)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update an existing sound
|
|
||||||
*/
|
|
||||||
async update(id: string | number, data: UpdateSoundRequest): Promise<Sound> {
|
|
||||||
return apiClient.patch<Sound>(API_CONFIG.ENDPOINTS.SOUNDS.UPDATE(id), data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a sound
|
|
||||||
*/
|
|
||||||
async delete(id: string | number): Promise<void> {
|
|
||||||
return apiClient.delete<void>(API_CONFIG.ENDPOINTS.SOUNDS.DELETE(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Upload a sound file
|
|
||||||
*/
|
|
||||||
async upload(file: File, metadata?: { title?: string; description?: string }): Promise<Sound> {
|
|
||||||
const formData = new FormData()
|
|
||||||
formData.append('file', file)
|
|
||||||
|
|
||||||
if (metadata?.title) {
|
|
||||||
formData.append('title', metadata.title)
|
|
||||||
}
|
|
||||||
if (metadata?.description) {
|
|
||||||
formData.append('description', metadata.description)
|
|
||||||
}
|
|
||||||
|
|
||||||
return apiClient.post<Sound>(API_CONFIG.ENDPOINTS.SOUNDS.UPLOAD, formData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const soundsService = new SoundsService()
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
import type { User } from '@/types/auth'
|
|
||||||
import { apiClient } from '../client'
|
|
||||||
import { API_CONFIG } from '../config'
|
|
||||||
import type { PaginatedResponse } from '../types'
|
|
||||||
|
|
||||||
export interface UpdateUserRequest {
|
|
||||||
name?: string
|
|
||||||
email?: string
|
|
||||||
picture?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UsersListParams {
|
|
||||||
page?: number
|
|
||||||
size?: number
|
|
||||||
search?: string
|
|
||||||
role?: string
|
|
||||||
is_active?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChangePasswordRequest {
|
|
||||||
current_password: string
|
|
||||||
new_password: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UsersService {
|
|
||||||
/**
|
|
||||||
* Get list of users with pagination (admin only)
|
|
||||||
*/
|
|
||||||
async list(params?: UsersListParams): Promise<PaginatedResponse<User>> {
|
|
||||||
return apiClient.get<PaginatedResponse<User>>(API_CONFIG.ENDPOINTS.USERS.LIST, {
|
|
||||||
params: params as Record<string, string | number | boolean | undefined>
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a specific user by ID
|
|
||||||
*/
|
|
||||||
async get(id: string | number): Promise<User> {
|
|
||||||
return apiClient.get<User>(API_CONFIG.ENDPOINTS.USERS.GET(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update user profile
|
|
||||||
*/
|
|
||||||
async update(id: string | number, data: UpdateUserRequest): Promise<User> {
|
|
||||||
return apiClient.patch<User>(API_CONFIG.ENDPOINTS.USERS.UPDATE(id), data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a user (admin only)
|
|
||||||
*/
|
|
||||||
async delete(id: string | number): Promise<void> {
|
|
||||||
return apiClient.delete<void>(API_CONFIG.ENDPOINTS.USERS.DELETE(id))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Change user password
|
|
||||||
*/
|
|
||||||
async changePassword(userId: string | number, data: ChangePasswordRequest): Promise<void> {
|
|
||||||
return apiClient.post<void>(`${API_CONFIG.ENDPOINTS.USERS.GET(userId)}/change-password`, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Upload user avatar
|
|
||||||
*/
|
|
||||||
async uploadAvatar(userId: string | number, file: File): Promise<User> {
|
|
||||||
const formData = new FormData()
|
|
||||||
formData.append('avatar', file)
|
|
||||||
|
|
||||||
return apiClient.post<User>(`${API_CONFIG.ENDPOINTS.USERS.GET(userId)}/avatar`, formData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const usersService = new UsersService()
|
|
||||||
@@ -20,15 +20,11 @@ export function AuthCallbackPage() {
|
|||||||
throw new Error('No authorization code received')
|
throw new Error('No authorization code received')
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Exchanging OAuth code for tokens...')
|
|
||||||
|
|
||||||
// Exchange the temporary code for proper auth cookies
|
// Exchange the temporary code for proper auth cookies
|
||||||
const result = await api.auth.exchangeOAuthToken({ code })
|
const result = await api.auth.exchangeOAuthToken({ code })
|
||||||
console.log('Token exchange successful:', result)
|
|
||||||
|
|
||||||
// Now get the user info
|
// Now get the user info
|
||||||
const user = await api.auth.getMe()
|
const user = await api.auth.getMe()
|
||||||
console.log('User info retrieved:', user)
|
|
||||||
|
|
||||||
// Update auth context
|
// Update auth context
|
||||||
if (setUser) setUser(user)
|
if (setUser) setUser(user)
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ export interface RegisterRequest {
|
|||||||
|
|
||||||
export interface AuthContextType {
|
export interface AuthContextType {
|
||||||
user: User | null
|
user: User | null
|
||||||
token: string | null
|
|
||||||
login: (credentials: LoginRequest) => Promise<void>
|
login: (credentials: LoginRequest) => Promise<void>
|
||||||
register: (data: RegisterRequest) => Promise<void>
|
register: (data: RegisterRequest) => Promise<void>
|
||||||
logout: () => Promise<void>
|
logout: () => Promise<void>
|
||||||
|
|||||||
Reference in New Issue
Block a user