Кнопка в настройках запрашивает полную выгрузку bindings из CFDM. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -27,7 +27,7 @@ describe('integrations CFDM routes', () => {
|
||||
method: 'POST',
|
||||
url: '/api/integrations/cfdm/ping',
|
||||
})
|
||||
expect(res.statusCode).toBe(401)
|
||||
expect(res.statusCode).toBe(503)
|
||||
})
|
||||
|
||||
it('принимает ping с верным Bearer', async () => {
|
||||
@@ -44,4 +44,22 @@ describe('integrations CFDM routes', () => {
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(res.json()).toEqual({ ok: true, service: 'vps-tracker' })
|
||||
})
|
||||
|
||||
it('принимает fullSync с пустым списком bindings', async () => {
|
||||
settingsRepository.upsert('settings-main', {
|
||||
integrationToken: 'test-secret',
|
||||
integrationEnabled: true,
|
||||
})
|
||||
const res = await app.inject({
|
||||
method: 'POST',
|
||||
url: '/api/integrations/cfdm/sync-bindings',
|
||||
headers: {
|
||||
authorization: 'Bearer test-secret',
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
payload: { bindings: [], fullSync: true },
|
||||
})
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(res.json()).toMatchObject({ ok: true, upserted: 0 })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -27,7 +27,9 @@ export const integrationsCfdmRoutes: FastifyPluginAsync = async (app) => {
|
||||
}
|
||||
|
||||
return runInIntegrationSpace(req, () => {
|
||||
const result = vpsDomainsRepository.syncBindings(parsed.data.bindings)
|
||||
const result = vpsDomainsRepository.syncBindings(parsed.data.bindings, {
|
||||
fullSync: parsed.data.fullSync === true,
|
||||
})
|
||||
settingsRepository.touchIntegrationSync()
|
||||
return {
|
||||
ok: true,
|
||||
|
||||
@@ -81,3 +81,49 @@ describe('settings telegram test', () => {
|
||||
expect(url).toContain('botoverride-token/')
|
||||
})
|
||||
})
|
||||
|
||||
describe('settings cfdm sync', () => {
|
||||
let app: Awaited<ReturnType<typeof buildApp>>
|
||||
|
||||
beforeEach(async () => {
|
||||
resetTestDb()
|
||||
settingsRepository.upsert('settings-main', {
|
||||
integrationEnabled: true,
|
||||
integrationToken: 'shared-token',
|
||||
cfdmApiUrl: 'http://cfdm.test',
|
||||
})
|
||||
app = await buildApp()
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await app.close()
|
||||
vi.unstubAllGlobals()
|
||||
closeDb()
|
||||
})
|
||||
|
||||
it('çàïðàøèâàåò ïîëíûé sync ó CFDM', async () => {
|
||||
const fetchMock = vi.fn(async () => Response.json({ ok: true, count: 3 }))
|
||||
vi.stubGlobal('fetch', fetchMock)
|
||||
|
||||
const res = await app.inject({ method: 'POST', url: '/api/settings/cfdm/sync' })
|
||||
expect(res.statusCode).toBe(200)
|
||||
expect(res.json()).toEqual({ ok: true, count: 3 })
|
||||
|
||||
const call = fetchMock.mock.calls[0] as [string, RequestInit] | undefined
|
||||
expect(call?.[0]).toBe('http://cfdm.test/api/v1/integrations/vps-tracker/sync')
|
||||
expect((call?.[1].headers as Record<string, string>).Authorization).toBe(
|
||||
'Bearer shared-token',
|
||||
)
|
||||
})
|
||||
|
||||
it('âîçâðàùàåò îøèáêó åñëè ïðè¸ì âûêëþ÷åí', async () => {
|
||||
settingsRepository.upsert('settings-main', {
|
||||
integrationEnabled: false,
|
||||
integrationToken: 'shared-token',
|
||||
cfdmApiUrl: 'http://cfdm.test',
|
||||
})
|
||||
const res = await app.inject({ method: 'POST', url: '/api/settings/cfdm/sync' })
|
||||
expect(res.statusCode).toBe(502)
|
||||
expect(res.json()).toMatchObject({ ok: false })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -6,6 +6,7 @@ import { settingsSchema, telegramTestBodySchema } from '@cfdm/shared/contracts/s
|
||||
import { restartScheduler } from '../services/scheduler.js'
|
||||
import { sendTelegramMessage } from '../services/telegram.js'
|
||||
import { deliverWebhook } from '../services/notifications/channels.js'
|
||||
import { requestCfdmFullSync } from '../services/cfdm-sync.js'
|
||||
import { requireSpaceRole } from '../plugins/space.js'
|
||||
|
||||
export const settingsRoutes: FastifyPluginAsync = async (app) => {
|
||||
@@ -80,4 +81,16 @@ export const settingsRoutes: FastifyPluginAsync = async (app) => {
|
||||
})
|
||||
return result.ok ? { ok: true } : { ok: false, error: result.error ?? 'Ошибка webhook' }
|
||||
})
|
||||
|
||||
app.post('/api/settings/cfdm/sync', async (req, reply) => {
|
||||
if (!requireSpaceRole(req, reply, 'admin')) return
|
||||
const result = await requestCfdmFullSync()
|
||||
if (!result.ok) {
|
||||
return reply.code(502).send({
|
||||
ok: false,
|
||||
error: result.error ?? 'Не удалось синхронизировать с CFDM',
|
||||
})
|
||||
}
|
||||
return { ok: true, count: result.count ?? 0 }
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import { settingsRepository } from '@cfdm/db/repositories/settings'
|
||||
|
||||
function resolveCfdmApiBase(): string | null {
|
||||
const row = settingsRepository.getBySpace()
|
||||
if (!row) return null
|
||||
const explicit = row.cfdmApiUrl?.trim()
|
||||
if (explicit) return explicit.replace(/\/$/, '')
|
||||
const cfdm = settingsRepository.getAppSwitcher().apps.find((a) => a.id === 'cfdm')
|
||||
return cfdm?.url?.trim().replace(/\/$/, '') ?? null
|
||||
}
|
||||
|
||||
export async function requestCfdmFullSync(): Promise<{
|
||||
ok: boolean
|
||||
count?: number
|
||||
error?: string
|
||||
}> {
|
||||
const row = settingsRepository.getBySpace()
|
||||
if (!row?.integrationEnabled) {
|
||||
return { ok: false, error: 'Включите приём синхронизации' }
|
||||
}
|
||||
|
||||
const token = settingsRepository.getIntegrationToken()
|
||||
const baseUrl = resolveCfdmApiBase()
|
||||
if (!baseUrl) return { ok: false, error: 'Укажите URL API CFDM' }
|
||||
if (!token) return { ok: false, error: 'Укажите integration token' }
|
||||
|
||||
try {
|
||||
const res = await fetch(`${baseUrl}/api/v1/integrations/vps-tracker/sync`, {
|
||||
method: 'POST',
|
||||
headers: { Authorization: `Bearer ${token}` },
|
||||
})
|
||||
const body = (await res.json().catch(() => ({}))) as {
|
||||
ok?: boolean
|
||||
count?: number
|
||||
error?: string
|
||||
}
|
||||
if (!res.ok || body.ok === false) {
|
||||
return {
|
||||
ok: false,
|
||||
error: body.error ?? `HTTP ${res.status}`,
|
||||
}
|
||||
}
|
||||
return { ok: true, count: body.count ?? 0 }
|
||||
} catch (err) {
|
||||
return {
|
||||
ok: false,
|
||||
error: err instanceof Error ? err.message : 'Ошибка сети',
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user