From 4dda77f118cb9ea5ed37b577dc346e2e41b1f18d Mon Sep 17 00:00:00 2001 From: Denozordec Date: Sun, 19 Jul 2026 22:15:13 +0700 Subject: [PATCH] =?UTF-8?q?feat(billmanager):=20=D1=83=D0=BD=D0=B8=D1=84?= =?UTF-8?q?=D0=B8=D1=86=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D0=B5=20=D0=BF=D1=80=D0=BE=D1=84=D0=B8=D0=BB=D0=B8=20=D1=85?= =?UTF-8?q?=D0=BE=D1=81=D1=82=D0=B5=D1=80=D0=BE=D0=B2=20(waicore=20vds.vps?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Overrides по apiBaseUrl: DEFAULT + partial merge; Waicore использует func=vds.vps. Co-authored-by: Cursor --- AGENTS.md | 2 + apps/api/src/services/billmanager/index.ts | 8 + .../src/services/billmanager/operations.ts | 100 +++++++++++-- .../src/services/billmanager/profiles.test.ts | 138 ++++++++++++++++++ .../services/billmanager/profiles/README.md | 46 ++++++ .../services/billmanager/profiles/default.ts | 28 ++++ .../services/billmanager/profiles/index.ts | 13 ++ .../services/billmanager/profiles/merge.ts | 50 +++++++ .../services/billmanager/profiles/registry.ts | 44 ++++++ .../services/billmanager/profiles/types.ts | 68 +++++++++ .../services/billmanager/profiles/waicore.ts | 16 ++ apps/api/src/services/billmanager/sync.ts | 36 +++-- 12 files changed, 523 insertions(+), 26 deletions(-) create mode 100644 apps/api/src/services/billmanager/profiles.test.ts create mode 100644 apps/api/src/services/billmanager/profiles/README.md create mode 100644 apps/api/src/services/billmanager/profiles/default.ts create mode 100644 apps/api/src/services/billmanager/profiles/index.ts create mode 100644 apps/api/src/services/billmanager/profiles/merge.ts create mode 100644 apps/api/src/services/billmanager/profiles/registry.ts create mode 100644 apps/api/src/services/billmanager/profiles/types.ts create mode 100644 apps/api/src/services/billmanager/profiles/waicore.ts diff --git a/AGENTS.md b/AGENTS.md index 78030a1..380b739 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -79,6 +79,8 @@ vps-tracker/ Формат запроса: `?authinfo=user:pass&out=bjson&func=vds|payment|dashboard.info|vds.order` +**Профили хостеров (overrides):** `apps/api/src/services/billmanager/profiles/` — матч по `apiBaseUrl`, новый хостер = один файл overrides + запись в registry. HOWTO: [`profiles/README.md`](apps/api/src/services/billmanager/profiles/README.md). + ## Уведомления - **Движок:** `apps/api/src/services/notifications/` — rules, dedup, engine, channels diff --git a/apps/api/src/services/billmanager/index.ts b/apps/api/src/services/billmanager/index.ts index 3b5e7be..6acedf6 100644 --- a/apps/api/src/services/billmanager/index.ts +++ b/apps/api/src/services/billmanager/index.ts @@ -10,11 +10,19 @@ export { fetchPayments, fetchVdsOrderPricelist, fetchVdsOrderPricelistAllDatacenters, + mapVdsWithProfile, + mapPaymentWithProfile, } from './operations.js' export { syncFromBillmanager } from './sync.js' export { runBillmanagerAccountSync } from './sync-job.js' export { billmanagerAccountRowForSync, resolveBillmanagerApi } from './context.js' +export { + resolveBillmanagerProfile, + DEFAULT_PROFILE, + mergeProfile, +} from './profiles/index.js' export type { BillmanagerSyncAccount } from './context.js' +export type { BillmanagerProfile, BillmanagerProfileOverrides } from './profiles/index.js' export type { SyncFromBillmanagerOptions, SyncFromBillmanagerResult, diff --git a/apps/api/src/services/billmanager/operations.ts b/apps/api/src/services/billmanager/operations.ts index d07c3ff..c038d27 100644 --- a/apps/api/src/services/billmanager/operations.ts +++ b/apps/api/src/services/billmanager/operations.ts @@ -1,5 +1,6 @@ /** * BILLmanager API operations — fetch VDS, payments, dashboard, tariffs + * All funcs / extract keys come from resolveBillmanagerProfile(baseUrl). */ import { billmanagerRequest } from './client.js' @@ -10,6 +11,11 @@ import { parseDatacenterName, parseTariffDesc, } from './parsers.js' +import { + resolveBillmanagerProfile, + type BillmanagerProfile, +} from './profiles/index.js' +import type { MappedPayment, MappedVps } from './mappers.js' export interface DashboardInfo { balance: number @@ -37,21 +43,60 @@ export interface TariffItem { country?: string } -export async function fetchVds(baseUrl: string, authinfo: string): Promise[]> { - const data = await billmanagerRequest(baseUrl, authinfo, 'vds') - const elems = extractList(data, 'vds') +function profileFor(baseUrl: string): BillmanagerProfile { + return resolveBillmanagerProfile(baseUrl) +} + +/** Map raw VDS elem through profile.map.vds (+ optional enrichVds). */ +export function mapVdsWithProfile( + profile: BillmanagerProfile, + item: Record, + providerId: string, + accountId: string, +): MappedVps { + const mapped = profile.map.vds(item, providerId, accountId) + return profile.map.enrichVds ? profile.map.enrichVds(item, mapped) : mapped +} + +export function mapPaymentWithProfile( + profile: BillmanagerProfile, + item: Record, + accountId: string, +): MappedPayment | null { + return profile.map.payment(item, accountId) +} + +export async function fetchVds( + baseUrl: string, + authinfo: string, + profile?: BillmanagerProfile, +): Promise[]> { + const p = profile ?? profileFor(baseUrl) + const data = await billmanagerRequest( + baseUrl, + authinfo, + p.funcs.listVds, + p.requestParams?.listVds, + ) + const elems = extractList(data, p.extract.listVdsKey) return elems.map((e) => elemToObject(e)) } export async function fetchDashboardInfo( baseUrl: string, authinfo: string, - opts: { fallbackCurrency?: string | null } = {}, + opts: { fallbackCurrency?: string | null; profile?: BillmanagerProfile } = {}, ): Promise { - const data = await billmanagerRequest(baseUrl, authinfo, 'dashboard.info', { - dashboard: 'info', - sfrom: 'ajax', - }) + const p = opts.profile ?? profileFor(baseUrl) + const data = await billmanagerRequest( + baseUrl, + authinfo, + p.funcs.dashboard, + p.requestParams?.dashboard ?? { + dashboard: 'info', + sfrom: 'ajax', + }, + ) const elems = extractList(data, 'dashboard') || (Array.isArray(data.elem) ? (data.elem as unknown[]) : []) @@ -83,32 +128,48 @@ export async function fetchPayments( createdate?: string filter?: string status?: string | number + profile?: BillmanagerProfile } = {}, ): Promise[]> { - const params: Record = {} + const p = opts.profile ?? profileFor(baseUrl) + const params: Record = { + ...p.requestParams?.payments, + } if (opts.createdatestart) params.createdatestart = opts.createdatestart if (opts.createdateend) params.createdateend = opts.createdateend if (opts.createdate === 'other') params.createdate = 'other' if (opts.filter === 'on') params.filter = 'on' if (opts.status != null) params.status = String(opts.status) - const data = await billmanagerRequest(baseUrl, authinfo, 'payment', params) - const elems = extractList(data, 'payment') + const data = await billmanagerRequest(baseUrl, authinfo, p.funcs.payments, params) + const elems = extractList(data, p.extract.paymentsKey) return elems.map((e) => elemToObject(e)) } export async function fetchVdsOrderPricelist( baseUrl: string, authinfo: string, - opts: { plid?: string; period?: string; datacenter?: string } = {}, + opts: { + plid?: string + period?: string + datacenter?: string + profile?: BillmanagerProfile + } = {}, ): Promise<{ tariffItems: TariffItem[]; slist: Record }> { - const params: Record = { + const p = opts.profile ?? profileFor(baseUrl) + const params: Record = { plid: opts.plid || '', sfrom: 'ajax', + ...p.requestParams?.orderPricelist, } if (opts.period) params.period = opts.period if (opts.datacenter) params.datacenter = opts.datacenter - const data = await billmanagerRequest(baseUrl, authinfo, 'vds.order', params) + const data = await billmanagerRequest( + baseUrl, + authinfo, + p.funcs.orderPricelist, + params, + ) const tariflist = extractTariflist(data) const listNode = (data.list as Record) ?? (data.doc as Record)?.list @@ -143,8 +204,10 @@ export async function fetchVdsOrderPricelist( export async function fetchVdsOrderPricelistAllDatacenters( baseUrl: string, authinfo: string, + profile?: BillmanagerProfile, ): Promise<{ tariffItems: TariffItem[]; slist: Record }> { - const initial = await fetchVdsOrderPricelist(baseUrl, authinfo) + const p = profile ?? profileFor(baseUrl) + const initial = await fetchVdsOrderPricelist(baseUrl, authinfo, { profile: p }) const slist = initial.slist || {} const datacenters = Array.isArray(slist.datacenter) ? slist.datacenter : [] @@ -161,7 +224,12 @@ export async function fetchVdsOrderPricelistAllDatacenters( const { country, location } = parseDatacenterName(dcName) const result = - i === 0 ? initial : await fetchVdsOrderPricelist(baseUrl, authinfo, { datacenter: dcKey }) + i === 0 + ? initial + : await fetchVdsOrderPricelist(baseUrl, authinfo, { + datacenter: dcKey, + profile: p, + }) for (const t of result.tariffItems) { allTariffItems.push({ ...t, diff --git a/apps/api/src/services/billmanager/profiles.test.ts b/apps/api/src/services/billmanager/profiles.test.ts new file mode 100644 index 0000000..88b9247 --- /dev/null +++ b/apps/api/src/services/billmanager/profiles.test.ts @@ -0,0 +1,138 @@ +import { describe, expect, it, vi, afterEach } from 'vitest' + +import { elemToObject, extractList } from './parsers.js' +import { mapVdsToVps } from './mappers.js' +import { + DEFAULT_PROFILE, + mergeProfile, + resolveBillmanagerProfile, + waicoreOverrides, +} from './profiles/index.js' +import { fetchVds, mapVdsWithProfile } from './operations.js' + +/** Minimal Waicore-style bjson (no credentials / addon noise). */ +const WAICORE_VDS_FIXTURE = { + func: 'vds.vps', + elem: [ + { + id: '87173', + ip: '212.192.246.214', + domain: 'instance87173.waicore.network', + expiredate: '2027-01-21', + real_expiredate: '2027-01-21', + ostempl: 'Ubuntu 24.04', + datacentername: '[DE] Франкфурт | Промо', + pricelist: '[DE] RP-1', + cost: '1.80 € / Месяц', + item_cost: '10.8000', + currency_str: '€', + createdate: '2025-07-15', + item_status_orig: '2', + item_status: 'Активен', + }, + ], +} + +describe('billmanager profiles', () => { + afterEach(() => { + vi.unstubAllGlobals() + vi.restoreAllMocks() + }) + + it('resolve: waicore hostname → waicore profile', () => { + const p = resolveBillmanagerProfile('https://my.waicore.com/') + expect(p.id).toBe('waicore') + expect(p.funcs.listVds).toBe('vds.vps') + expect(p.funcs.payments).toBe('payment') + expect(p.funcs.dashboard).toBe('dashboard.info') + }) + + it('resolve: keyword in URL → waicore', () => { + expect(resolveBillmanagerProfile('https://panel.example/waicore-proxy').id).toBe( + 'waicore', + ) + }) + + it('resolve: unknown hoster → default', () => { + const p = resolveBillmanagerProfile('https://bill.hoster.ru/') + expect(p.id).toBe('default') + expect(p.funcs.listVds).toBe('vds') + }) + + it('merge: only overrides listVds func', () => { + const merged = mergeProfile(DEFAULT_PROFILE, waicoreOverrides) + expect(merged.funcs.listVds).toBe('vds.vps') + expect(merged.funcs.payments).toBe(DEFAULT_PROFILE.funcs.payments) + expect(merged.extract.listVdsKey).toBe(DEFAULT_PROFILE.extract.listVdsKey) + expect(merged.map.vds).toBe(DEFAULT_PROFILE.map.vds) + }) + + it('Waicore fixture elem → MappedVps fields', () => { + const profile = resolveBillmanagerProfile('https://my.waicore.com/') + const elems = extractList(WAICORE_VDS_FIXTURE, profile.extract.listVdsKey) + expect(elems).toHaveLength(1) + const item = elemToObject(elems[0]!) + const vps = mapVdsWithProfile(profile, item, 'prov-1', 'acc-1') + expect(vps.externalId).toBe('87173') + expect(vps.ip).toBe('212.192.246.214') + expect(vps.dns).toBe('instance87173.waicore.network') + expect(vps.paidUntil).toBe('2027-01-21') + expect(vps.os).toBe('Ubuntu 24.04') + expect(vps.status).toBe('active') + }) + + it('fetchVds for waicore URL uses func=vds.vps', async () => { + const calls: string[] = [] + vi.stubGlobal( + 'fetch', + vi.fn(async (input: RequestInfo | URL) => { + const url = String(input) + calls.push(url) + return { + ok: true, + json: async () => WAICORE_VDS_FIXTURE, + } + }), + ) + + const items = await fetchVds('https://my.waicore.com/', 'user:pass') + expect(calls).toHaveLength(1) + expect(calls[0]).toContain('func=vds.vps') + expect(items).toHaveLength(1) + expect(items[0]?.id).toBe('87173') + }) + + it('fetchVds for default URL uses func=vds', async () => { + const calls: string[] = [] + vi.stubGlobal( + 'fetch', + vi.fn(async (input: RequestInfo | URL) => { + const url = String(input) + calls.push(url) + return { + ok: true, + json: async () => ({ elem: [] }), + } + }), + ) + + await fetchVds('https://bill.example.com/', 'user:pass') + expect(calls[0]).toContain('func=vds') + expect(calls[0]).not.toContain('func=vds.vps') + }) + + it('default mapVds still works without profile enrich', () => { + const mapped = mapVdsToVps( + { + id: '1', + ip: '1.2.3.4', + domain: '', + expiredate: '2026-12-01', + item_status_orig: '2', + }, + 'p', + 'a', + ) + expect(mapped.paidUntil).toBe('2026-12-01') + }) +}) diff --git a/apps/api/src/services/billmanager/profiles/README.md b/apps/api/src/services/billmanager/profiles/README.md new file mode 100644 index 0000000..2d574ff --- /dev/null +++ b/apps/api/src/services/billmanager/profiles/README.md @@ -0,0 +1,46 @@ +# BILLmanager hoster profiles + +Унифицированные переопределители для исключений из стандартного ISPsystem API. + +## Как добавить профиль (5 минут) + +1. Создай `profiles/.ts` с **только расхождениями**: + +```ts +import type { BillmanagerProfileOverrides } from './types.js' + +export const myHosterOverrides: BillmanagerProfileOverrides = { + id: 'myhoster', + match: { + hostnames: ['myhoster.com'], + keywords: ['myhoster'], + }, + funcs: { + listVds: 'vds.custom', // если отличается от 'vds' + }, + // map: { enrichVds: (item, mapped) => ({ ...mapped, country: 'DE' }) }, + // requestParams: { listVds: { p_cnt: 1000 } }, +} +``` + +2. Зарегистрируй в [`registry.ts`](./registry.ts) — массив `PROFILE_OVERRIDES` (порядок = приоритет матча). + +3. Добавь fixture + тест в [`profiles.test.ts`](../profiles.test.ts). + +4. Новый hook (редко) — расширь `BillmanagerProfile` в [`types.ts`](./types.ts) и значение в [`default.ts`](./default.ts). + +## Контракт + +| Поле | Назначение | +|------|------------| +| `match.hostnames` | substring hostname | +| `match.keywords` | substring всего URL | +| `funcs.*` | `func=` для list / payment / dashboard / order | +| `extract.*` | ключ для `extractList` | +| `map.vds` / `map.payment` | полный маппер | +| `map.enrichVds` | пост-обработка после default map | +| `requestParams.*` | доп. query params | + +`resolveBillmanagerProfile(apiBaseUrl)` → `merge(DEFAULT, override)` или `DEFAULT`. + +Sync и operations **не** содержат `if (hoster)` — только профиль. diff --git a/apps/api/src/services/billmanager/profiles/default.ts b/apps/api/src/services/billmanager/profiles/default.ts new file mode 100644 index 0000000..aead5b5 --- /dev/null +++ b/apps/api/src/services/billmanager/profiles/default.ts @@ -0,0 +1,28 @@ +import { mapPaymentToPayment, mapVdsToVps } from '../mappers.js' +import type { BillmanagerProfile } from './types.js' + +/** Standard ISPsystem BILLmanager 6 behaviour. */ +export const DEFAULT_PROFILE: BillmanagerProfile = { + id: 'default', + match: {}, + funcs: { + listVds: 'vds', + payments: 'payment', + dashboard: 'dashboard.info', + orderPricelist: 'vds.order', + }, + extract: { + listVdsKey: 'vds', + paymentsKey: 'payment', + }, + map: { + vds: mapVdsToVps, + payment: mapPaymentToPayment, + }, + requestParams: { + dashboard: { + dashboard: 'info', + sfrom: 'ajax', + }, + }, +} diff --git a/apps/api/src/services/billmanager/profiles/index.ts b/apps/api/src/services/billmanager/profiles/index.ts new file mode 100644 index 0000000..18f25b0 --- /dev/null +++ b/apps/api/src/services/billmanager/profiles/index.ts @@ -0,0 +1,13 @@ +export { DEFAULT_PROFILE } from './default.js' +export { mergeProfile } from './merge.js' +export { PROFILE_OVERRIDES, resolveBillmanagerProfile } from './registry.js' +export type { + BillmanagerExtract, + BillmanagerFuncs, + BillmanagerMap, + BillmanagerMatch, + BillmanagerProfile, + BillmanagerProfileOverrides, + BillmanagerRequestParams, +} from './types.js' +export { waicoreOverrides } from './waicore.js' diff --git a/apps/api/src/services/billmanager/profiles/merge.ts b/apps/api/src/services/billmanager/profiles/merge.ts new file mode 100644 index 0000000..bca37b0 --- /dev/null +++ b/apps/api/src/services/billmanager/profiles/merge.ts @@ -0,0 +1,50 @@ +import type { BillmanagerProfile, BillmanagerProfileOverrides } from './types.js' + +/** + * Merge DEFAULT profile with hoster overrides. + * Nested funcs/extract/map are shallow-merged; requestParams replaced per-key. + */ +export function mergeProfile( + defaults: BillmanagerProfile, + overrides: BillmanagerProfileOverrides, +): BillmanagerProfile { + return { + id: overrides.id, + match: { + ...defaults.match, + ...overrides.match, + }, + funcs: { + ...defaults.funcs, + ...overrides.funcs, + }, + extract: { + ...defaults.extract, + ...overrides.extract, + }, + map: { + ...defaults.map, + ...overrides.map, + }, + requestParams: { + ...defaults.requestParams, + ...overrides.requestParams, + listVds: { + ...defaults.requestParams?.listVds, + ...overrides.requestParams?.listVds, + }, + payments: { + ...defaults.requestParams?.payments, + ...overrides.requestParams?.payments, + }, + dashboard: { + ...defaults.requestParams?.dashboard, + ...overrides.requestParams?.dashboard, + }, + orderPricelist: { + ...defaults.requestParams?.orderPricelist, + ...overrides.requestParams?.orderPricelist, + }, + }, + } +} diff --git a/apps/api/src/services/billmanager/profiles/registry.ts b/apps/api/src/services/billmanager/profiles/registry.ts new file mode 100644 index 0000000..d68dd43 --- /dev/null +++ b/apps/api/src/services/billmanager/profiles/registry.ts @@ -0,0 +1,44 @@ +import { DEFAULT_PROFILE } from './default.js' +import { mergeProfile } from './merge.js' +import type { BillmanagerProfile, BillmanagerProfileOverrides } from './types.js' +import { waicoreOverrides } from './waicore.js' + +/** + * Hoster override list — first match wins. + * Add new hosters here after creating profiles/.ts. + */ +export const PROFILE_OVERRIDES: BillmanagerProfileOverrides[] = [waicoreOverrides] + +function matchesUrl(url: string, override: BillmanagerProfileOverrides): boolean { + const match = override.match + if (!match) return false + + let hostname = '' + try { + hostname = new URL(url).hostname.toLowerCase() + } catch { + hostname = '' + } + const haystack = url.toLowerCase() + + if (match.hostnames?.some((h) => hostname.includes(h.toLowerCase()))) { + return true + } + if (match.keywords?.some((k) => haystack.includes(k.toLowerCase()))) { + return true + } + return false +} + +/** Resolve profile for apiBaseUrl: first matching override merged onto DEFAULT, else DEFAULT. */ +export function resolveBillmanagerProfile(apiBaseUrl: string): BillmanagerProfile { + const url = String(apiBaseUrl || '').trim() + if (!url) return DEFAULT_PROFILE + + for (const override of PROFILE_OVERRIDES) { + if (matchesUrl(url, override)) { + return mergeProfile(DEFAULT_PROFILE, override) + } + } + return DEFAULT_PROFILE +} diff --git a/apps/api/src/services/billmanager/profiles/types.ts b/apps/api/src/services/billmanager/profiles/types.ts new file mode 100644 index 0000000..2419ec7 --- /dev/null +++ b/apps/api/src/services/billmanager/profiles/types.ts @@ -0,0 +1,68 @@ +/** + * Unified BILLmanager hoster profile contract. + * DEFAULT fills all hooks; hoster files declare only overrides. + */ + +import type { MappedPayment, MappedVps } from '../mappers.js' + +export type BillmanagerMatch = { + /** Hostname substring match (e.g. waicore.com) */ + hostnames?: string[] + /** Full URL lowercase substring (e.g. waicore) */ + keywords?: string[] +} + +export type BillmanagerFuncs = { + listVds: string + payments: string + dashboard: string + orderPricelist: string +} + +export type BillmanagerExtract = { + listVdsKey: string + paymentsKey: string +} + +export type BillmanagerMap = { + vds: ( + item: Record, + providerId: string, + accountId: string, + ) => MappedVps + payment: ( + item: Record, + accountId: string, + ) => MappedPayment | null + /** Optional post-process after map.vds (specs / geo / etc.) */ + enrichVds?: ( + item: Record, + mapped: MappedVps, + ) => MappedVps +} + +export type BillmanagerRequestParams = { + listVds?: Record + payments?: Record + dashboard?: Record + orderPricelist?: Record +} + +export type BillmanagerProfile = { + id: string + match: BillmanagerMatch + funcs: BillmanagerFuncs + extract: BillmanagerExtract + map: BillmanagerMap + requestParams?: BillmanagerRequestParams +} + +/** Deep-partial for hoster override files (only divergences). */ +export type BillmanagerProfileOverrides = { + id: string + match?: BillmanagerMatch + funcs?: Partial + extract?: Partial + map?: Partial + requestParams?: BillmanagerRequestParams +} diff --git a/apps/api/src/services/billmanager/profiles/waicore.ts b/apps/api/src/services/billmanager/profiles/waicore.ts new file mode 100644 index 0000000..fa8aa04 --- /dev/null +++ b/apps/api/src/services/billmanager/profiles/waicore.ts @@ -0,0 +1,16 @@ +import type { BillmanagerProfileOverrides } from './types.js' + +/** + * Waicore (my.waicore.com) — list VPS via func=vds.vps instead of vds. + * Response uses top-level elem[] (covered by extractList). + */ +export const waicoreOverrides: BillmanagerProfileOverrides = { + id: 'waicore', + match: { + hostnames: ['waicore.com', 'waicore.network'], + keywords: ['waicore'], + }, + funcs: { + listVds: 'vds.vps', + }, +} diff --git a/apps/api/src/services/billmanager/sync.ts b/apps/api/src/services/billmanager/sync.ts index 8e30767..c6bff74 100644 --- a/apps/api/src/services/billmanager/sync.ts +++ b/apps/api/src/services/billmanager/sync.ts @@ -7,15 +7,17 @@ import { getDb, schema } from '@cfdm/db' import type { BillmanagerSyncAccount } from './context.js' import { syncFallbackCurrency } from '@cfdm/shared/utils/account-balance' -import { mapPaymentToPayment, mapVdsToVps } from './mappers.js' import { fetchDashboardInfo, fetchPayments, fetchVds, fetchVdsOrderPricelistAllDatacenters, + mapPaymentWithProfile, + mapVdsWithProfile, type DashboardInfo, type TariffItem, } from './operations.js' +import { resolveBillmanagerProfile } from './profiles/index.js' export interface SyncFromBillmanagerOptions { skipTariffs?: boolean @@ -69,6 +71,7 @@ export async function syncFromBillmanager( } const authinfo = apiCredentials.trim() const db = getDb() + const profile = resolveBillmanagerProfile(apiBaseUrl) const fetchVpsPayments = !skipVpsPayments const fetchTariffs = !skipTariffs @@ -76,16 +79,29 @@ export async function syncFromBillmanager( const fallbackCurrency = syncFallbackCurrency(account) const [vdsItems, paymentItems, dashboardInfo, tariffResult] = await Promise.all([ - fetchVpsPayments ? fetchVds(apiBaseUrl, authinfo) : [], - fetchVpsPayments ? fetchPayments(apiBaseUrl, authinfo, {}) : [], + fetchVpsPayments ? fetchVds(apiBaseUrl, authinfo, profile) : [], fetchVpsPayments - ? fetchDashboardInfo(apiBaseUrl, authinfo, { fallbackCurrency }).catch(() => null) + ? fetchPayments(apiBaseUrl, authinfo, { profile }) + : [], + fetchVpsPayments + ? fetchDashboardInfo(apiBaseUrl, authinfo, { + fallbackCurrency, + profile, + }).catch(() => null) : null, fetchTariffs - ? fetchVdsOrderPricelistAllDatacenters(apiBaseUrl, authinfo).catch((err) => { - console.warn('fetchVdsOrderPricelistAllDatacenters failed:', err instanceof Error ? err.message : err) - return { tariffItems: [] as TariffItem[], slist: {} as Record } - }) + ? fetchVdsOrderPricelistAllDatacenters(apiBaseUrl, authinfo, profile).catch( + (err) => { + console.warn( + 'fetchVdsOrderPricelistAllDatacenters failed:', + err instanceof Error ? err.message : err, + ) + return { + tariffItems: [] as TariffItem[], + slist: {} as Record, + } + }, + ) : { tariffItems: [] as TariffItem[], slist: {} as Record }, ]) const { tariffItems = [], slist = {} } = tariffResult || {} @@ -95,7 +111,7 @@ export async function syncFromBillmanager( if (fetchVpsPayments) { for (const item of vdsItems) { - const vps = mapVdsToVps(item, providerId, accountId) + const vps = mapVdsWithProfile(profile, item, providerId, accountId) const id = `vps-bm-${accountId}-${vps.externalId}` const additionalIps = JSON.stringify(vps.additionalIps || []) const dailyRate = vps.dailyRate @@ -229,7 +245,7 @@ export async function syncFromBillmanager( ) for (const item of paymentItems) { - const payment = mapPaymentToPayment(item, accountId) + const payment = mapPaymentWithProfile(profile, item, accountId) if (!payment || payment.amount <= 0) continue const note = payment.note if (existingPayments.has(note)) continue