import axios, { AxiosError } from 'axios'; import { useMutation, useQuery, UseMutationOptions, UseQueryOptions, } from 'react-query'; import { request } from "~/shared/libs"; import { clearAdminAuth, persistAdminAuth } from "~/shared/libs/admin-auth"; export type AdminServiceState = { name: string; status?: string; last_reported_seconds: number | null; }; export type AdminOverviewResponse = { project: { host: string | null; name: string; privacy: string; }; codebase: { branch: string | null; commit: string | null; }; node: { id: string; service_wallet: string; ton_master: string; highload_wallet: string | null; }; runtime: { python: string; implementation: string; platform: string; utc_now: string; }; ipfs: Record & { identity?: Record | { error: string }; bitswap?: Record | { error: string }; repo?: Record | { error: string }; }; content: { encrypted_total: number; upload_sessions_total: number; derivatives_ready: number; }; ton: { host: string | null; api_key_configured: boolean; testnet: boolean; }; services: AdminServiceState[]; }; export type AdminStorageResponse = { directories: Array<{ label: string; path: string; exists: boolean; file_count: number; size_bytes: number; }>; disk: null | { path: string; total_bytes: number; used_bytes: number; free_bytes: number; percent_used: number | null; }; derivatives: { ready: number; processing: number; pending: number; failed: number; total_bytes: number; }; }; export type AdminUploadsContentDerivative = { kind: string; status: string; size_bytes: number | null; error: string | null; created_at: string | null; updated_at: string | null; attempts: number; download_url: string | null; }; export type AdminUploadsContentUploadHistoryItem = { state: string; at: string | null; error: string | null; filename: string | null; }; export type AdminUploadsContentIpfs = { pin_state: string | null; pin_error: string | null; bytes_total: number | null; bytes_fetched: number | null; pinned_at: string | null; updated_at: string | null; } | null; export type AdminUploadsContentStored = { stored_id: number | null; type: string | null; owner_address: string | null; user_id: number | null; status: string | null; content_url: string | null; download_url: string | null; created: string | null; updated: string | null; user?: { id: number; telegram_id: number; username: string | null; first_name: string | null; last_name: string | null; }; } | null; export type AdminUploadsContentLinks = { web_view: string | null; start_app: string | null; api_view: string | null; download_primary: string | null; download_derivatives: Array<{ kind: string; url: string; size_bytes: number | null; }>; }; export type AdminUploadsContentFlags = { issues: boolean; processing: boolean; ready: boolean; unindexed: boolean; }; export type AdminContentDistributionNode = { node_id: number | null; is_local: boolean; host: string | null; public_host: string | null; version: string | null; role: string | null; last_seen: string | null; content: { encrypted_cid: string | null; content_type: string | null; size_bytes: number | null; preview_enabled: boolean | null; updated_at: string | null; metadata_cid?: string | null; issuer_node_id?: string | null; }; links: { web_view: string | null; api_view: string | null; gateway_view: string | null; }; }; export type AdminContentDistribution = { local_present: boolean; nodes: AdminContentDistributionNode[]; }; export type AdminUploadsContentStatus = { upload_state: string | null; conversion_state: string | null; ipfs_state: string | null; onchain: { indexed: boolean; onchain_index: number | null; item_address: string | null; license_count?: number; } | null; }; export type AdminUploadsContent = { encrypted_cid: string; metadata_cid: string | null; content_hash: string | null; title: string; description: string | null; content_type: string; size: { encrypted: number | null; plain: number | null; }; created_at: string | null; updated_at: string | null; status: AdminUploadsContentStatus; upload_history: AdminUploadsContentUploadHistoryItem[]; derivative_summary: Record; derivatives: AdminUploadsContentDerivative[]; ipfs: AdminUploadsContentIpfs; stored: AdminUploadsContentStored; links: AdminUploadsContentLinks; distribution: AdminContentDistribution; flags?: AdminUploadsContentFlags; }; export type AdminUploadsResponse = { total: number; states: Record; recent: Array<{ id: string; filename: string | null; size_bytes: number | null; state: string; encrypted_cid: string | null; error: string | null; updated_at: string; created_at: string; }>; contents: AdminUploadsContent[]; matching_total?: number; filter?: string[]; search?: string | null; limit?: number; scan?: number; scanned?: number; category_totals?: Record; }; export type AdminUploadsQueryParams = { filter?: string | string[]; search?: string; limit?: number; scan?: number; }; type AdminUploadsQueryKey = ['admin', 'uploads', string | null, string | null, number | null, number | null]; export type AdminUserWalletConnectionSummary = { primary_address: string | null; active_count: number; total_count: number; last_connected_at: string | null; connections: Array<{ id: number; address: string; network: string; invalidated: boolean; created_at: string | null; updated_at: string | null; }>; }; export type AdminUserContentSummary = { total: number; onchain: number; local: number; disabled: number; }; export type AdminUserLicensesSummary = { total: number; active: number; by_type: Record; by_status: Record; }; export type AdminUserStarsSummary = { total: number; paid: number; unpaid: number; amount_total: number; amount_paid: number; amount_unpaid: number; }; export type AdminUserIpActivity = { last: { ip: string | null; type: string | null; seen_at: string | null; } | null; unique_ips: number; recent: Array<{ ip: string | null; type: string | null; seen_at: string | null; }>; }; export type AdminUserItem = { id: number; is_admin: boolean; telegram_id: number; username: string | null; first_name: string | null; last_name: string | null; lang_code: string | null; created_at: string | null; updated_at: string | null; last_use: string | null; meta: { ref_id?: string | null; referrer_id?: string | null; }; wallets: AdminUserWalletConnectionSummary; content: AdminUserContentSummary; licenses: AdminUserLicensesSummary; stars: AdminUserStarsSummary; ip_activity: AdminUserIpActivity; }; export type AdminEventItem = { id: number; origin_public_key: string | null; origin_host: string | null; seq: number; uid: string; event_type: string; status: string; created_at: string | null; received_at: string | null; applied_at: string | null; payload: Record; links: Record; }; export type AdminEventsResponse = { total: number; limit: number; offset: number; filters?: Record; items: AdminEventItem[]; available_filters: { types: Record; statuses: Record; origins: Record; }; }; export type AdminEventsQueryParams = { limit?: number; offset?: number; type?: string | string[]; status?: string | string[]; origin?: string | string[]; search?: string; }; type AdminEventsQueryKey = ['admin', 'events', string]; export type AdminUsersSummary = { users_returned: number; admins_total: number; wallets_total: number; wallets_active: number; licenses_total: number; licenses_active: number; stars_total: number; stars_paid: number; stars_unpaid: number; stars_amount_total: number; stars_amount_paid: number; stars_amount_unpaid: number; unique_ips_total: number; }; export type AdminUsersResponse = { total: number; limit: number; offset: number; search?: string | null; filters?: Record; has_more: boolean; items: AdminUserItem[]; summary: AdminUsersSummary; }; export type AdminUsersQueryParams = { limit?: number; offset?: number; search?: string; }; type AdminUsersQueryKey = ['admin', 'users', string]; export type AdminLicenseItem = { id: number; type: string | null; status: string | null; license_type: number | null; onchain_address: string | null; owner_address: string | null; user: { id: number; telegram_id: number; username: string | null; first_name: string | null; last_name: string | null; } | null; wallet_connection: { id: number; address: string; network: string; invalidated: boolean; created_at: string | null; updated_at: string | null; } | null; content: { id: number | null; hash: string | null; cid: string | null; title: string | null; type: string | null; owner_address: string | null; onchain_index: number | null; user_id: number | null; download_url: string | null; } | null; created_at: string | null; updated_at: string | null; meta: Record; links: { tonviewer: string | null; content_view: string | null; }; }; export type AdminLicensesCounts = { status: Record; type: Record; license_type: Record; }; export type AdminLicensesResponse = { total: number; limit: number; offset: number; search?: string | null; filters?: Record; has_more: boolean; items: AdminLicenseItem[]; counts: AdminLicensesCounts; }; export type AdminLicensesQueryParams = { limit?: number; offset?: number; search?: string; type?: string | string[]; status?: string | string[]; license_type?: number | string | Array; user_id?: number; owner?: string; address?: string; content_hash?: string; }; type AdminLicensesQueryKey = ['admin', 'licenses', string]; export type AdminStarsInvoiceItem = { id: number; external_id: string; type: string | null; amount: number; paid: boolean; invoice_url: string | null; created_at: string | null; status: 'paid' | 'pending'; user: { id: number; telegram_id: number; username: string | null; first_name: string | null; last_name: string | null; } | null; content: { id: number | null; hash: string | null; cid: string | null; title: string | null; onchain_index: number | null; owner_address: string | null; user_id: number | null; download_url: string | null; } | null; }; export type AdminStarsStats = { total: number; paid: number; unpaid: number; amount_total: number; amount_paid: number; amount_unpaid: number; by_type: Record; }; export type AdminStarsResponse = { total: number; limit: number; offset: number; search?: string | null; filters?: Record; has_more: boolean; items: AdminStarsInvoiceItem[]; stats: AdminStarsStats; }; export type AdminStarsQueryParams = { limit?: number; offset?: number; search?: string; type?: string | string[]; paid?: boolean; user_id?: number; content_hash?: string; }; type AdminStarsQueryKey = ['admin', 'stars', string]; export type AdminSystemResponse = { env: Record; service_config: Array<{ key: string; value: string | null; raw: string | null; }>; services: AdminServiceState[]; blockchain_tasks: Record; latest_index_items: Array<{ encrypted_cid: string | null; updated_at: string; }>; telegram_bots: Array<{ role: string; username: string; url: string; }>; }; export type AdminBlockchainResponse = { counts: Record; recent: Array<{ id: string; destination: string | null; amount: string | null; status: string; epoch: number | null; seqno: number | null; transaction_hash: string | null; updated: string; }>; }; export type AdminNodesResponse = { items: Array<{ ip: string | null; port: number | null; public_key: string | null; role: 'trusted' | 'read-only' | 'deny'; version?: string | null; last_seen?: string | null; notes?: string | null; }>; }; // --------- Состояние сети (новый раздел) --------- export type AdminNetworkMember = { node_id: string; public_key: string | null; public_host: string | null; version: string | null; role: string; ip: string | null; asn: number | null; ip_first_octet: number | null; reachability_ratio: number; last_update: number | null; accepts_inbound: boolean; is_bootstrap: boolean; receipts_total?: number; receipts_asn_unique?: number; }; export type AdminNetworkPerNodeReplication = Record< string, { leases_held: number; leaderships: number; sample_contents: string[]; } >; export type AdminNetworkResponse = { summary: { n_estimate: number; n_estimate_trusted: number; active_trusted: number; members_total: number; active: number; islands: number; replication_conflicts: { under: number; over: number }; config: { heartbeat_interval: number; lease_ttl: number; gossip_interval_sec: number; gossip_backoff_base_sec: number; gossip_backoff_cap_sec: number; }; }; members: AdminNetworkMember[]; per_node_replication: AdminNetworkPerNodeReplication; receipts: Array<{ target_id: string; issuer_id: string; asn: number | null; timestamp: number | null; status: 'valid' | 'bad_signature' | 'unknown_issuer' | 'mismatch_node_id' | 'unknown'; }>; }; export const useAdminNetwork = ( options?: QueryOptions, ) => { return useQuery( ['admin', 'network'], async () => { const { data } = await request.get('/admin.network'); return data; }, { ...defaultQueryOptions, ...options, }, ); }; export type AdminStatusResponse = { ipfs: { bitswap: Record; repo: Record; }; pin_counts: Record; derivatives: { ready: number; processing: number; pending: number; failed: number; total_bytes: number; }; convert_backlog: number; limits: { DERIVATIVE_CACHE_MAX_GB: number; DERIVATIVE_CACHE_TTL_DAYS: number; SYNC_MAX_CONCURRENT_PINS: number; SYNC_DISK_LOW_WATERMARK_PCT: number; }; }; export type AdminCacheSetLimitsPayload = { max_gb: number; ttl_days: number; }; export type AdminCacheCleanupPayload = | { mode: 'ttl'; } | { mode?: 'fit'; max_gb: number; }; export type AdminSyncSetLimitsPayload = { max_concurrent_pins: number; disk_low_watermark_pct: number; }; export type AdminNodeSetRolePayload = { public_key?: string; host?: string; role: 'trusted' | 'read-only' | 'deny'; }; const defaultQueryOptions = { staleTime: 30_000, refetchOnWindowFocus: false, } as const; type QueryOptions = UseQueryOptions< TData, AxiosError, TData, TQueryKey >; type MutationOptions = UseMutationOptions< TData, AxiosError, TVariables >; export const useAdminOverview = ( options?: QueryOptions, ) => { return useQuery( ['admin', 'overview'], async () => { const { data } = await request.get('/admin.overview'); return data; }, { ...defaultQueryOptions, ...options, }, ); }; export const useAdminStorage = ( options?: QueryOptions, ) => { return useQuery( ['admin', 'storage'], async () => { const { data } = await request.get('/admin.storage'); return data; }, { ...defaultQueryOptions, ...options, }, ); }; export const useAdminUploads = ( params?: AdminUploadsQueryParams, options?: QueryOptions, ) => { const filterParts = Array.isArray(params?.filter) ? params.filter.filter(Boolean) : params?.filter ? [params.filter] : []; const filterValue = filterParts.length > 0 ? filterParts.join(',') : null; const searchValue = params?.search?.trim() || null; const limitValue = params?.limit ?? null; const scanValue = params?.scan ?? null; const queryKey: AdminUploadsQueryKey = ['admin', 'uploads', filterValue, searchValue, limitValue, scanValue]; const queryParams: Record = { filter: filterValue ?? undefined, search: searchValue ?? undefined, limit: limitValue ?? undefined, scan: scanValue ?? undefined, }; return useQuery( queryKey, async () => { const { data } = await request.get('/admin.uploads', { params: queryParams, }); return data; }, { ...defaultQueryOptions, ...options, }, ); }; export const useAdminUsers = ( params?: AdminUsersQueryParams, options?: QueryOptions, ) => { const normalizedSearch = params?.search?.trim() || null; const paramsKeyPayload = { limit: params?.limit ?? null, offset: params?.offset ?? null, search: normalizedSearch, }; const paramsKey = JSON.stringify(paramsKeyPayload); const queryParams: Record = { limit: params?.limit, offset: params?.offset, search: normalizedSearch ?? undefined, }; return useQuery( ['admin', 'users', paramsKey], async () => { const { data } = await request.get('/admin.users', { params: queryParams, }); return data; }, { ...defaultQueryOptions, ...options, }, ); }; export const useAdminLicenses = ( params?: AdminLicensesQueryParams, options?: QueryOptions, ) => { const normalizedSearch = params?.search?.trim() || null; const typeValue = Array.isArray(params?.type) ? params.type.filter(Boolean).join(',') : params?.type ?? null; const statusValue = Array.isArray(params?.status) ? params.status.filter(Boolean).join(',') : params?.status ?? null; const licenseTypeValue = Array.isArray(params?.license_type) ? params.license_type.filter((value) => value !== undefined && value !== null).join(',') : params?.license_type ?? null; const paramsKeyPayload = { limit: params?.limit ?? null, offset: params?.offset ?? null, search: normalizedSearch, type: typeValue, status: statusValue, license_type: licenseTypeValue, user_id: params?.user_id ?? null, owner: params?.owner ?? null, address: params?.address ?? null, content_hash: params?.content_hash ?? null, }; const paramsKey = JSON.stringify(paramsKeyPayload); const queryParams: Record = { limit: params?.limit, offset: params?.offset, search: normalizedSearch ?? undefined, type: typeValue ?? undefined, status: statusValue ?? undefined, license_type: licenseTypeValue ?? undefined, user_id: params?.user_id, owner: params?.owner ?? undefined, address: params?.address ?? undefined, content_hash: params?.content_hash ?? undefined, }; return useQuery( ['admin', 'licenses', paramsKey], async () => { const { data } = await request.get('/admin.licenses', { params: queryParams, }); return data; }, { ...defaultQueryOptions, ...options, }, ); }; export const useAdminStars = ( params?: AdminStarsQueryParams, options?: QueryOptions, ) => { const normalizedSearch = params?.search?.trim() || null; const typeValue = Array.isArray(params?.type) ? params.type.filter(Boolean).join(',') : params?.type ?? null; const paramsKeyPayload = { limit: params?.limit ?? null, offset: params?.offset ?? null, search: normalizedSearch, type: typeValue, paid: typeof params?.paid === 'boolean' ? params.paid : null, user_id: params?.user_id ?? null, content_hash: params?.content_hash ?? null, }; const paramsKey = JSON.stringify(paramsKeyPayload); const queryParams: Record = { limit: params?.limit, offset: params?.offset, search: normalizedSearch ?? undefined, type: typeValue ?? undefined, user_id: params?.user_id, content_hash: params?.content_hash ?? undefined, }; if (typeof params?.paid === 'boolean') { queryParams.paid = params.paid ? 1 : 0; } return useQuery( ['admin', 'stars', paramsKey], async () => { const { data } = await request.get('/admin.stars', { params: queryParams, }); return data; }, { ...defaultQueryOptions, ...options, }, ); }; export const useAdminEvents = ( params?: AdminEventsQueryParams, options?: QueryOptions, ) => { const normalizedSearch = params?.search?.trim() || null; const typeValue = Array.isArray(params?.type) ? params.type.filter(Boolean).join(',') : params?.type ?? null; const statusValue = Array.isArray(params?.status) ? params.status.filter(Boolean).join(',') : params?.status ?? null; const originValue = Array.isArray(params?.origin) ? params.origin.filter(Boolean).join(',') : params?.origin ?? null; const paramsKeyPayload = { limit: params?.limit ?? null, offset: params?.offset ?? null, search: normalizedSearch, type: typeValue, status: statusValue, origin: originValue, }; const paramsKey = JSON.stringify(paramsKeyPayload); const queryParams: Record = { limit: params?.limit, offset: params?.offset, search: normalizedSearch ?? undefined, type: typeValue ?? undefined, status: statusValue ?? undefined, origin: originValue ?? undefined, }; return useQuery( ['admin', 'events', paramsKey], async () => { const { data } = await request.get('/admin.events', { params: queryParams, }); return data; }, { ...defaultQueryOptions, ...options, }, ); }; export const useAdminSystem = ( options?: QueryOptions, ) => { return useQuery( ['admin', 'system'], async () => { const { data } = await request.get('/admin.system'); return data; }, { ...defaultQueryOptions, ...options, }, ); }; export const useAdminBlockchain = ( options?: QueryOptions, ) => { return useQuery( ['admin', 'blockchain'], async () => { const { data } = await request.get('/admin.blockchain'); return data; }, { ...defaultQueryOptions, ...options, }, ); }; export const useAdminNodes = ( options?: QueryOptions, ) => { return useQuery( ['admin', 'nodes'], async () => { const { data } = await request.get('/admin.nodes'); return data; }, { ...defaultQueryOptions, ...options, }, ); }; export const useAdminStatus = ( options?: QueryOptions, ) => { return useQuery( ['admin', 'status'], async () => { const { data } = await request.get('/admin.status'); return data; }, { ...defaultQueryOptions, ...options, }, ); }; export type AdminLoginResponse = { ok: true; cookie_name?: string; header_name?: string; max_age?: number; }; export const useAdminLogin = ( options?: MutationOptions, ) => { return useMutation( async ({ secret }) => { const { data } = await request.post('/admin.login', { secret }); persistAdminAuth({ token: secret, headerName: data.header_name, cookieName: data.cookie_name, maxAge: data.max_age, }); return data; }, options, ); }; export const useAdminLogout = ( options?: MutationOptions<{ ok: true }, void>, ) => { return useMutation<{ ok: true }, AxiosError, void>( async () => { const { data } = await request.post<{ ok: true }>('/admin.logout'); clearAdminAuth(); return data; }, options, ); }; export const useAdminCacheSetLimits = ( options?: MutationOptions<{ ok: true }, AdminCacheSetLimitsPayload>, ) => { return useMutation<{ ok: true }, AxiosError, AdminCacheSetLimitsPayload>( async (payload) => { const { data } = await request.post<{ ok: true }>('/admin.cache.setLimits', payload); return data; }, options, ); }; export const useAdminCacheCleanup = ( options?: MutationOptions<{ ok: true; removed?: number }, AdminCacheCleanupPayload>, ) => { return useMutation<{ ok: true; removed?: number }, AxiosError, AdminCacheCleanupPayload>( async (payload) => { const { data } = await request.post<{ ok: true; removed?: number }>('/admin.cache.cleanup', payload); return data; }, options, ); }; export const useAdminSyncSetLimits = ( options?: MutationOptions<{ ok: true }, AdminSyncSetLimitsPayload>, ) => { return useMutation<{ ok: true }, AxiosError, AdminSyncSetLimitsPayload>( async (payload) => { const { data } = await request.post<{ ok: true }>('/admin.sync.setLimits', payload); return data; }, options, ); }; export const useAdminNodeSetRole = ( options?: MutationOptions<{ ok: true; node: { ip: string | null; public_key: string | null; role: string } }, AdminNodeSetRolePayload>, ) => { return useMutation< { ok: true; node: { ip: string | null; public_key: string | null; role: string } }, AxiosError, AdminNodeSetRolePayload >( async (payload) => { const { data } = await request.post<{ ok: true; node: { ip: string | null; public_key: string | null; role: string } }>( '/admin.node.setRole', payload, ); return data; }, options, ); }; export const useAdminSetUserAdmin = ( options?: MutationOptions<{ ok: true; user: { id: number; is_admin: boolean } }, { user_id: number; is_admin: boolean }>, ) => { return useMutation<{ ok: true; user: { id: number; is_admin: boolean } }, AxiosError, { user_id: number; is_admin: boolean }>( async (payload) => { const { data } = await request.post<{ ok: true; user: { id: number; is_admin: boolean } }>('/admin.users.setAdmin', payload); return data; }, options, ); }; export const useAdminSyncLimits = () => { const cacheSetLimits = useAdminCacheSetLimits(); const syncSetLimits = useAdminSyncSetLimits(); return { cacheSetLimits, syncSetLimits }; }; export const isUnauthorizedError = (error: unknown) => { if (!error) { return false; } if (axios.isAxiosError(error)) { return error.response?.status === 401; } return false; };