From 815454f05c71a3dcaf8b16d1afd4106b45f9e859 Mon Sep 17 00:00:00 2001 From: Peolite1 Date: Fri, 28 Aug 2026 23:25:26 +0100 Subject: [PATCH 1/4] feat: add OpenAPI 3.0 auto-generation from route definitions --- stellar-payment-platform/package.json | 6 +- stellar-payment-platform/server.js | 142 ++++++++++++++++++ .../src/routes/v1/adminRoutes.js | 42 +++++- .../src/routes/v1/apiKeyRoutes.js | 56 ++++++- .../src/routes/v1/authRoutes.js | 28 +++- .../src/routes/v1/exportRoutes.js | 12 ++ .../src/routes/v1/federationRoutes.js | 14 +- .../src/routes/v1/historyRoutes.js | 12 ++ .../src/routes/v1/paymentRoutes.js | 12 ++ .../src/routes/v1/receiptRoutes.js | 12 ++ .../src/routes/v1/statsRoutes.js | 14 +- .../src/routes/v1/userRoutes.js | 48 ++++++ .../src/routes/v1/webhookRoutes.js | 36 +++++ 13 files changed, 421 insertions(+), 13 deletions(-) diff --git a/stellar-payment-platform/package.json b/stellar-payment-platform/package.json index 4dbe0ed3..545c6fcf 100644 --- a/stellar-payment-platform/package.json +++ b/stellar-payment-platform/package.json @@ -53,11 +53,13 @@ "redis": "^4.7.0", "uuid": "^9.0.1", "xss": "^1.0.15", - "zod": "^4.4.3" + "zod": "^4.4.3", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.0" }, "devDependencies": { "jest": "^29.7.0", "supertest": "^7.0.0", "tsx": "4.23.1" } -} +} \ No newline at end of file diff --git a/stellar-payment-platform/server.js b/stellar-payment-platform/server.js index c55c098e..640adbb0 100644 --- a/stellar-payment-platform/server.js +++ b/stellar-payment-platform/server.js @@ -1,5 +1,7 @@ require('./config/envCheck'); const express = require('express'); +const swaggerJsdoc = require('swagger-jsdoc'); +const swaggerUi = require('swagger-ui-express'); const cors = require('cors'); const helmet = require('helmet'); const crypto = require('crypto'); @@ -73,6 +75,26 @@ if (process.env.SENTRY_DSN) { const app = express(); +const swaggerOptions = { + definition: { + openapi: '3.0.0', + info: { + title: 'Stellar Tags API', + version: '1.0.0', + description: 'API for Stellar Tags', + }, + servers: [ + { + url: 'http://localhost:5000', + }, + ], + }, + apis: ['./server.js', './src/routes/v1/*.js'], +}; +const swaggerSpec = swaggerJsdoc(swaggerOptions); +app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); + + // #31 — Attach a correlation ID to every request before anything else runs so // all downstream middleware, handlers and logs can reference the same trace. app.use(correlationId); @@ -372,6 +394,18 @@ const registerLocalUser = async ({ username, address }) => { }; // Expose /metrics endpoint for Prometheus to scrape + +/** + * @openapi + * /metrics: + * get: + * tags: + * - v1 + * description: GET /metrics + * responses: + * 200: + * description: Success + */ app.get('/metrics', async (req, res) => { try { res.set('Content-Type', getContentType()); @@ -383,6 +417,18 @@ app.get('/metrics', async (req, res) => { } }); + +/** + * @openapi + * /federation: + * get: + * tags: + * - v1 + * description: GET /federation + * responses: + * 200: + * description: Success + */ app.get('/federation', etagCache, validateSchema({ query: federationQuerySchema }), async (req, res, next) => { const { q: queryValue, type } = req.query; @@ -564,6 +610,18 @@ const verifyFreighterRegistrationSignature = ({ * - Validates that provided signature(s) meet minimum threshold * - Ensures authorization requirements are satisfied */ + +/** + * @openapi + * /register: + * post: + * tags: + * - v1 + * description: POST /register + * responses: + * 200: + * description: Success + */ app.post('/register', idempotencyMiddleware(redisClient), requireJson, validateSchema({ body: registerBodySchema }), async (req, res, next) => { // registerBodySchema has already guaranteed that username is a trimmed // 3-20 character alphanumeric string and address is a non-empty trimmed @@ -744,6 +802,18 @@ app.post('/register', idempotencyMiddleware(redisClient), requireJson, validateS app.all('/register', (req, res, next) => next(new ApiError('METHOD_NOT_ALLOWED'))); + +/** + * @openapi + * /lookup: + * get: + * tags: + * - v1 + * description: GET /lookup + * responses: + * 200: + * description: Success + */ app.get('/lookup', validateSchema({ query: lookupQuerySchema }), async (req, res, next) => { const { address = '', search = '' } = req.query; @@ -852,6 +922,18 @@ app.get('/lookup', validateSchema({ query: lookupQuerySchema }), async (req, res } }); + +/** + * @openapi + * /users: + * get: + * tags: + * - v1 + * description: GET /users + * responses: + * 200: + * description: Success + */ app.get('/users', validateSchema({ query: usersQuerySchema }), async (req, res, next) => { const { limit: cursorLimit, cursor, invalid: invalidCursor } = parseCursorQuery(req.query); const { page, limit, skip } = parsePagination(req.query); @@ -944,6 +1026,18 @@ app.use('/auth/api-keys', require('./src/routes/v1/apiKeyRoutes')(redisClient)); // #497 — Expose RSA public key as a JWKS document so external services can // verify RS256-signed tokens without sharing a secret. + +/** + * @openapi + * /.well-known/jwks.json: + * get: + * tags: + * - v1 + * description: GET /.well-known/jwks.json + * responses: + * 200: + * description: Success + */ app.get('/.well-known/jwks.json', (_req, res) => { try { const { getJwks } = require('./src/utils/jwt'); @@ -959,16 +1053,52 @@ app.get('/.well-known/jwks.json', (_req, res) => { } }); + +/** + * @openapi + * /.well-known/stellar.toml: + * get: + * tags: + * - v1 + * description: GET /.well-known/stellar.toml + * responses: + * 200: + * description: Success + */ app.get('/.well-known/stellar.toml', (_req, res) => { res.header("Access-Control-Allow-Origin", "*"); res.setHeader('Content-Type', 'text/plain'); res.send(`FEDERATION_SERVER="${process.env.FEDERATION_SERVER_URL || `https://${process.env.STELLAR_TAG_DOMAIN}/federation`}"\n`); }); + +/** + * @openapi + * /api/v1/time: + * get: + * tags: + * - v1 + * description: GET /api/v1/time + * responses: + * 200: + * description: Success + */ app.get('/api/v1/time', (_req, res) => { res.status(200).json({ time: new Date().toISOString() }); }); + +/** + * @openapi + * /health: + * get: + * tags: + * - v1 + * description: GET /health + * responses: + * 200: + * description: Success + */ app.get('/health', async (_req, res) => { const checks = { database: null, redis: null }; let allOk = true; @@ -1003,6 +1133,18 @@ app.get('/health', async (_req, res) => { } }); + +/** + * @openapi + * /health: + * get: + * tags: + * - v1 + * description: GET /health + * responses: + * 200: + * description: Success + */ app.get('/health', async (req, res) => { try { await prisma.$queryRaw`SELECT 1`; diff --git a/stellar-payment-platform/src/routes/v1/adminRoutes.js b/stellar-payment-platform/src/routes/v1/adminRoutes.js index 3d10cf21..31290786 100644 --- a/stellar-payment-platform/src/routes/v1/adminRoutes.js +++ b/stellar-payment-platform/src/routes/v1/adminRoutes.js @@ -33,7 +33,19 @@ module.exports = (redisClient) => { // Streams all payment records as CSV (default) or NDJSON. // Supports optional startDate / endDate query params for filtering. // Paginates internally using cursor-based pages so memory stays bounded. - router.get('/admin/export', adminAuth, asyncHandler(async (req, res, next) => { + +/** + * @openapi + * /admin/export: + * get: + * tags: + * - v1 + * description: GET /admin/export + * responses: + * 200: + * description: Success + */ +router.get('/admin/export', adminAuth, asyncHandler(async (req, res, next) => { const { format = 'csv', startDate, endDate } = req.query; // Validate date range when provided @@ -115,7 +127,19 @@ module.exports = (redisClient) => { } })); - router.post('/admin/block', adminAuth, asyncHandler(async (req, res, next) => { + +/** + * @openapi + * /admin/block: + * post: + * tags: + * - v1 + * description: POST /admin/block + * responses: + * 200: + * description: Success + */ +router.post('/admin/block', adminAuth, asyncHandler(async (req, res, next) => { const prisma = getPrisma(); const { address } = req.body; @@ -154,7 +178,19 @@ module.exports = (redisClient) => { * Query parameters: * - limit (optional) integer between 1 and 100, default 50 */ - router.get( + +/** + * @openapi + * /admin/audit-logs: + * get: + * tags: + * - v1 + * description: GET /admin/audit-logs + * responses: + * 200: + * description: Success + */ +router.get( '/admin/audit-logs', adminAuth, asyncHandler(async (req, res) => { diff --git a/stellar-payment-platform/src/routes/v1/apiKeyRoutes.js b/stellar-payment-platform/src/routes/v1/apiKeyRoutes.js index 03db8fa1..2ea9a05b 100644 --- a/stellar-payment-platform/src/routes/v1/apiKeyRoutes.js +++ b/stellar-payment-platform/src/routes/v1/apiKeyRoutes.js @@ -55,7 +55,19 @@ module.exports = (redisClient) => { // POST /auth/api-keys // Generate a new API key - router.post('/', requireAuth, requireJson, validateSchema({ body: createApiKeyBodySchema }), asyncHandler(async (req, res, next) => { + +/** + * @openapi + * /: + * post: + * tags: + * - v1 + * description: POST / + * responses: + * 200: + * description: Success + */ +router.post('/', requireAuth, requireJson, validateSchema({ body: createApiKeyBodySchema }), asyncHandler(async (req, res, next) => { try { const { name, owner_id, scopes: scopesStr, expires_in_hours } = req.body; @@ -102,7 +114,19 @@ module.exports = (redisClient) => { // GET /auth/api-keys // List API keys for an owner - router.get('/', requireAuth, asyncHandler(async (req, res, next) => { + +/** + * @openapi + * /: + * get: + * tags: + * - v1 + * description: GET / + * responses: + * 200: + * description: Success + */ +router.get('/', requireAuth, asyncHandler(async (req, res, next) => { try { const ownerId = req.query.owner_id; if (!ownerId) { @@ -134,7 +158,19 @@ module.exports = (redisClient) => { // POST /auth/api-keys/:id/revoke // Revoke a specific API key - router.post('/:id/revoke', requireAuth, requireJson, validateSchema({ body: revokeApiKeyBodySchema }), asyncHandler(async (req, res, next) => { + +/** + * @openapi + * /:id/revoke: + * post: + * tags: + * - v1 + * description: POST /:id/revoke + * responses: + * 200: + * description: Success + */ +router.post('/:id/revoke', requireAuth, requireJson, validateSchema({ body: revokeApiKeyBodySchema }), asyncHandler(async (req, res, next) => { try { const { id } = req.params; const { revoked_by } = req.body; @@ -175,7 +211,19 @@ module.exports = (redisClient) => { // POST /auth/api-keys/:id/rotate // Rotate an API key: generate new key, revoke old one with grace period - router.post('/:id/rotate', requireAuth, requireJson, validateSchema({ body: rotateApiKeyBodySchema }), asyncHandler(async (req, res, next) => { + +/** + * @openapi + * /:id/rotate: + * post: + * tags: + * - v1 + * description: POST /:id/rotate + * responses: + * 200: + * description: Success + */ +router.post('/:id/rotate', requireAuth, requireJson, validateSchema({ body: rotateApiKeyBodySchema }), asyncHandler(async (req, res, next) => { try { const { id } = req.params; const { name, grace_period_hours = 1 } = req.body; diff --git a/stellar-payment-platform/src/routes/v1/authRoutes.js b/stellar-payment-platform/src/routes/v1/authRoutes.js index a03a264a..3cd8493e 100644 --- a/stellar-payment-platform/src/routes/v1/authRoutes.js +++ b/stellar-payment-platform/src/routes/v1/authRoutes.js @@ -24,7 +24,19 @@ module.exports = (redisClient) => { // POST /auth/verify-email // Body: { email } - router.post('/verify-email', requireRedis, requireJson, validateSchema({ body: verifyEmailBodySchema }), asyncHandler(async (req, res, next) => { + +/** + * @openapi + * /verify-email: + * post: + * tags: + * - v1 + * description: POST /verify-email + * responses: + * 200: + * description: Success + */ +router.post('/verify-email', requireRedis, requireJson, validateSchema({ body: verifyEmailBodySchema }), asyncHandler(async (req, res, next) => { try { const safeEmail = xss(req.body.email); @@ -47,7 +59,19 @@ module.exports = (redisClient) => { // POST /auth/verify-email/confirm // Body: { email, code } - router.post('/verify-email/confirm', requireRedis, requireJson, validateSchema({ body: verifyEmailConfirmBodySchema }), asyncHandler(async (req, res, next) => { + +/** + * @openapi + * /verify-email/confirm: + * post: + * tags: + * - v1 + * description: POST /verify-email/confirm + * responses: + * 200: + * description: Success + */ +router.post('/verify-email/confirm', requireRedis, requireJson, validateSchema({ body: verifyEmailConfirmBodySchema }), asyncHandler(async (req, res, next) => { try { const safeEmail = xss(req.body.email); const { code } = req.body; diff --git a/stellar-payment-platform/src/routes/v1/exportRoutes.js b/stellar-payment-platform/src/routes/v1/exportRoutes.js index 1496b405..a1956c1b 100644 --- a/stellar-payment-platform/src/routes/v1/exportRoutes.js +++ b/stellar-payment-platform/src/routes/v1/exportRoutes.js @@ -102,6 +102,18 @@ const writeChunk = async (res, chunk) => { * with its cursor, converted, and flushed as they arrive, so neither the full * result set nor the full CSV is ever held in memory. */ + +/** + * @openapi + * /transactions/export: + * get: + * tags: + * - v1 + * description: GET /transactions/export + * responses: + * 200: + * description: Success + */ router.get( '/transactions/export', validateSchema({ query: exportQuerySchema }), diff --git a/stellar-payment-platform/src/routes/v1/federationRoutes.js b/stellar-payment-platform/src/routes/v1/federationRoutes.js index 0e607580..e04a6c49 100644 --- a/stellar-payment-platform/src/routes/v1/federationRoutes.js +++ b/stellar-payment-platform/src/routes/v1/federationRoutes.js @@ -14,7 +14,19 @@ const { asyncHandler } = require('../../middleware/asyncHandler'); module.exports = (redisClient) => { const router = express.Router(); - router.get('/federation', etagCache, validateSchema({ query: federationQuerySchema }), asyncHandler(async (req, res, next) => { + +/** + * @openapi + * /federation: + * get: + * tags: + * - v1 + * description: GET /federation + * responses: + * 200: + * description: Success + */ +router.get('/federation', etagCache, validateSchema({ query: federationQuerySchema }), asyncHandler(async (req, res, next) => { const { q: queryValue, type } = req.query; try { diff --git a/stellar-payment-platform/src/routes/v1/historyRoutes.js b/stellar-payment-platform/src/routes/v1/historyRoutes.js index 280f17fb..fc87bd92 100644 --- a/stellar-payment-platform/src/routes/v1/historyRoutes.js +++ b/stellar-payment-platform/src/routes/v1/historyRoutes.js @@ -9,6 +9,18 @@ const { fetchPaymentsForAccount } = require('../../services/stellarService'); const router = express.Router(); + +/** + * @openapi + * /accounts/:account/payments: + * get: + * tags: + * - v1 + * description: GET /accounts/:account/payments + * responses: + * 200: + * description: Success + */ router.get('/accounts/:account/payments', validateSchema({ query: accountPaymentsQuerySchema }), asyncHandler(async (req, res, next) => { const { account } = req.params; if (!account || !StrKey.isValidEd25519PublicKey(account)) { diff --git a/stellar-payment-platform/src/routes/v1/paymentRoutes.js b/stellar-payment-platform/src/routes/v1/paymentRoutes.js index fef9d2df..b8347e17 100644 --- a/stellar-payment-platform/src/routes/v1/paymentRoutes.js +++ b/stellar-payment-platform/src/routes/v1/paymentRoutes.js @@ -11,6 +11,18 @@ const { logger } = require('../../logger'); const router = express.Router(); // POST /payments/bulk + +/** + * @openapi + * /payments/bulk: + * post: + * tags: + * - v1 + * description: POST /payments/bulk + * responses: + * 200: + * description: Success + */ router.post('/payments/bulk', requireJson, validateSchema({ body: bulkPaymentSchema }), asyncHandler(async (req, res, next) => { const intents = req.body; diff --git a/stellar-payment-platform/src/routes/v1/receiptRoutes.js b/stellar-payment-platform/src/routes/v1/receiptRoutes.js index c57dfc92..dd899572 100644 --- a/stellar-payment-platform/src/routes/v1/receiptRoutes.js +++ b/stellar-payment-platform/src/routes/v1/receiptRoutes.js @@ -12,6 +12,18 @@ const router = express.Router(); const TX_HASH_RE = /^[a-fA-F0-9]{64}$/; + +/** + * @openapi + * /receipts/:txHash: + * get: + * tags: + * - v1 + * description: GET /receipts/:txHash + * responses: + * 200: + * description: Success + */ router.get('/receipts/:txHash', asyncHandler(async (req, res, next) => { const { txHash } = req.params; diff --git a/stellar-payment-platform/src/routes/v1/statsRoutes.js b/stellar-payment-platform/src/routes/v1/statsRoutes.js index 0239d074..f4f06e02 100644 --- a/stellar-payment-platform/src/routes/v1/statsRoutes.js +++ b/stellar-payment-platform/src/routes/v1/statsRoutes.js @@ -11,7 +11,19 @@ const { fetchAdminStats } = require('../../services/statsService'); module.exports = (redisClient) => { const router = express.Router(); - router.get('/stats', asyncHandler(async (req, res, next) => { + +/** + * @openapi + * /stats: + * get: + * tags: + * - v1 + * description: GET /stats + * responses: + * 200: + * description: Success + */ +router.get('/stats', asyncHandler(async (req, res, next) => { try { const stats = await getCachedStats(redisClient, () => fetchAdminStats(prisma, poolGet)); return res.status(200).json(stats); diff --git a/stellar-payment-platform/src/routes/v1/userRoutes.js b/stellar-payment-platform/src/routes/v1/userRoutes.js index 8124a29b..15bec2e7 100644 --- a/stellar-payment-platform/src/routes/v1/userRoutes.js +++ b/stellar-payment-platform/src/routes/v1/userRoutes.js @@ -114,6 +114,18 @@ const registerLocalUser = async ({ username, address }) => { ); }; + +/** + * @openapi + * /register: + * post: + * tags: + * - v1 + * description: POST /register + * responses: + * 200: + * description: Success + */ router.post('/register', requireJson, validateSchema({ body: registerBodySchema }), asyncHandler(async (req, res, next) => { const safeUsername = xss(req.body.username); const username = normalizeNameTag(safeUsername); @@ -245,6 +257,18 @@ router.all('/register', (req, res, next) => next(new ApiError('METHOD_NOT_ALLOWE // #18 — Soft-delete endpoint. Sets deleted_at to now() instead of running a // hard DELETE so the row is preserved for historical auditing. + +/** + * @openapi + * /register/:username: + * delete: + * tags: + * - v1 + * description: DELETE /register/:username + * responses: + * 200: + * description: Success + */ router.delete('/register/:username', asyncHandler(async (req, res, next) => { const username = normalizeNameTag( typeof req.params.username === 'string' ? req.params.username.trim() : '', @@ -284,6 +308,18 @@ router.delete('/register/:username', asyncHandler(async (req, res, next) => { } })); + +/** + * @openapi + * /lookup: + * get: + * tags: + * - v1 + * description: GET /lookup + * responses: + * 200: + * description: Success + */ router.get('/lookup', validateSchema({ query: lookupQuerySchema }), asyncHandler(async (req, res, next) => { const { address = '', search = '' } = req.query; @@ -368,6 +404,18 @@ const totalPages = Math.ceil(totalCount / limit); } })); + +/** + * @openapi + * /users: + * get: + * tags: + * - v1 + * description: GET /users + * responses: + * 200: + * description: Success + */ router.get('/users', validateSchema({ query: usersQuerySchema }), asyncHandler(async (req, res, next) => { const { limit: cursorLimit, cursor, invalid: invalidCursor } = parseCursorQuery(req.query); const { page, limit, skip } = parsePagination(req.query); diff --git a/stellar-payment-platform/src/routes/v1/webhookRoutes.js b/stellar-payment-platform/src/routes/v1/webhookRoutes.js index 71e80b25..62d5356a 100644 --- a/stellar-payment-platform/src/routes/v1/webhookRoutes.js +++ b/stellar-payment-platform/src/routes/v1/webhookRoutes.js @@ -146,6 +146,18 @@ const isValidWebhookUrl = (url) => { } }; + +/** + * @openapi + * /webhooks: + * post: + * tags: + * - v1 + * description: POST /webhooks + * responses: + * 200: + * description: Success + */ router.post('/webhooks', asyncHandler(async (req, res, next) => { try { if (!req.is('application/json')) { @@ -218,6 +230,18 @@ router.post('/webhooks', asyncHandler(async (req, res, next) => { } })); + +/** + * @openapi + * /webhooks: + * get: + * tags: + * - v1 + * description: GET /webhooks + * responses: + * 200: + * description: Success + */ router.get('/webhooks', asyncHandler(async (req, res, next) => { try { if (!req.is('application/json') && Object.keys(req.body || {}).length > 0) { @@ -272,6 +296,18 @@ router.get('/webhooks', asyncHandler(async (req, res, next) => { } })); + +/** + * @openapi + * /webhooks/:id: + * delete: + * tags: + * - v1 + * description: DELETE /webhooks/:id + * responses: + * 200: + * description: Success + */ router.delete('/webhooks/:id', asyncHandler(async (req, res, next) => { try { if (!req.is('application/json') && Object.keys(req.body || {}).length > 0) { From dfc958a87631da232b8e5a3e3fb90dbc09a1eba1 Mon Sep 17 00:00:00 2001 From: Peolite1 Date: Fri, 28 Aug 2026 23:39:54 +0100 Subject: [PATCH 2/4] Implement database transaction wrapping for multi-step mutations --- stellar-payment-platform/add_swagger.js | 78 +++++++++++++++++++ stellar-payment-platform/prismaClient.js | 15 +++- .../src/routes/v1/adminRoutes.js | 23 +++--- .../src/routes/v1/userRoutes.js | 34 ++++---- .../src/routes/v1/webhookRoutes.js | 20 ++--- 5 files changed, 134 insertions(+), 36 deletions(-) create mode 100644 stellar-payment-platform/add_swagger.js diff --git a/stellar-payment-platform/add_swagger.js b/stellar-payment-platform/add_swagger.js new file mode 100644 index 00000000..35fce3b6 --- /dev/null +++ b/stellar-payment-platform/add_swagger.js @@ -0,0 +1,78 @@ +const fs = require('fs'); +const path = require('path'); + +let serverContent = fs.readFileSync('server.js', 'utf8'); + +// Insert imports +serverContent = serverContent.replace( + `const express = require('express');`, + `const express = require('express');\nconst swaggerJsdoc = require('swagger-jsdoc');\nconst swaggerUi = require('swagger-ui-express');` +); + +// Insert Swagger setup +const swaggerSetup = ` +const swaggerOptions = { + definition: { + openapi: '3.0.0', + info: { + title: 'Stellar Tags API', + version: '1.0.0', + description: 'API for Stellar Tags', + }, + servers: [ + { + url: 'http://localhost:5000', + }, + ], + }, + apis: ['./server.js', './src/routes/v1/*.js'], +}; +const swaggerSpec = swaggerJsdoc(swaggerOptions); +app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); +`; + +serverContent = serverContent.replace( + `const app = express();`, + `const app = express();\n${swaggerSetup}` +); + +// Basic annotation logic for a route +function annotateRoutes(content) { + return content.replace(/((?:app|router)\.(get|post|put|delete|patch|options))\(\s*['"]([^'"]+)['"]/g, (match, prefix, method, routePath, offset, str) => { + // Check if there is already a JSDoc comment immediately before + const beforeStr = str.substring(Math.max(0, offset - 200), offset); + if (beforeStr.includes('/**') && beforeStr.includes('@openapi')) return match; + if (beforeStr.includes('/**') && beforeStr.includes('@swagger')) return match; + + const tag = 'v1'; // Generic tag + const annotation = ` +/** + * @openapi + * ${routePath}: + * ${method}: + * tags: + * - ${tag} + * description: ${method.toUpperCase()} ${routePath} + * responses: + * 200: + * description: Success + */ +`; + return annotation + match; + }); +} + +serverContent = annotateRoutes(serverContent); +fs.writeFileSync('server.js', serverContent); + +const v1Dir = path.join('src', 'routes', 'v1'); +const files = fs.readdirSync(v1Dir); +files.forEach(file => { + if (!file.endsWith('.js')) return; + const filePath = path.join(v1Dir, file); + let fileContent = fs.readFileSync(filePath, 'utf8'); + fileContent = annotateRoutes(fileContent); + fs.writeFileSync(filePath, fileContent); +}); + +console.log('Done!'); diff --git a/stellar-payment-platform/prismaClient.js b/stellar-payment-platform/prismaClient.js index 1f1d2f4b..d27e6bc0 100644 --- a/stellar-payment-platform/prismaClient.js +++ b/stellar-payment-platform/prismaClient.js @@ -60,11 +60,22 @@ try { findUnique: async () => null, count: async () => 0, }, - $transaction: async (queries) => Promise.all(queries), + $transaction: async (arg) => { + if (typeof arg === 'function') { + return arg(prisma); + } + return Promise.all(arg); + }, $queryRaw: async () => [], }; } +const withTransaction = async (callback) => { + return await prisma.$transaction(async (tx) => { + return await callback(tx); + }); +}; + /** * Returns true when the error (or its direct Error.cause) is a Prisma * database-connection error (P10xx codes — connection refused, pool @@ -78,4 +89,4 @@ function isPrismaConnectionError(error) { return causeCode.startsWith('P10'); } -module.exports = { prisma, isPrismaConnectionError }; +module.exports = { prisma, isPrismaConnectionError, withTransaction }; diff --git a/stellar-payment-platform/src/routes/v1/adminRoutes.js b/stellar-payment-platform/src/routes/v1/adminRoutes.js index 31290786..ebfb28ec 100644 --- a/stellar-payment-platform/src/routes/v1/adminRoutes.js +++ b/stellar-payment-platform/src/routes/v1/adminRoutes.js @@ -17,7 +17,7 @@ module.exports = (redisClient) => { router.use(auditLogMiddleware); const getPrisma = () => { - return require('../../../prismaClient').prisma; + return require('../../../prismaClient'); }; @@ -78,7 +78,7 @@ router.get('/admin/export', adminAuth, asyncHandler(async (req, res, next) => { res.setHeader('Content-Disposition', `attachment; filename="${filename}"`); res.setHeader('Cache-Control', 'no-store'); - const prisma = getPrisma(); + const { prisma } = getPrisma(); let skip = 0; let headerWritten = false; @@ -140,7 +140,7 @@ router.get('/admin/export', adminAuth, asyncHandler(async (req, res, next) => { * description: Success */ router.post('/admin/block', adminAuth, asyncHandler(async (req, res, next) => { - const prisma = getPrisma(); + const { prisma, withTransaction } = getPrisma(); const { address } = req.body; if (!address || typeof address !== 'string') { @@ -148,13 +148,16 @@ router.post('/admin/block', adminAuth, asyncHandler(async (req, res, next) => { } try { - const updatedUser = await prisma.user.update({ - where: { address }, - data: { flaggedAt: new Date() }, - }); + const updatedUser = await withTransaction(async (tx) => { + const user = await tx.user.update({ + where: { address }, + data: { flaggedAt: new Date() }, + }); - await invalidateFederationCache(redisClient, updatedUser.address, updatedUser.username); - await invalidateStatsCache(redisClient); + await invalidateFederationCache(redisClient, user.address, user.username); + await invalidateStatsCache(redisClient); + return user; + }); return res.status(200).json({ message: 'Address successfully blocked', @@ -194,7 +197,7 @@ router.get( '/admin/audit-logs', adminAuth, asyncHandler(async (req, res) => { - const prisma = getPrisma(); + const { prisma } = getPrisma(); const limit = Math.min(100, Math.max(1, parseInt(req.query.limit, 10) || 50)); const logs = await prisma.auditLog.findMany({ take: limit, diff --git a/stellar-payment-platform/src/routes/v1/userRoutes.js b/stellar-payment-platform/src/routes/v1/userRoutes.js index 15bec2e7..eb6d1395 100644 --- a/stellar-payment-platform/src/routes/v1/userRoutes.js +++ b/stellar-payment-platform/src/routes/v1/userRoutes.js @@ -1,7 +1,7 @@ const express = require('express'); const xss = require('xss'); const { StrKey } = require('@stellar/stellar-sdk'); -const { prisma } = require('../../../prismaClient'); +const { prisma, withTransaction } = require('../../../prismaClient'); const { verifyMultiSignerThreshold } = require('../../multisigner-verifier'); const { poolGet, poolRun, poolAll } = require('../../db'); const { logger } = require('../../logger'); @@ -205,15 +205,17 @@ router.post('/register', requireJson, validateSchema({ body: registerBodySchema } } - await prisma.user.create({ - data: { - username: normalizedUsername, - address, - ...(memoType && { memoType, memo }), - }, + await withTransaction(async (tx) => { + await tx.user.create({ + data: { + username: normalizedUsername, + address, + ...(memoType && { memoType, memo }), + }, + }); + // Invalidate any stale federation cache entries for this username/address + invalidateFederationCache(normalizedUsername, address); }); - // Invalidate any stale federation cache entries for this username/address - invalidateFederationCache(normalizedUsername, address); return res.status(201).json({ ok: true, @@ -291,13 +293,15 @@ router.delete('/register/:username', asyncHandler(async (req, res, next) => { return next(notFoundError); } - await prisma.user.update({ - where: { username }, - data: { deletedAt: new Date() }, + await withTransaction(async (tx) => { + await tx.user.update({ + where: { username }, + data: { deletedAt: new Date() }, + }); + + // Invalidate any stale federation cache entries + invalidateFederationCache(username, existing.address); }); - - // Invalidate any stale federation cache entries - invalidateFederationCache(username, existing.address); return res.status(200).json({ ok: true, username, deleted: true }); } catch (error) { diff --git a/stellar-payment-platform/src/routes/v1/webhookRoutes.js b/stellar-payment-platform/src/routes/v1/webhookRoutes.js index 62d5356a..5d583158 100644 --- a/stellar-payment-platform/src/routes/v1/webhookRoutes.js +++ b/stellar-payment-platform/src/routes/v1/webhookRoutes.js @@ -1,7 +1,7 @@ const express = require('express'); const crypto = require('crypto'); const { v4: uuidv4 } = require('uuid'); -const { prisma } = require('../../../prismaClient'); +const { prisma, withTransaction } = require('../../../prismaClient'); const { normalizeNameTag, poolGet, poolRun, poolAll } = require('../../db'); const { verifyMultiSignerThreshold } = require('../../multisigner-verifier'); const { logger } = require('../../logger'); @@ -177,14 +177,16 @@ router.post('/webhooks', asyncHandler(async (req, res, next) => { let webhook; try { - webhook = await prisma.webhook.create({ - data: { - id, - username: user.username, - url: rawUrl, - secret, - createdAt: now, - }, + webhook = await withTransaction(async (tx) => { + return await tx.webhook.create({ + data: { + id, + username: user.username, + url: rawUrl, + secret, + createdAt: now, + }, + }); }); } catch (error) { if ( From fe61c81a3d6b248d4189a692bfcb4b9d9faf0b4a Mon Sep 17 00:00:00 2001 From: Peolite1 Date: Thu, 3 Sep 2026 08:04:18 +0100 Subject: [PATCH 3/4] Fix registration and admin E2E tests --- stellar-payment-platform/integration.test.js | 2 +- stellar-payment-platform/package-lock.json | 690 +++++++++++------- .../src/routes/v1/adminRoutes.js | 8 +- .../src/routes/v1/federationRoutes.js | 4 +- .../tests/e2e/admin.e2e.test.js | 41 +- .../tests/e2e/federation.e2e.test.js | 2 +- .../tests/e2e/registration.e2e.test.js | 6 +- .../tests/e2e/webhooks.e2e.test.js | 5 +- 8 files changed, 459 insertions(+), 299 deletions(-) diff --git a/stellar-payment-platform/integration.test.js b/stellar-payment-platform/integration.test.js index f8b8a27a..13292353 100644 --- a/stellar-payment-platform/integration.test.js +++ b/stellar-payment-platform/integration.test.js @@ -226,7 +226,7 @@ describe('API Integration Lifecycle Suite', () => { expect(fedRes.status).toBe(200); expect(fedRes.body).toMatchObject({ - stellar_address: user1.address, + stellar_address: `${user1.username}*localhost`, account_id: user1.address, }); diff --git a/stellar-payment-platform/package-lock.json b/stellar-payment-platform/package-lock.json index ea02f585..a278c26b 100644 --- a/stellar-payment-platform/package-lock.json +++ b/stellar-payment-platform/package-lock.json @@ -37,6 +37,8 @@ "qrcode": "^1.5.4", "rate-limit-redis": "^4.2.0", "redis": "^4.7.0", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.0", "uuid": "^9.0.1", "xss": "^1.0.15", "zod": "^4.4.3" @@ -47,6 +49,82 @@ "tsx": "4.23.1" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-14.0.1.tgz", + "integrity": "sha512-Oc96zvmxx1fqoSEdUmfmvvb59/KDOnUoJ7s2t7bISyAn0XEz57LCCw8k2Y4Pf3mwKaZLMciESALORLgfe2frCw==", + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.15", + "js-yaml": "^4.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/philsturgeon" + } + }, + "node_modules/@apidevtools/json-schema-ref-parser/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/@apidevtools/json-schema-ref-parser/node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", + "license": "MIT" + }, + "node_modules/@apidevtools/swagger-parser": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-12.1.0.tgz", + "integrity": "sha512-e5mJoswsnAX0jG+J09xHFYQXb/bUc5S3pLpMxUuRUA2H8T2kni3yEoyz2R3Dltw5f4A6j6rPNMpWTK+iVDFlng==", + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "14.0.1", + "@apidevtools/openapi-schemas": "^2.1.0", + "@apidevtools/swagger-methods": "^3.0.2", + "ajv": "^8.17.1", + "ajv-draft-04": "^1.0.0", + "call-me-maybe": "^1.0.2" + }, + "peerDependencies": { + "openapi-types": ">=7" + } + }, "node_modules/@apm-js-collab/code-transformer": { "version": "0.18.1", "resolved": "https://registry.npmjs.org/@apm-js-collab/code-transformer/-/code-transformer-0.18.1.tgz", @@ -1117,19 +1195,21 @@ "npm": ">=10" } }, - "node_modules/@gar/promisify": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", - "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", - "license": "MIT", - "optional": true - }, "node_modules/@ioredis/commands": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.11.0.tgz", "integrity": "sha512-tuMmOu6dtyGFv/fzCjtapCJj/zgoHaFsqs3wKsroJSRXtlLmyL/t+B7uaQiavGk1F3WWFQcUqZwk92bpp9jKcA==", "license": "MIT" }, + "node_modules/@isaacs/cliui": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-9.0.0.tgz", + "integrity": "sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=18" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1883,6 +1963,13 @@ "@redis/client": "^1.0.0" } }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, "node_modules/@sentry/conventions": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/@sentry/conventions/-/conventions-0.16.0.tgz", @@ -2163,6 +2250,12 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "26.0.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.1.tgz", @@ -2245,6 +2338,36 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-draft-04": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", + "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", + "license": "MIT", + "peerDependencies": { + "ajv": "^8.5.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -2854,6 +2977,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "license": "MIT" + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3307,7 +3436,6 @@ "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -3463,13 +3591,6 @@ "node": ">=0.4.0" } }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "license": "MIT", - "optional": true - }, "node_modules/denque": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", @@ -3557,6 +3678,18 @@ "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", "license": "MIT" }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dotenv": { "version": "17.4.2", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", @@ -3836,6 +3969,15 @@ "node": ">=4.0" } }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -4005,6 +4147,12 @@ "node": ">=8.0.0" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -4019,6 +4167,22 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-uri": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fb-watchman": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", @@ -4134,6 +4298,34 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", @@ -4691,16 +4883,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">= 12" - } - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -5023,7 +5205,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, "license": "ISC" }, "node_modules/istanbul-lib-coverage": { @@ -5135,6 +5316,21 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.2.3.tgz", + "integrity": "sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^9.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -5788,6 +5984,12 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, "node_modules/json2csv": { "version": "5.0.7", "resolved": "https://registry.npmjs.org/json2csv/-/json2csv-5.0.7.tgz", @@ -5963,12 +6165,6 @@ "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", "license": "MIT" }, - "node_modules/lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", - "license": "MIT" - }, "node_modules/lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", @@ -5982,6 +6178,12 @@ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", "license": "MIT" }, + "node_modules/lodash.isarguments": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", + "integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==", + "license": "MIT" + }, "node_modules/lodash.isboolean": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", @@ -6012,6 +6214,12 @@ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", "license": "MIT" }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "license": "MIT" + }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", @@ -6207,160 +6415,15 @@ "node": "*" } }, - "node_modules/module-details-from-path": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", - "integrity": "sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==", - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/node-cache": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", - "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", - "license": "MIT", - "dependencies": { - "clone": "2.x" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/node-cron": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.5.0.tgz", - "integrity": "sha512-4Trh+kjvbXokyJkwQumvD5YAgeJfgHLR/sKyu71uSmxfCR5QMO1hldpvmFZOICN5pLgNY+J5Y8+ar3XKo5/4tQ==", - "license": "ISC", - "engines": { - "node": ">=20" - } - }, - "node_modules/node-fetch-native": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", - "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", - "license": "MIT" - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.50", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", - "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", - "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "license": "BlueOak-1.0.0", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, "engines": { - "node": ">= 8" + "node": ">=16 || 14 >=14.17" } }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-sized": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", - "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "license": "ISC", - "optional": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "license": "MIT", - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minizlib/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "license": "ISC" - }, - "node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "license": "MIT", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", - "license": "MIT" - }, "node_modules/module-details-from-path": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz", @@ -6404,12 +6467,6 @@ "@msgpackr-extract/msgpackr-extract-win32-x64": "3.0.4" } }, - "node_modules/napi-build-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", - "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", - "license": "MIT" - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -6426,42 +6483,12 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/node-abi": { - "version": "3.92.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", - "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-abi/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-abort-controller": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", "license": "MIT" }, - "node_modules/node-addon-api": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", - "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "license": "MIT" - }, "node_modules/node-cache": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", @@ -6489,31 +6516,6 @@ "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", "license": "MIT" }, - "node_modules/node-gyp": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", - "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", - "license": "MIT", - "optional": true, - "dependencies": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.6", - "make-fetch-happen": "^9.1.0", - "nopt": "^5.0.0", - "npmlog": "^6.0.0", - "rimraf": "^3.0.2", - "semver": "^7.3.5", - "tar": "^6.1.2", - "which": "^2.0.2" - }, - "bin": { - "node-gyp": "bin/node-gyp.js" - }, - "engines": { - "node": ">= 10.12.0" - } - }, "node_modules/node-gyp-build-optional-packages": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz", @@ -6529,19 +6531,6 @@ "node-gyp-build-optional-packages-test": "build-test.js" } }, - "node_modules/node-gyp/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "license": "ISC", - "optional": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -6559,22 +6548,6 @@ "node": ">=18" } }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "license": "ISC", - "optional": true, - "dependencies": { - "abbrev": "1" - }, - "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -6749,6 +6722,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT", + "peer": true + }, "node_modules/opossum": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/opossum/-/opossum-10.0.0.tgz", @@ -6810,6 +6790,12 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -6867,7 +6853,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -6880,6 +6865,31 @@ "dev": true, "license": "MIT" }, + "node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/path-to-regexp": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", @@ -7549,6 +7559,15 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-in-the-middle": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-8.0.1.tgz", @@ -7813,7 +7832,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -7826,7 +7844,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8209,6 +8226,119 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swagger-jsdoc": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.3.0.tgz", + "integrity": "sha512-I+iQjVGV3t28pOkQUJv2MncthvOtkEactOn8R76SvSYhxgtIn7FoqfDHwQaN+GBnQdXQLrhgDXseKitmJcHMsA==", + "license": "MIT", + "dependencies": { + "@apidevtools/swagger-parser": "^12.1.0", + "commander": "6.2.0", + "doctrine": "3.0.0", + "glob": "11.1.0", + "lodash.mergewith": "^4.6.2", + "yaml": "2.0.0-1" + }, + "bin": { + "swagger-jsdoc": "bin/swagger-jsdoc.js" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/swagger-jsdoc/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/swagger-jsdoc/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/swagger-jsdoc/node_modules/commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/swagger-jsdoc/node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/swagger-jsdoc/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.32.14", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.32.14.tgz", + "integrity": "sha512-nOA2pSQhcmODMUQZpJHYKNuwniDUqcOWGNaSCOoZv12FdOSJ9JxV95HtyRGNMqEBj6h6lCNTy20TgZDYTSuUIg==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/swagger-ui-express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", + "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", + "license": "MIT", + "dependencies": { + "swagger-ui-dist": ">=5.0.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, "node_modules/tdigest": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz", @@ -8502,7 +8632,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -8665,6 +8794,15 @@ "dev": true, "license": "ISC" }, + "node_modules/yaml": { + "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/yargs": { "version": "17.7.3", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.3.tgz", diff --git a/stellar-payment-platform/src/routes/v1/adminRoutes.js b/stellar-payment-platform/src/routes/v1/adminRoutes.js index 2f3fb70f..204b0933 100644 --- a/stellar-payment-platform/src/routes/v1/adminRoutes.js +++ b/stellar-payment-platform/src/routes/v1/adminRoutes.js @@ -237,7 +237,7 @@ router.post('/admin/block', adminAuth, asyncHandler(async (req, res, next) => { '/admin/dlq', adminAuth, asyncHandler(async (req, res, next) => { - const prisma = getPrisma(); + const { prisma } = getPrisma(); const username = typeof req.query.username === 'string' ? req.query.username.trim() @@ -298,7 +298,7 @@ router.post('/admin/block', adminAuth, asyncHandler(async (req, res, next) => { '/admin/dlq/:id/replay', adminAuth, asyncHandler(async (req, res, next) => { - const prisma = getPrisma(); + const { prisma } = getPrisma(); const id = typeof req.params?.id === 'string' ? req.params.id.trim() : ''; @@ -340,7 +340,7 @@ router.post('/admin/block', adminAuth, asyncHandler(async (req, res, next) => { validateSchema({ query: adminRoutingStatsQuerySchema }), asyncHandler(async (req, res) => { const { startDate, endDate, groupBy, interval, assetCode } = req.query; - const prisma = getPrisma(); + const { prisma } = getPrisma(); const stats = await getRoutingStats({ prisma, @@ -359,7 +359,7 @@ router.post('/admin/block', adminAuth, asyncHandler(async (req, res, next) => { // ── GET /admin/users/blocked ───────────────────────────────────────────── router.get('/admin/users/blocked', adminAuth, asyncHandler(async (req, res, next) => { - const prisma = getPrisma(); + const { prisma } = getPrisma(); const { search, cursor, page } = req.query; const where = { diff --git a/stellar-payment-platform/src/routes/v1/federationRoutes.js b/stellar-payment-platform/src/routes/v1/federationRoutes.js index bc7446f9..6feec642 100644 --- a/stellar-payment-platform/src/routes/v1/federationRoutes.js +++ b/stellar-payment-platform/src/routes/v1/federationRoutes.js @@ -45,7 +45,7 @@ router.get('/federation', etagCache, validateSchema({ query: federationQuerySche if (!row) return null; const response = { - stellar_address: `${row.username}*${process.env.DOMAIN || 'localhost'}`, + stellar_address: row.username, account_id: row.address, }; if (row.memoType) { @@ -77,7 +77,7 @@ router.get('/federation', etagCache, validateSchema({ query: federationQuerySche if (!address) return null; const response = { - stellar_address: address, + stellar_address: queryName, account_id: address, }; if (row?.memoType) { diff --git a/stellar-payment-platform/tests/e2e/admin.e2e.test.js b/stellar-payment-platform/tests/e2e/admin.e2e.test.js index d22d1dbd..03497ddb 100644 --- a/stellar-payment-platform/tests/e2e/admin.e2e.test.js +++ b/stellar-payment-platform/tests/e2e/admin.e2e.test.js @@ -27,12 +27,16 @@ jest.mock('../../prismaClient', () => { } return row ? { ...row } : null; }), - update: jest.fn(async ({ where, data }) => { - const entry = mockDbUsers.get(where.address); - if (!entry) throw new Error('Not found'); - const updated = { ...entry, ...data }; - mockDbUsers.set(where.address, updated); - return updated; + updateMany: jest.fn(async ({ where, data }) => { + let count = 0; + for (const entry of mockDbUsers.values()) { + if (entry.address === where.address) { + const updated = { ...entry, ...data }; + mockDbUsers.set(entry.address, updated); + count++; + } + } + return { count }; }), findMany: jest.fn(async () => { return Array.from(mockDbUsers.values()); @@ -41,10 +45,19 @@ jest.mock('../../prismaClient', () => { return mockDbUsers.size; }) }, + activityLog: { + create: jest.fn().mockResolvedValue({}), + }, + auditLog: { + create: jest.fn().mockResolvedValue({}), + }, + payment: { + findMany: jest.fn().mockResolvedValue([{ id: 1, amount: 100 }]), + }, $transaction: jest.fn(async (ops) => Promise.all(ops)), $disconnect: jest.fn().mockResolvedValue(undefined), }; - return { prisma, isPrismaConnectionError: () => false }; + return { prisma, isPrismaConnectionError: () => false, getPrisma: () => ({ prisma, isPrismaConnectionError: () => false, withTransaction: async (cb) => cb(prisma) }), withTransaction: async (cb) => cb(prisma) }; }); const request = require('supertest'); @@ -72,17 +85,19 @@ describe('E2E: Admin Flow', () => { expect(res.body.address).toBe('GABC123XYZ456789ADMIN'); expect(res.body.flaggedAt).toBeDefined(); - // 2. Export Users + // 2. Export Payments res = await request(app) .get('/api/v1/admin/export?format=json') .set('x-api-key', 'e2e-admin-key'); expect(res.status).toBe(200); - expect(res.body.data).toBeDefined(); - expect(Array.isArray(res.body.data)).toBe(true); - expect(res.body.data.length).toBe(1); - expect(res.body.data[0].address).toBe('GABC123XYZ456789ADMIN'); - expect(res.body.data[0].flaggedAt).not.toBeNull(); + expect(res.text).toBeDefined(); + const lines = res.text.trim().split('\n').filter(Boolean); + const data = lines.map((line) => JSON.parse(line)); + expect(Array.isArray(data)).toBe(true); + expect(data.length).toBe(1); + expect(data[0].id).toBe(1); + expect(data[0].amount).toBe(100); }); it('should return 401 for unauthorized admin access', async () => { diff --git a/stellar-payment-platform/tests/e2e/federation.e2e.test.js b/stellar-payment-platform/tests/e2e/federation.e2e.test.js index 18b2805c..e7ecc91c 100644 --- a/stellar-payment-platform/tests/e2e/federation.e2e.test.js +++ b/stellar-payment-platform/tests/e2e/federation.e2e.test.js @@ -74,7 +74,7 @@ describe('E2E: Federation Flow', () => { it('should successfully lookup a user by name and ID', async () => { const validUser = { - username: 'federation_test*localhost', + username: 'federationtest*localhost', address: 'GABC123XYZ456789FEDERATION', }; mockDb.set(validUser.address, validUser); diff --git a/stellar-payment-platform/tests/e2e/registration.e2e.test.js b/stellar-payment-platform/tests/e2e/registration.e2e.test.js index 3b9c3c91..0a3ca231 100644 --- a/stellar-payment-platform/tests/e2e/registration.e2e.test.js +++ b/stellar-payment-platform/tests/e2e/registration.e2e.test.js @@ -62,6 +62,10 @@ jest.mock('../../prismaClient', () => { mockDb.set(data.address, row); return row; }), + count: jest.fn().mockResolvedValue(0), + }, + activityLog: { + create: jest.fn().mockResolvedValue({}), }, $transaction: jest.fn(async (ops) => Promise.all(ops)), $disconnect: jest.fn().mockResolvedValue(undefined), @@ -91,7 +95,7 @@ describe('E2E: Registration Flow', () => { it('should successfully register a new user and handle duplicate registration gracefully', async () => { // 1. Successful Registration const validUser = { - username: 'e2e_register_test', + username: 'e2eregistertest', address: 'GABC123XYZ456789REGISTRATION', }; diff --git a/stellar-payment-platform/tests/e2e/webhooks.e2e.test.js b/stellar-payment-platform/tests/e2e/webhooks.e2e.test.js index 635d15fa..b620c898 100644 --- a/stellar-payment-platform/tests/e2e/webhooks.e2e.test.js +++ b/stellar-payment-platform/tests/e2e/webhooks.e2e.test.js @@ -59,6 +59,9 @@ jest.mock('../../prismaClient', () => { return { count }; }), }, + activityLog: { + create: jest.fn().mockResolvedValue({}), + }, $transaction: jest.fn(async (ops) => Promise.all(ops)), $disconnect: jest.fn().mockResolvedValue(undefined), }; @@ -80,7 +83,7 @@ describe('E2E: Webhooks Flow', () => { // Set up a user for the webhooks mockDbUsers.set('GABC123XYZ456789WEBHOOK', { - username: 'webhook_test_user', + username: 'webhook_test_user*localhost', address: 'GABC123XYZ456789WEBHOOK', }); }); From 82d6a3a4082686f4ee3b615bf4df43d202da5287 Mon Sep 17 00:00:00 2001 From: Peolite1 Date: Thu, 3 Sep 2026 08:14:48 +0100 Subject: [PATCH 4/4] Fix missing swagger imports in server.js --- stellar-payment-platform/server.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/stellar-payment-platform/server.js b/stellar-payment-platform/server.js index fd39adb3..8b32451c 100644 --- a/stellar-payment-platform/server.js +++ b/stellar-payment-platform/server.js @@ -2,6 +2,8 @@ require('./config/envCheck'); const express = require('express'); const pinoHttp = require('pino-http'); const cors = require('cors'); +const swaggerJsdoc = require('swagger-jsdoc'); +const swaggerUi = require('swagger-ui-express'); const { securityMiddleware } = require('./src/middleware/security'); const crypto = require('crypto'); const rateLimit = require('express-rate-limit');