feat(admin): ingest аудита из apps и users 1:1 с Sheet журнала
Build and Push Auth Portal Docker Image / build-and-push (push) Successful in 1m46s
Build and Push Auth Portal Docker Image / create-release (push) Skipped

Добавлен POST /api/v1/ingest/audit, фильтры source_app/user_id, last_login_at; таблица пользователей по solution-users-1 с журналом в Sheet.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-07-21 13:24:28 +07:00
co-authored by Cursor
parent 26fc8253c3
commit 57e34ff5e9
27 changed files with 1983 additions and 639 deletions
+60
View File
@@ -11,6 +11,7 @@ async function buildTestApp() {
ADMIN_PASSWORD: 'adminpass',
DATABASE_URL: 'sqlite::memory:',
NODE_ENV: 'test',
AUDIT_INGEST_SECRET: 'dev-audit-ingest-secret',
})
return buildApp({ config, databaseUrl: 'sqlite::memory:' })
}
@@ -26,6 +27,65 @@ async function adminToken(app: Awaited<ReturnType<typeof buildTestApp>>) {
}
describe('audit log API', () => {
it('ingests external events with secret and dedupes by event_id', async () => {
const app = await buildTestApp()
const denied = await app.inject({
method: 'POST',
url: '/api/v1/ingest/audit',
payload: { events: [] },
})
expect(denied.statusCode).toBe(401)
const eventId = 'evt-test-1'
const ok = await app.inject({
method: 'POST',
url: '/api/v1/ingest/audit',
headers: { authorization: 'Bearer dev-audit-ingest-secret' },
payload: {
events: [
{
event_id: eventId,
source_app: 'vps',
action: 'vps.vps.create',
summary: 'Создан VPS',
actor_email: '[email protected]',
},
],
},
})
expect(ok.statusCode).toBe(200)
expect(ok.json()).toMatchObject({ accepted: 1, duplicates: 0 })
const dup = await app.inject({
method: 'POST',
url: '/api/v1/ingest/audit',
headers: { authorization: 'Bearer dev-audit-ingest-secret' },
payload: {
events: [
{
event_id: eventId,
source_app: 'vps',
action: 'vps.vps.create',
summary: 'Создан VPS',
},
],
},
})
expect(dup.json()).toMatchObject({ accepted: 0, duplicates: 1 })
const token = await adminToken(app)
const list = await app.inject({
method: 'GET',
url: '/api/v1/admin/audit?source_app=vps',
headers: { authorization: `Bearer ${token}` },
})
expect(list.statusCode).toBe(200)
const entries = list.json() as { source_app: string; action: string }[]
expect(entries.some((e) => e.action === 'vps.vps.create')).toBe(true)
await app.close()
})
it('records login and lists for admin', async () => {
const app = await buildTestApp()
const token = await adminToken(app)