535 lines
13 KiB
TypeScript
535 lines
13 KiB
TypeScript
import { API_CONFIG, API_ENDPOINTS } from "@/constants/api";
|
||
import {
|
||
ApiListResponse,
|
||
ApiResponse,
|
||
AppleSignInRequest,
|
||
AppleSignInResponse,
|
||
Country,
|
||
FavoriteCheckResponse,
|
||
FavoriteListResponse,
|
||
FavoriteRequest,
|
||
H2HData,
|
||
League,
|
||
LiveScoreMatch,
|
||
Match,
|
||
MatchDetailData,
|
||
OddsData,
|
||
RefreshTokenRequest,
|
||
RefreshTokenResponse,
|
||
SearchResult,
|
||
Sport,
|
||
UpcomingMatch,
|
||
UserProfile,
|
||
} from "@/types/api";
|
||
import axios from "axios";
|
||
import { storage } from "./storage";
|
||
|
||
const apiClient = axios.create({
|
||
baseURL: API_CONFIG.BASE_URL,
|
||
timeout: API_CONFIG.TIMEOUT,
|
||
headers: {
|
||
"Content-Type": "application/json",
|
||
},
|
||
});
|
||
|
||
apiClient.interceptors.request.use(async (config) => {
|
||
const token = await storage.getAccessToken();
|
||
if (token) {
|
||
config.headers.Authorization = `Bearer ${token}`;
|
||
}
|
||
return config;
|
||
});
|
||
|
||
const refreshTokenApi = async (
|
||
request: RefreshTokenRequest,
|
||
): Promise<RefreshTokenResponse> => {
|
||
try {
|
||
const response = await apiClient.post<ApiResponse<RefreshTokenResponse>>(
|
||
API_ENDPOINTS.REFRESH_TOKEN,
|
||
request,
|
||
);
|
||
|
||
if (response.data.code === 0) {
|
||
return response.data.data;
|
||
}
|
||
|
||
throw new Error(response.data.message);
|
||
} catch (error) {
|
||
console.error("Refresh token error:", error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
apiClient.interceptors.response.use(
|
||
(response) => response,
|
||
async (error) => {
|
||
const originalRequest = error.config;
|
||
if (error.response?.status === 401 && !originalRequest._retry) {
|
||
originalRequest._retry = true;
|
||
try {
|
||
const refreshTokenValue = await storage.getRefreshToken();
|
||
if (refreshTokenValue) {
|
||
const res = await refreshTokenApi({
|
||
refreshToken: refreshTokenValue,
|
||
});
|
||
await storage.setAccessToken(res.accessToken);
|
||
originalRequest.headers.Authorization = `Bearer ${res.accessToken}`;
|
||
return apiClient(originalRequest);
|
||
}
|
||
} catch {
|
||
await storage.clear();
|
||
}
|
||
}
|
||
return Promise.reject(error);
|
||
},
|
||
);
|
||
|
||
export const fetchSports = async (): Promise<Sport[]> => {
|
||
try {
|
||
const response = await apiClient.get<ApiResponse<ApiListResponse<Sport>>>(
|
||
API_ENDPOINTS.SPORTS,
|
||
);
|
||
if (response.data.code === 0) {
|
||
return response.data.data.list;
|
||
}
|
||
throw new Error(response.data.message);
|
||
} catch (error) {
|
||
console.error("Fetch sports error:", error);
|
||
// Do not return mock data here — rethrow to let caller handle the error
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
export const fetchCountries = async (): Promise<Country[]> => {
|
||
try {
|
||
const response = await apiClient.get<ApiResponse<ApiListResponse<Country>>>(
|
||
API_ENDPOINTS.COUNTRIES,
|
||
);
|
||
if (response.data.code === 0) {
|
||
return response.data.data.list;
|
||
}
|
||
throw new Error(response.data.message);
|
||
} catch (error) {
|
||
console.error("Fetch countries error:", error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
export const fetchLeagues = async (params: {
|
||
sportId?: number;
|
||
countryKey?: string;
|
||
date?: string;
|
||
page?: number;
|
||
pageSize?: number;
|
||
sortBy?: "name" | "matchCount" | "createdAt";
|
||
sortOrder?: "asc" | "desc";
|
||
}): Promise<ApiListResponse<League>> => {
|
||
try {
|
||
const response = await apiClient.get<ApiResponse<ApiListResponse<League>>>(
|
||
API_ENDPOINTS.LEAGUES,
|
||
{ params },
|
||
);
|
||
if (response.data.code === 0) {
|
||
return response.data.data;
|
||
}
|
||
throw new Error(response.data.message);
|
||
} catch (error) {
|
||
console.error("Fetch leagues error:", error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
export const fetchTodayMatches = async (options: {
|
||
sportId: number;
|
||
date?: Date | string;
|
||
timezone?: string;
|
||
leagueKey?: string;
|
||
page?: number;
|
||
pageSize?: number;
|
||
}): Promise<ApiListResponse<Match>> => {
|
||
try {
|
||
const params: {
|
||
sportId: number;
|
||
date?: string;
|
||
timezone?: string;
|
||
leagueKey?: string;
|
||
page?: number;
|
||
pageSize?: number;
|
||
} = {
|
||
sportId: options.sportId,
|
||
leagueKey: options.leagueKey,
|
||
page: options.page,
|
||
pageSize: options.pageSize,
|
||
};
|
||
|
||
if (options.date) {
|
||
if (options.date instanceof Date) {
|
||
const year = options.date.getFullYear();
|
||
const month = String(options.date.getMonth() + 1).padStart(2, "0");
|
||
const day = String(options.date.getDate()).padStart(2, "0");
|
||
params.date = `${year}-${month}-${day}`;
|
||
} else {
|
||
params.date = options.date;
|
||
}
|
||
}
|
||
|
||
if (options.timezone) {
|
||
params.timezone = options.timezone;
|
||
}
|
||
|
||
const response = await apiClient.get<ApiResponse<ApiListResponse<Match>>>(
|
||
API_ENDPOINTS.MATCHES_TODAY,
|
||
{ params },
|
||
);
|
||
if (response.data.code === 0) {
|
||
// console.log("Fetched today matches:", JSON.stringify(response.data.data));
|
||
return response.data.data;
|
||
}
|
||
throw new Error(response.data.message);
|
||
} catch (error) {
|
||
console.error("Fetch matches error:", error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
export const fetchMatchDetail = async (
|
||
id: string,
|
||
): Promise<MatchDetailData> => {
|
||
try {
|
||
const response = await apiClient.get<ApiResponse<MatchDetailData>>(
|
||
API_ENDPOINTS.MATCH_DETAIL(id),
|
||
);
|
||
if (response.data.code === 0) {
|
||
return response.data.data;
|
||
}
|
||
throw new Error(response.data.message);
|
||
} catch (error) {
|
||
console.error("Fetch match detail error:", error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
export const fetchLiveScore = async (
|
||
sportId: number,
|
||
leagueId?: number,
|
||
timezone?: string,
|
||
): Promise<LiveScoreMatch[]> => {
|
||
console.log("Fetching live scores with params:", {
|
||
sportId,
|
||
leagueId,
|
||
timezone,
|
||
});
|
||
try {
|
||
const params: { sport_id: number; league_id?: number; timezone?: string } =
|
||
{
|
||
sport_id: sportId,
|
||
};
|
||
|
||
if (leagueId) {
|
||
params.league_id = leagueId;
|
||
}
|
||
|
||
if (timezone) {
|
||
params.timezone = timezone;
|
||
}
|
||
|
||
const response = await apiClient.get<ApiResponse<LiveScoreMatch[]>>(
|
||
API_ENDPOINTS.LIVESCORE,
|
||
{ params },
|
||
);
|
||
|
||
if (response.data.code === 0) {
|
||
// console.log("Live score data:", JSON.stringify(response.data.data));
|
||
return response.data.data;
|
||
}
|
||
|
||
throw new Error(response.data.message);
|
||
} catch (error) {
|
||
console.error("Fetch livescore error:", error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
export const fetchUpcomingMatches = async (
|
||
sportId: number,
|
||
leagueKey: string,
|
||
limit: number = 50,
|
||
): Promise<UpcomingMatch[]> => {
|
||
try {
|
||
const response = await apiClient.get<
|
||
ApiResponse<ApiListResponse<UpcomingMatch>>
|
||
>(API_ENDPOINTS.UPCOMING_MATCHES, {
|
||
params: {
|
||
sport_id: sportId,
|
||
leagueKey,
|
||
limit,
|
||
},
|
||
});
|
||
|
||
if (response.data.code === 0) {
|
||
return response.data.data.list;
|
||
}
|
||
|
||
throw new Error(response.data.message);
|
||
} catch (error) {
|
||
console.error("Fetch upcoming matches error:", error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// 获取实时赔率(足球/网球使用 LiveOdds,篮球/板球使用 Odds)
|
||
export const fetchOdds = async (
|
||
sportId: number,
|
||
matchId: number,
|
||
): Promise<OddsData> => {
|
||
try {
|
||
const response = await apiClient.get<ApiResponse<OddsData>>(
|
||
API_ENDPOINTS.ODDS,
|
||
{
|
||
params: {
|
||
sport_id: sportId,
|
||
match_id: matchId,
|
||
},
|
||
},
|
||
);
|
||
|
||
if (response.data.code === 0) {
|
||
return response.data.data;
|
||
}
|
||
|
||
throw new Error(response.data.message);
|
||
} catch (error) {
|
||
console.error("Fetch odds error:", error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// 搜索联赛、球队或球员
|
||
export const fetchSearch = async (
|
||
query: string,
|
||
sportId?: number,
|
||
): Promise<SearchResult> => {
|
||
try {
|
||
const params: { q: string; sportId?: number } = { q: query };
|
||
if (sportId) {
|
||
params.sportId = sportId;
|
||
}
|
||
|
||
const response = await apiClient.get<ApiResponse<SearchResult>>(
|
||
API_ENDPOINTS.SEARCH,
|
||
{ params },
|
||
);
|
||
|
||
if (response.data.code === 0) {
|
||
return response.data.data;
|
||
}
|
||
|
||
throw new Error(response.data.message);
|
||
} catch (error) {
|
||
console.error("Fetch search error:", error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
// 获取两队/两球员之间的历史对战数据
|
||
export const fetchH2H = async (
|
||
sportId: number,
|
||
options: {
|
||
firstTeamId?: number;
|
||
secondTeamId?: number;
|
||
firstPlayerId?: number;
|
||
secondPlayerId?: number;
|
||
timezone?: string;
|
||
},
|
||
): Promise<H2HData> => {
|
||
try {
|
||
const params: {
|
||
sport_id: number;
|
||
first_team_id?: number;
|
||
second_team_id?: number;
|
||
first_player_id?: number;
|
||
second_player_id?: number;
|
||
timezone?: string;
|
||
} = {
|
||
sport_id: sportId,
|
||
};
|
||
|
||
if (options.firstTeamId) {
|
||
params.first_team_id = options.firstTeamId;
|
||
}
|
||
if (options.secondTeamId) {
|
||
params.second_team_id = options.secondTeamId;
|
||
}
|
||
if (options.firstPlayerId) {
|
||
params.first_player_id = options.firstPlayerId;
|
||
}
|
||
if (options.secondPlayerId) {
|
||
params.second_player_id = options.secondPlayerId;
|
||
}
|
||
if (options.timezone) {
|
||
params.timezone = options.timezone;
|
||
}
|
||
|
||
const response = await apiClient.get<ApiResponse<H2HData>>(
|
||
API_ENDPOINTS.H2H,
|
||
{ params },
|
||
);
|
||
|
||
if (response.data.code === 0) {
|
||
return response.data.data;
|
||
}
|
||
|
||
throw new Error(response.data.message);
|
||
} catch (error) {
|
||
console.error("Fetch H2H error:", error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
export const appleSignIn = async (
|
||
request: AppleSignInRequest,
|
||
): Promise<AppleSignInResponse> => {
|
||
try {
|
||
const response = await apiClient.post<ApiResponse<AppleSignInResponse>>(
|
||
API_ENDPOINTS.APPLE_SIGNIN,
|
||
request,
|
||
);
|
||
|
||
if (response.data.code === 0) {
|
||
return response.data.data;
|
||
}
|
||
|
||
throw new Error(response.data.message);
|
||
} catch (error) {
|
||
console.error("Apple sign in error:", error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
export const logout = async (): Promise<string> => {
|
||
try {
|
||
const response = await apiClient.post<ApiResponse<string>>(
|
||
API_ENDPOINTS.LOGOUT,
|
||
);
|
||
|
||
if (response.data.code === 0) {
|
||
return response.data.data;
|
||
}
|
||
|
||
throw new Error(response.data.message);
|
||
} catch (error) {
|
||
console.error("Logout error:", error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
export const refreshToken = refreshTokenApi;
|
||
|
||
export const fetchUserProfile = async (): Promise<UserProfile> => {
|
||
try {
|
||
const response = await apiClient.get<ApiResponse<UserProfile>>(
|
||
API_ENDPOINTS.USER_PROFILE,
|
||
);
|
||
|
||
if (response.data.code === 0) {
|
||
return response.data.data;
|
||
}
|
||
|
||
throw new Error(response.data.message);
|
||
} catch (error) {
|
||
console.error("Fetch user profile error:", error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
export const addFavorite = async (request: FavoriteRequest): Promise<any> => {
|
||
try {
|
||
const token = await storage.getAccessToken();
|
||
if (!token) {
|
||
// throw new Error("Please login first");
|
||
return;
|
||
}
|
||
// console.log("Adding favorite with request:", request);
|
||
const response = await apiClient.post<ApiResponse<any>>(
|
||
API_ENDPOINTS.FAVORITES,
|
||
request,
|
||
);
|
||
if (response.data.code === 0) {
|
||
return response.data.data;
|
||
}
|
||
throw new Error(response.data.message);
|
||
} catch (error) {
|
||
console.error("Add favorite error:", error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
export const removeFavorite = async (request: {
|
||
type: string;
|
||
typeId: string;
|
||
}): Promise<any> => {
|
||
try {
|
||
const token = await storage.getAccessToken();
|
||
if (!token) {
|
||
// throw new Error("Please login first");
|
||
return;
|
||
}
|
||
console.log("Removing favorite with request:", request);
|
||
const response = await apiClient.delete<ApiResponse<any>>(
|
||
API_ENDPOINTS.FAVORITES,
|
||
{ data: request },
|
||
);
|
||
if (response.data.code === 0) {
|
||
return response.data.data;
|
||
}
|
||
throw new Error(response.data.message);
|
||
} catch (error) {
|
||
console.error("Remove favorite error:", error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
export const checkFavorite = async (
|
||
type: string,
|
||
typeId: string,
|
||
): Promise<FavoriteCheckResponse> => {
|
||
try {
|
||
const response = await apiClient.get<ApiResponse<FavoriteCheckResponse>>(
|
||
API_ENDPOINTS.CHECK_FAVORITE,
|
||
{
|
||
params: { type, typeId },
|
||
},
|
||
);
|
||
if (response.data.code === 0) {
|
||
return response.data.data;
|
||
}
|
||
throw new Error(response.data.message);
|
||
} catch (error) {
|
||
console.error("Check favorite error:", error);
|
||
throw error;
|
||
}
|
||
};
|
||
|
||
export const fetchFavorites = async (
|
||
type: string,
|
||
page: number = 1,
|
||
pageSize: number = 20,
|
||
): Promise<FavoriteListResponse> => {
|
||
try {
|
||
const response = await apiClient.get<ApiResponse<FavoriteListResponse>>(
|
||
API_ENDPOINTS.FAVORITES,
|
||
{
|
||
params: { type, page, pageSize },
|
||
},
|
||
);
|
||
if (response.data.code === 0) {
|
||
console.log("Fetched favorites:", JSON.stringify(response.data.data));
|
||
return response.data.data;
|
||
}
|
||
throw new Error(response.data.message);
|
||
} catch (error) {
|
||
console.error("Fetch favorites error:", error);
|
||
throw error;
|
||
}
|
||
};
|