initial
This commit is contained in:
30
frontend/src/api/auth.ts
Normal file
30
frontend/src/api/auth.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import client from './client'
|
||||
import type { TokenResponse, User } from '@/types'
|
||||
|
||||
export interface RegisterData {
|
||||
login: string
|
||||
password: string
|
||||
nickname: string
|
||||
}
|
||||
|
||||
export interface LoginData {
|
||||
login: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export const authApi = {
|
||||
register: async (data: RegisterData): Promise<TokenResponse> => {
|
||||
const response = await client.post<TokenResponse>('/auth/register', data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
login: async (data: LoginData): Promise<TokenResponse> => {
|
||||
const response = await client.post<TokenResponse>('/auth/login', data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
me: async (): Promise<User> => {
|
||||
const response = await client.get<User>('/auth/me')
|
||||
return response.data
|
||||
},
|
||||
}
|
||||
34
frontend/src/api/client.ts
Normal file
34
frontend/src/api/client.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import axios, { AxiosError } from 'axios'
|
||||
|
||||
const API_URL = import.meta.env.VITE_API_URL || '/api/v1'
|
||||
|
||||
const client = axios.create({
|
||||
baseURL: API_URL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
})
|
||||
|
||||
// Request interceptor to add auth token
|
||||
client.interceptors.request.use((config) => {
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
return config
|
||||
})
|
||||
|
||||
// Response interceptor to handle errors
|
||||
client.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error: AxiosError<{ detail: string }>) => {
|
||||
if (error.response?.status === 401) {
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('user')
|
||||
window.location.href = '/login'
|
||||
}
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
export default client
|
||||
11
frontend/src/api/feed.ts
Normal file
11
frontend/src/api/feed.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import client from './client'
|
||||
import type { FeedResponse } from '@/types'
|
||||
|
||||
export const feedApi = {
|
||||
get: async (marathonId: number, limit = 20, offset = 0): Promise<FeedResponse> => {
|
||||
const response = await client.get<FeedResponse>(`/marathons/${marathonId}/feed`, {
|
||||
params: { limit, offset },
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
}
|
||||
70
frontend/src/api/games.ts
Normal file
70
frontend/src/api/games.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import client from './client'
|
||||
import type { Game, Challenge } from '@/types'
|
||||
|
||||
export interface CreateGameData {
|
||||
title: string
|
||||
download_url: string
|
||||
genre?: string
|
||||
cover_url?: string
|
||||
}
|
||||
|
||||
export interface CreateChallengeData {
|
||||
title: string
|
||||
description: string
|
||||
type: string
|
||||
difficulty: string
|
||||
points: number
|
||||
estimated_time?: number
|
||||
proof_type: string
|
||||
proof_hint?: string
|
||||
}
|
||||
|
||||
export const gamesApi = {
|
||||
list: async (marathonId: number): Promise<Game[]> => {
|
||||
const response = await client.get<Game[]>(`/marathons/${marathonId}/games`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
get: async (id: number): Promise<Game> => {
|
||||
const response = await client.get<Game>(`/games/${id}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
create: async (marathonId: number, data: CreateGameData): Promise<Game> => {
|
||||
const response = await client.post<Game>(`/marathons/${marathonId}/games`, data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
delete: async (id: number): Promise<void> => {
|
||||
await client.delete(`/games/${id}`)
|
||||
},
|
||||
|
||||
uploadCover: async (id: number, file: File): Promise<Game> => {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
const response = await client.post<Game>(`/games/${id}/cover`, formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
|
||||
// Challenges
|
||||
getChallenges: async (gameId: number): Promise<Challenge[]> => {
|
||||
const response = await client.get<Challenge[]>(`/games/${gameId}/challenges`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
createChallenge: async (gameId: number, data: CreateChallengeData): Promise<Challenge> => {
|
||||
const response = await client.post<Challenge>(`/games/${gameId}/challenges`, data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
deleteChallenge: async (id: number): Promise<void> => {
|
||||
await client.delete(`/challenges/${id}`)
|
||||
},
|
||||
|
||||
generateChallenges: async (marathonId: number): Promise<{ message: string }> => {
|
||||
const response = await client.post<{ message: string }>(`/marathons/${marathonId}/generate-challenges`)
|
||||
return response.data
|
||||
},
|
||||
}
|
||||
5
frontend/src/api/index.ts
Normal file
5
frontend/src/api/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export { authApi } from './auth'
|
||||
export { marathonsApi } from './marathons'
|
||||
export { gamesApi } from './games'
|
||||
export { wheelApi } from './wheel'
|
||||
export { feedApi } from './feed'
|
||||
64
frontend/src/api/marathons.ts
Normal file
64
frontend/src/api/marathons.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import client from './client'
|
||||
import type { Marathon, MarathonListItem, LeaderboardEntry, ParticipantInfo, User } from '@/types'
|
||||
|
||||
export interface CreateMarathonData {
|
||||
title: string
|
||||
description?: string
|
||||
start_date: string
|
||||
duration_days?: number
|
||||
}
|
||||
|
||||
export interface ParticipantWithUser extends ParticipantInfo {
|
||||
user: User
|
||||
}
|
||||
|
||||
export const marathonsApi = {
|
||||
list: async (): Promise<MarathonListItem[]> => {
|
||||
const response = await client.get<MarathonListItem[]>('/marathons')
|
||||
return response.data
|
||||
},
|
||||
|
||||
get: async (id: number): Promise<Marathon> => {
|
||||
const response = await client.get<Marathon>(`/marathons/${id}`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
create: async (data: CreateMarathonData): Promise<Marathon> => {
|
||||
const response = await client.post<Marathon>('/marathons', data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
update: async (id: number, data: Partial<CreateMarathonData>): Promise<Marathon> => {
|
||||
const response = await client.patch<Marathon>(`/marathons/${id}`, data)
|
||||
return response.data
|
||||
},
|
||||
|
||||
delete: async (id: number): Promise<void> => {
|
||||
await client.delete(`/marathons/${id}`)
|
||||
},
|
||||
|
||||
start: async (id: number): Promise<Marathon> => {
|
||||
const response = await client.post<Marathon>(`/marathons/${id}/start`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
finish: async (id: number): Promise<Marathon> => {
|
||||
const response = await client.post<Marathon>(`/marathons/${id}/finish`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
join: async (inviteCode: string): Promise<Marathon> => {
|
||||
const response = await client.post<Marathon>('/marathons/join', { invite_code: inviteCode })
|
||||
return response.data
|
||||
},
|
||||
|
||||
getParticipants: async (id: number): Promise<ParticipantWithUser[]> => {
|
||||
const response = await client.get<ParticipantWithUser[]>(`/marathons/${id}/participants`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
getLeaderboard: async (id: number): Promise<LeaderboardEntry[]> => {
|
||||
const response = await client.get<LeaderboardEntry[]>(`/marathons/${id}/leaderboard`)
|
||||
return response.data
|
||||
},
|
||||
}
|
||||
41
frontend/src/api/wheel.ts
Normal file
41
frontend/src/api/wheel.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import client from './client'
|
||||
import type { SpinResult, Assignment, CompleteResult, DropResult } from '@/types'
|
||||
|
||||
export const wheelApi = {
|
||||
spin: async (marathonId: number): Promise<SpinResult> => {
|
||||
const response = await client.post<SpinResult>(`/marathons/${marathonId}/spin`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
getCurrentAssignment: async (marathonId: number): Promise<Assignment | null> => {
|
||||
const response = await client.get<Assignment | null>(`/marathons/${marathonId}/current-assignment`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
complete: async (
|
||||
assignmentId: number,
|
||||
data: { proof_url?: string; comment?: string; proof_file?: File }
|
||||
): Promise<CompleteResult> => {
|
||||
const formData = new FormData()
|
||||
if (data.proof_url) formData.append('proof_url', data.proof_url)
|
||||
if (data.comment) formData.append('comment', data.comment)
|
||||
if (data.proof_file) formData.append('proof_file', data.proof_file)
|
||||
|
||||
const response = await client.post<CompleteResult>(`/assignments/${assignmentId}/complete`, formData, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
|
||||
drop: async (assignmentId: number): Promise<DropResult> => {
|
||||
const response = await client.post<DropResult>(`/assignments/${assignmentId}/drop`)
|
||||
return response.data
|
||||
},
|
||||
|
||||
getHistory: async (marathonId: number, limit = 20, offset = 0): Promise<Assignment[]> => {
|
||||
const response = await client.get<Assignment[]>(`/marathons/${marathonId}/my-history`, {
|
||||
params: { limit, offset },
|
||||
})
|
||||
return response.data
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user