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 <[email protected]>
This commit is contained in:
@@ -5,7 +5,8 @@ import type {
|
||||
BalanceLedgerRow,
|
||||
} from '@/types/entities'
|
||||
import { accountBalanceApi } from '@/lib/account'
|
||||
import { accountBillmanagerUiReady } from '@/lib/billmanager'
|
||||
import { isSyncApiType } from '@cfdm/shared/contracts/provider'
|
||||
import { accountSyncUiReady } from '@/lib/provider-sync'
|
||||
import {
|
||||
accountHasApiLedgerMismatch,
|
||||
getStaleSyncAccountIds,
|
||||
@@ -40,7 +41,7 @@ export function getAccountHealthFlags(
|
||||
const provider = ctx.providers.find((p) => p.id === account.providerId)
|
||||
const flags: AccountHealthFlag[] = []
|
||||
|
||||
if (provider?.apiType === 'billmanager' && !account.apiCredentialsSet) {
|
||||
if (isSyncApiType(provider?.apiType) && !account.apiCredentialsSet) {
|
||||
flags.push('no-creds')
|
||||
}
|
||||
|
||||
@@ -99,5 +100,5 @@ export function countLowBalanceAccounts(
|
||||
|
||||
export function isAccountSyncable(account: ProviderAccount, providers: Provider[]): boolean {
|
||||
const provider = providers.find((p) => p.id === account.providerId)
|
||||
return accountBillmanagerUiReady(account, provider)
|
||||
return accountSyncUiReady(account, provider)
|
||||
}
|
||||
|
||||
@@ -101,10 +101,10 @@ export const api = {
|
||||
`/api/sync/${encodeURIComponent(accountId)}/balance`,
|
||||
),
|
||||
|
||||
testConnection: (apiBaseUrl: string, apiCredentials: string) =>
|
||||
testConnection: (apiBaseUrl: string, apiCredentials: string, apiType?: string) =>
|
||||
fetchApi('/api/sync/test-connection', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ apiBaseUrl, apiCredentials }),
|
||||
body: JSON.stringify({ apiBaseUrl, apiCredentials, apiType }),
|
||||
}),
|
||||
|
||||
fetchSyncStatus: () => fetchApi('/api/sync/status'),
|
||||
|
||||
@@ -1,45 +1,12 @@
|
||||
import type { ProviderAccount, Provider } from '@/types/entities'
|
||||
|
||||
export function providerByIdMap(providers: Provider[]): Map<string, Provider> {
|
||||
return new Map(providers.map((p) => [p.id, p]))
|
||||
}
|
||||
|
||||
export function billmanagerSyncableAccounts(
|
||||
providerAccounts: ProviderAccount[],
|
||||
providers: Provider[],
|
||||
): ProviderAccount[] {
|
||||
const pmap = providerByIdMap(providers)
|
||||
return providerAccounts.filter((a) => {
|
||||
const p = pmap.get(a.providerId)
|
||||
return p?.apiType === 'billmanager' && Boolean((p.apiBaseUrl || '').trim()) && a.apiCredentialsSet
|
||||
})
|
||||
}
|
||||
|
||||
export function accountBillmanagerUiReady(
|
||||
account: ProviderAccount,
|
||||
provider?: Provider | null,
|
||||
): boolean {
|
||||
return (
|
||||
provider?.apiType === 'billmanager' &&
|
||||
Boolean((provider.apiBaseUrl || '').trim()) &&
|
||||
Boolean(account.apiCredentialsSet)
|
||||
)
|
||||
}
|
||||
|
||||
export function accountUsesBillmanagerBalanceApi(
|
||||
account: ProviderAccount,
|
||||
provider?: Provider | null,
|
||||
): boolean {
|
||||
return provider?.apiType === 'billmanager' && account.balance_api != null
|
||||
}
|
||||
|
||||
export function accountSelectLabel(
|
||||
account: ProviderAccount,
|
||||
providerById: Map<string, Provider>,
|
||||
scopedProviderId?: string,
|
||||
): string {
|
||||
const name = account.name?.trim() || '—'
|
||||
if (scopedProviderId) return name
|
||||
const providerName = providerById.get(account.providerId)?.name ?? '—'
|
||||
return `${providerName} / ${name}`
|
||||
}
|
||||
export {
|
||||
providerByIdMap,
|
||||
syncableAccounts,
|
||||
billmanagerSyncableAccounts,
|
||||
accountSyncUiReady,
|
||||
accountBillmanagerUiReady,
|
||||
accountUsesApiBalance,
|
||||
accountUsesBillmanagerBalanceApi,
|
||||
accountSelectLabel,
|
||||
isSyncApiType,
|
||||
accountCredentialLabels,
|
||||
} from './provider-sync'
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { isSyncApiType } from '@cfdm/shared/contracts/provider'
|
||||
|
||||
import type { ProviderAccount, Provider } from '@/types/entities'
|
||||
|
||||
export { isSyncApiType }
|
||||
|
||||
export function providerByIdMap(providers: Provider[]): Map<string, Provider> {
|
||||
return new Map(providers.map((p) => [p.id, p]))
|
||||
}
|
||||
|
||||
export function syncableAccounts(
|
||||
providerAccounts: ProviderAccount[],
|
||||
providers: Provider[],
|
||||
): ProviderAccount[] {
|
||||
const pmap = providerByIdMap(providers)
|
||||
return providerAccounts.filter((a) => {
|
||||
const p = pmap.get(a.providerId)
|
||||
return isSyncApiType(p?.apiType) && Boolean((p?.apiBaseUrl || '').trim()) && a.apiCredentialsSet
|
||||
})
|
||||
}
|
||||
|
||||
/** @deprecated Используйте syncableAccounts */
|
||||
export const billmanagerSyncableAccounts = syncableAccounts
|
||||
|
||||
export function accountSyncUiReady(
|
||||
account: ProviderAccount,
|
||||
provider?: Provider | null,
|
||||
): boolean {
|
||||
return (
|
||||
isSyncApiType(provider?.apiType) &&
|
||||
Boolean((provider?.apiBaseUrl || '').trim()) &&
|
||||
Boolean(account.apiCredentialsSet)
|
||||
)
|
||||
}
|
||||
|
||||
/** @deprecated Используйте accountSyncUiReady */
|
||||
export const accountBillmanagerUiReady = accountSyncUiReady
|
||||
|
||||
export function accountUsesApiBalance(
|
||||
account: ProviderAccount,
|
||||
provider?: Provider | null,
|
||||
): boolean {
|
||||
return isSyncApiType(provider?.apiType) && account.balance_api != null
|
||||
}
|
||||
|
||||
/** @deprecated Используйте accountUsesApiBalance */
|
||||
export const accountUsesBillmanagerBalanceApi = accountUsesApiBalance
|
||||
|
||||
export function accountSelectLabel(
|
||||
account: ProviderAccount,
|
||||
providerById: Map<string, Provider>,
|
||||
scopedProviderId?: string,
|
||||
): string {
|
||||
const name = account.name?.trim() || '—'
|
||||
if (scopedProviderId) return name
|
||||
const providerName = providerById.get(account.providerId)?.name ?? '—'
|
||||
return `${providerName} / ${name}`
|
||||
}
|
||||
|
||||
export function accountCredentialLabels(apiType?: string | null): {
|
||||
loginLabel: string
|
||||
passwordLabel: string
|
||||
loginPlaceholder: string
|
||||
} {
|
||||
if (String(apiType).toLowerCase() === '4vps') {
|
||||
return {
|
||||
loginLabel: 'Panel ID',
|
||||
passwordLabel: 'API Key',
|
||||
loginPlaceholder: '1',
|
||||
}
|
||||
}
|
||||
return {
|
||||
loginLabel: 'Логин API',
|
||||
passwordLabel: 'Пароль API',
|
||||
loginPlaceholder: '',
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { z } from 'zod'
|
||||
import { apiTypeSchema as sharedApiTypeSchema } from '@cfdm/shared/contracts/provider'
|
||||
import { billingModeSchema as sharedBillingModeSchema } from '@cfdm/shared/contracts/provider-account'
|
||||
import { customFieldsSchema } from '@cfdm/shared/contracts/custom-fields'
|
||||
|
||||
@@ -12,7 +13,7 @@ export const paymentTypeSchema = z.enum([
|
||||
'monthly_debit',
|
||||
])
|
||||
export const ledgerDirectionSchema = z.enum(['credit', 'debit'])
|
||||
export const apiTypeSchema = z.enum(['billmanager', 'none'])
|
||||
export const apiTypeSchema = sharedApiTypeSchema
|
||||
|
||||
export const providerSchema = z.object({
|
||||
id: z.string().min(1).optional(),
|
||||
|
||||
Reference in New Issue
Block a user