feat(api, web): интеграция хостера 4VPS.SU — синк VPS, баланса и тарифов
Docker / build (push) Has been cancelled
Docker / build (push) Has been cancelled
Добавлен адаптер 4vps, обобщён pipeline синхронизации через ProviderAdapter и обновлён UI для Panel ID и API Key. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,17 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const API_TYPES = ['billmanager', '4vps', 'none'] as const
|
||||
export type ApiType = (typeof API_TYPES)[number]
|
||||
export const apiTypeSchema = z.enum(API_TYPES).optional().default('none')
|
||||
|
||||
export const SYNC_API_TYPES = ['billmanager', '4vps'] as const
|
||||
export type SyncApiType = (typeof SYNC_API_TYPES)[number]
|
||||
|
||||
export function isSyncApiType(apiType?: string | null): apiType is SyncApiType {
|
||||
const key = String(apiType || '').toLowerCase()
|
||||
return (SYNC_API_TYPES as readonly string[]).includes(key)
|
||||
}
|
||||
|
||||
export const providerSchema = z.object({
|
||||
id: z.string().optional(),
|
||||
name: z.string().min(1, 'Name is required'),
|
||||
@@ -9,7 +21,7 @@ export const providerSchema = z.object({
|
||||
usdRate: z.string().optional().default(''),
|
||||
eurRate: z.string().optional().default(''),
|
||||
notes: z.string().optional().default(''),
|
||||
apiType: z.string().optional().default(''),
|
||||
apiType: apiTypeSchema,
|
||||
apiBaseUrl: z.string().optional().default(''),
|
||||
})
|
||||
|
||||
|
||||
@@ -13,3 +13,32 @@ export function buildApiCredentials(login: string, password: string): string {
|
||||
if (!l) return p
|
||||
return p ? `${l}:${p}` : ''
|
||||
}
|
||||
|
||||
export interface FourVpsCredentials {
|
||||
panelId: number | null
|
||||
apiKey: string
|
||||
}
|
||||
|
||||
/** Разбор 4VPS-кредов формата `panelId:apiKey` (panelId опционален). */
|
||||
export function parseFourVpsCredentials(credentials: string | null | undefined): FourVpsCredentials {
|
||||
const cred = String(credentials ?? '').trim()
|
||||
if (!cred) return { panelId: null, apiKey: '' }
|
||||
const idx = cred.indexOf(':')
|
||||
if (idx <= 0) return { panelId: null, apiKey: cred }
|
||||
const panelPart = cred.slice(0, idx).trim()
|
||||
const apiKey = cred.slice(idx + 1)
|
||||
const panelId = panelPart ? Number.parseInt(panelPart, 10) : null
|
||||
return {
|
||||
panelId: panelId != null && Number.isFinite(panelId) ? panelId : null,
|
||||
apiKey,
|
||||
}
|
||||
}
|
||||
|
||||
/** Собрать 4VPS-креды: panelId + API key. */
|
||||
export function buildFourVpsCredentials(panelId: string, apiKey: string): string {
|
||||
const pid = panelId.trim()
|
||||
const key = apiKey
|
||||
if (!pid && !key) return ''
|
||||
if (!pid) return key
|
||||
return key ? `${pid}:${key}` : pid
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { accountBalanceApi } from './account-balance.js'
|
||||
import { isSyncApiType } from '../contracts/provider.js'
|
||||
import { getPaidUntilDate, type PaidUntilContext } from './paid-until.js'
|
||||
|
||||
export const STALE_SYNC_HOURS = 48
|
||||
@@ -166,12 +167,12 @@ export function computeInventoryHealth(input: InventoryHealthInput): InventoryIs
|
||||
})
|
||||
}
|
||||
|
||||
const bmAccounts = providerAccounts.filter((a) => {
|
||||
const syncAccounts = providerAccounts.filter((a) => {
|
||||
const p = providerById.get(a.providerId)
|
||||
return p?.apiType === 'billmanager' && Boolean((p.apiBaseUrl || '').trim()) && a.apiCredentialsSet
|
||||
return isSyncApiType(p?.apiType) && Boolean((p?.apiBaseUrl || '').trim()) && a.apiCredentialsSet
|
||||
})
|
||||
const staleMs = STALE_SYNC_HOURS * 60 * 60 * 1000
|
||||
const staleAccounts = bmAccounts.filter((a) => {
|
||||
const staleAccounts = syncAccounts.filter((a) => {
|
||||
const t = lastOkSyncFinishedAt(a.id, syncLog)
|
||||
if (t == null) return true
|
||||
return now.getTime() - t > staleMs
|
||||
@@ -226,12 +227,12 @@ export function getStaleSyncAccountIds(
|
||||
now = new Date(),
|
||||
): string[] {
|
||||
const providerById = new Map(providers.map((p) => [p.id, p]))
|
||||
const bmAccounts = providerAccounts.filter((a) => {
|
||||
const syncAccounts = providerAccounts.filter((a) => {
|
||||
const p = providerById.get(a.providerId)
|
||||
return p?.apiType === 'billmanager' && Boolean((p.apiBaseUrl || '').trim()) && a.apiCredentialsSet
|
||||
return isSyncApiType(p?.apiType) && Boolean((p?.apiBaseUrl || '').trim()) && a.apiCredentialsSet
|
||||
})
|
||||
const staleMs = STALE_SYNC_HOURS * 60 * 60 * 1000
|
||||
return bmAccounts
|
||||
return syncAccounts
|
||||
.filter((a) => {
|
||||
const t = lastOkSyncFinishedAt(a.id, syncLog)
|
||||
if (t == null) return true
|
||||
|
||||
Reference in New Issue
Block a user