feat(api): implement chestny-znak web app with True API integration

- Fastify backend with auth (CryptoPro CAdES-BES), KM check, UPD parsing
- React frontend with auth page, code check, XML upload, results table
- Client-side signing via crypto-pro 2.3.0 (browser plugin)
- Token caching with auto-refresh, retry with exponential backoff
- Input validation schemas, file size limits, global 401 interceptor
- Docker compose with backend + frontend (nginx) services
This commit is contained in:
kislovdm
2026-07-08 16:53:35 +03:00
parent d2819ff03c
commit 70eac33c8c
19 changed files with 501 additions and 240 deletions
+25 -13
View File
@@ -1,6 +1,6 @@
import type { FastifyInstance } from 'fastify'
import { parseUpdXml } from '../services/upd-parser.js'
import { checkCodesAuth } from '../services/cises-service.js'
import { checkCodesAuth, AppError } from '../services/cises-service.js'
export async function updRoutes(app: FastifyInstance): Promise<void> {
app.post('/api/upload-upd', async (request, reply) => {
@@ -13,24 +13,36 @@ export async function updRoutes(app: FastifyInstance): Promise<void> {
const xmlContent = buffer.toString('utf-8')
const fileName = data.filename
const parsed = await parseUpdXml(xmlContent, fileName)
let parsed
try {
parsed = await parseUpdXml(xmlContent, fileName)
} catch {
return reply.status(400).send({ error: 'Invalid XML file' })
}
if (parsed.codes.length === 0) {
return reply.status(400).send({ error: 'No marking codes found in UPD' })
}
const checkResults = await checkCodesAuth({ codes: parsed.codes })
try {
const checkResults = await checkCodesAuth({ codes: parsed.codes })
return {
document: {
number: parsed.documentNumber,
date: parsed.documentDate,
seller: parsed.sellerName,
buyer: parsed.buyerName,
fileName: parsed.fileName,
},
codesFound: parsed.codes.length,
results: checkResults,
return {
document: {
number: parsed.documentNumber,
date: parsed.documentDate,
seller: parsed.sellerName,
buyer: parsed.buyerName,
fileName: parsed.fileName,
},
codesFound: parsed.codes.length,
results: checkResults,
}
} catch (err) {
if (err instanceof AppError) {
return reply.status(err.statusCode).send({ error: err.message })
}
throw err
}
})
}