import { API_OPERATIONS, apiEndpointPath } from "../../src/shared/api/generated/endpoints.ts"; import { countryFlag, stripCountryFlagPrefix } from "./data/countryMeta.js"; const API_BASE_URL = import.meta.env?.VITE_API_BASE_URL || "/api"; const TOKEN_KEY = "hyapp-admin.access-token"; const APP_CODE_KEY = "hyapp-admin.app-code"; const APP_CODE_HEADER = "X-App-Code"; export function getCurrentAppCode() { return normalizeAppCode(window.localStorage.getItem(APP_CODE_KEY) || "lalu"); } export function setCurrentAppCode(value) { const appCode = normalizeAppCode(value) || "lalu"; window.localStorage.setItem(APP_CODE_KEY, appCode); return appCode; } export async function fetchStatisticsOverview({ appCode, countryId, endMs, regionId, seriesEndMs, seriesStartMs, startMs }) { const query = { app_code: normalizeAppCode(appCode) || "lalu" }; if (startMs) { query.start_ms = String(startMs); } if (endMs) { query.end_ms = String(endMs); } if (seriesStartMs) { query.series_start_ms = String(seriesStartMs); } if (seriesEndMs) { query.series_end_ms = String(seriesEndMs); } if (countryId) { query.country_id = String(countryId); } if (regionId && regionId !== "all") { query.region_id = String(regionId); } return fetchDatabiData("/v1/statistics/overview", { appCode, query }); } export async function fetchSelfGameStatisticsOverview({ appCode, countryId, endMs, gameId, regionId, startMs }) { const query = { app_code: normalizeAppCode(appCode) || "lalu" }; if (startMs) { query.start_ms = String(startMs); } if (endMs) { query.end_ms = String(endMs); } if (countryId) { query.country_id = String(countryId); } if (regionId && regionId !== "all") { query.region_id = String(regionId); } if (gameId && gameId !== "all") { query.game_id = String(gameId); } return fetchDatabiData("/v1/statistics/self-games/overview", { appCode, query }); } export async function fetchFilterOptions({ appCode } = {}) { const [apps, regions, countries] = await Promise.all([ fetchDatabiData(apiEndpointPath(API_OPERATIONS.listApps), { appCode }), fetchDatabiData(apiEndpointPath(API_OPERATIONS.listRegions), { appCode, query: { status: "active" } }), fetchDatabiData(apiEndpointPath(API_OPERATIONS.listCountries), { appCode, query: { enabled: true } }) ]); return normalizeFilterOptions({ apps, countries, regions }); } async function fetchDatabiData(path, { appCode, query } = {}) { const response = await fetch(buildURL(path, query), { credentials: "include", headers: requestHeaders(appCode) }); const payload = await readJSON(response); if (isAuthExpired(response, payload)) { redirectToLogin(window); throw new Error(payload.message || "访问凭证已失效"); } if (!response.ok || payload.code !== 0) { throw new Error(payload.message || response.statusText || "请求失败"); } return payload.data; } function buildURL(path, query) { const url = new URL(`${API_BASE_URL}${path}`, window.location.origin); if (query) { Object.entries(query).forEach(([key, value]) => { if (value !== undefined && value !== null && value !== "") { url.searchParams.set(key, String(value)); } }); } return url.toString(); } function requestHeaders(appCode) { const headers = {}; const token = window.localStorage.getItem(TOKEN_KEY); if (token) { headers.Authorization = `Bearer ${token}`; } const normalized = normalizeAppCode(appCode); if (normalized) { headers[APP_CODE_HEADER] = normalized; } return headers; } async function readJSON(response) { const text = await response.text(); if (!text) { return {}; } try { return JSON.parse(text); } catch { return { code: response.ok ? 0 : response.status, message: text }; } } export function isAuthExpired(response, payload) { return response.status === 401 || Number(payload?.code) === 40100; } export function redirectToLogin(targetWindow = window) { targetWindow.localStorage.removeItem(TOKEN_KEY); if (targetWindow.location.pathname !== "/login") { targetWindow.location.replace("/login"); } } function normalizeAppCode(value) { return String(value || "").trim().toLowerCase(); } function normalizeFilterOptions({ apps, countries, regions }) { const regionItems = normalizeList(regions); const regionOptions = [ { countryCodes: [], id: "all", label: "全部区域", value: "all" }, ...regionItems .filter((region) => String(region.regionCode || "").toUpperCase() !== "GLOBAL") .map((region) => { const id = String(region.regionId ?? region.id ?? region.regionCode ?? ""); return { countryCodes: normalizeCodes(region.countries), id, label: [region.name, region.regionCode].filter(Boolean).join(" · ") || `区域 ${id}`, value: id }; }) .filter((region) => region.id) ]; const countryRegionIds = buildCountryRegionIndex(regionOptions); const countryOptions = [ { countryCode: "", id: 0, label: "全部国家", regionIds: [], searchText: "全部国家 all", value: 0 }, ...normalizeList(countries) .map((country) => normalizeCountryOption(country, countryRegionIds)) .filter(Boolean) ]; const appOptions = normalizeList(apps) .map((app) => { const code = normalizeAppCode(app.appCode ?? app.app_code ?? app.code); return { code, label: app.appName || app.app_name || app.name || code, value: code }; }) .filter((app) => app.code); return { appOptions, countryOptions, regionOptions }; } function normalizeList(value) { if (Array.isArray(value)) { return value; } if (Array.isArray(value?.items)) { return value.items; } if (Array.isArray(value?.list)) { return value.list; } return []; } function normalizeCodes(value) { return Array.isArray(value) ? value.map((item) => String(item || "").trim().toUpperCase()).filter(Boolean) : []; } function buildCountryRegionIndex(regions) { const index = new Map(); regions.forEach((region) => { if (region.id === "all") { return; } region.countryCodes.forEach((code) => { const ids = index.get(code) || []; ids.push(region.id); index.set(code, ids); }); }); return index; } function normalizeCountryOption(country, countryRegionIds) { const countryCode = String(country.countryCode ?? country.country_code ?? country.code ?? "").trim().toUpperCase(); const id = Number(country.countryId ?? country.country_id ?? country.id ?? 0); if (!countryCode && !id) { return null; } const rawName = country.countryDisplayName || country.country_display_name || country.countryName || country.country_name || country.name || countryCode; const name = stripCountryFlagPrefix(rawName); const flag = country.flag || countryFlag(countryCode); const label = [flag, name].filter(Boolean).join(" "); const searchText = [flag, rawName, name, countryCode, country.isoAlpha3, country.iso_alpha3, country.phoneCountryCode, country.phone_country_code] .filter(Boolean) .join(" ") .toLowerCase(); return { countryCode, flag, id, label, name, regionIds: countryRegionIds.get(countryCode) || [], searchText, value: id }; }