Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions backend/src/middleware/requestLogger.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,16 @@ export function requestLogger(req, res, next) {
const startAt = process.hrtime.bigint();

res.on('finish', () => {
const durationMs = Number(process.hrtime.bigint() - startAt) / 1e6;
const durationMs = parseFloat((Number(process.hrtime.bigint() - startAt) / 1e6).toFixed(2));
const level = res.statusCode >= 500 ? 'error' : res.statusCode >= 400 ? 'warn' : 'info';

logger[level]('http', {
correlationId,
method: req.method,
path: req.path,
url: req.originalUrl,
status: res.statusCode,
durationMs: durationMs.toFixed(2),
durationMs,
contentLength: res.getHeader('content-length') ?? null,
userAgent: req.headers['user-agent'] ?? null,
});
Expand Down
40 changes: 31 additions & 9 deletions backend/src/routes/multiSig.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,22 @@ import express from 'express';
import { validate, rules } from '../middleware/validate.js';
import * as MultiSigService from '../services/multiSig.js';
import { broadcastToAccount } from '../services/websocket.js';
import logger from '../config/logger.js';

const router = express.Router();

function logError(req, error, context = {}) {
logger.error('route.error', {
requestId: req.id,
correlationId: req.correlationId,
method: req.method,
path: req.path,
...context,
error: error.message,
stack: error.stack,
});
}

/**
* @swagger
* /api/multisig/account/create:
Expand Down Expand Up @@ -63,7 +76,8 @@ router.post('/account/create', rules.createMultiSig, validate, async (req, res)
broadcastToAccount(result.publicKey, { type: 'multisig_created', ...result });
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error);
res.status(500).json({ error: 'Failed to create multi-sig account' });
}
});

Expand Down Expand Up @@ -91,7 +105,8 @@ router.get('/account/:publicKey', rules.publicKeyParam, validate, async (req, re
const config = await MultiSigService.getMultiSigConfig(req.params.publicKey);
res.json(config);
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { publicKey: req.params.publicKey });
res.status(500).json({ error: 'Failed to retrieve multi-sig configuration' });
}
});

Expand Down Expand Up @@ -132,7 +147,8 @@ router.post('/account/update', rules.updateMultiSig, validate, async (req, res)
const result = await MultiSigService.updateMultiSigConfig(sourceSecret, updates);
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error);
res.status(500).json({ error: 'Failed to update multi-sig configuration' });
}
});

Expand Down Expand Up @@ -177,7 +193,8 @@ router.post('/transaction/build', rules.buildMultiSigTx, validate, async (req, r
broadcastToAccount(sourcePublicKey, { type: 'multisig_tx_pending', ...result });
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { destination: req.body.destination, amount: req.body.amount });
res.status(500).json({ error: 'Failed to build multi-sig transaction' });
}
});

Expand Down Expand Up @@ -211,7 +228,8 @@ router.post('/transaction/sign', rules.signMultiSigTx, validate, async (req, res
const result = await MultiSigService.addSignature(txId, signerSecret);
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { txId: req.body.txId });
res.status(500).json({ error: 'Failed to add signature' });
}
});

Expand Down Expand Up @@ -244,7 +262,8 @@ router.post('/transaction/submit', rules.submitMultiSigTx, validate, async (req,
broadcastToAccount(result.hash, { type: 'multisig_tx_submitted', ...result });
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { txId: req.body.txId });
res.status(500).json({ error: 'Failed to submit multi-sig transaction' });
}
});

Expand Down Expand Up @@ -280,7 +299,8 @@ router.post('/transaction/verify', rules.verifyMultiSigTx, validate, async (req,
const result = MultiSigService.verifySignatures(txXdr, expectedSigners);
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error);
res.status(500).json({ error: 'Failed to verify signatures' });
}
});

Expand All @@ -307,7 +327,8 @@ router.get('/transaction/pending/:publicKey', rules.publicKeyParam, validate, as
const transactions = MultiSigService.getPendingTransactions(req.params.publicKey);
res.json({ transactions });
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { publicKey: req.params.publicKey });
res.status(500).json({ error: 'Failed to retrieve pending transactions' });
}
});

Expand Down Expand Up @@ -337,7 +358,8 @@ router.get('/transaction/:txId', async (req, res) => {
if (!tx) return res.status(404).json({ error: 'Transaction not found' });
res.json(tx);
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { txId: req.params.txId });
res.status(500).json({ error: 'Failed to retrieve transaction' });
}
});

Expand Down
58 changes: 43 additions & 15 deletions backend/src/routes/stellar.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,23 @@ import { cacheMiddleware } from '../middleware/cache.js';
import { keys as cacheKeys, TTL, invalidateBalance } from '../cache/appCache.js';
import prisma from '../db/client.js';
import { getSubscriptionByPublicKey, sendWebPush } from '../notifications/webPush.js';
import logger from '../config/logger.js';
import { createRateLimiter } from '../middleware/rateLimiter.js';

const router = express.Router();

function logError(req, error, context = {}) {
logger.error('route.error', {
requestId: req.id,
correlationId: req.correlationId,
method: req.method,
path: req.path,
...context,
error: error.message,
stack: error.stack,
});
}

/**
* @swagger
* /api/stellar/account/create:
Expand All @@ -42,7 +55,8 @@ router.post('/account/create', async (req, res) => {
const account = await StellarService.createAccount();
res.json(account);
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error);
res.status(500).json({ error: 'Failed to create account' });
}
});

Expand All @@ -52,7 +66,8 @@ router.post('/account/fund', rules.publicKeyBody, validate, async (req, res) =>
const result = await StellarService.fundAccount(req.body.publicKey);
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { publicKey: req.body.publicKey });
res.status(500).json({ error: 'Failed to fund account' });
}
});

Expand Down Expand Up @@ -105,7 +120,8 @@ router.get('/account/:publicKey', rules.publicKeyParam, validate,
const balance = await StellarService.getBalance(req.params.publicKey);
res.json(balance);
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { publicKey: req.params.publicKey });
res.status(500).json({ error: 'Failed to retrieve balance' });
}
}
);
Expand Down Expand Up @@ -176,7 +192,8 @@ router.post('/payment/send', paymentRateLimiter, rules.sendPayment, validate, as

res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { destination: req.body.destination, amount: req.body.amount, assetCode: req.body.assetCode });
res.status(500).json({ error: 'Failed to send payment' });
}
});

Expand Down Expand Up @@ -230,15 +247,17 @@ router.get('/account/:publicKey/transactions', rules.publicKeyParam, validate, a
}
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { publicKey: req.params.publicKey });
res.status(500).json({ error: 'Failed to retrieve transactions' });
}
});

router.get('/fee-stats', cacheMiddleware(TTL.FEE_STATS, () => cacheKeys.feeStats()), async (req, res) => {
try {
res.json(await StellarService.getFeeStats());
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error);
res.status(500).json({ error: 'Failed to retrieve fee stats' });
}
});

Expand All @@ -253,7 +272,8 @@ router.get('/exchange-rate/:from/:to', rules.assetCodeParams, validate,
}
res.json({ from, to, rate });
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { from: req.params.from, to: req.params.to });
res.status(500).json({ error: 'Failed to retrieve exchange rate' });
}
}
);
Expand All @@ -264,7 +284,8 @@ router.get('/rates', async (req, res) => {
const rates = await getAllRates();
res.json({ rates });
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error);
res.status(500).json({ error: 'Failed to retrieve rates' });
}
});

Expand All @@ -276,7 +297,8 @@ router.get('/convert/:from/:to/:amount', rules.assetCodeParams, validate, async
const result = await convert(amount, req.params.from, req.params.to);
res.json({ from: req.params.from, to: req.params.to, amount, converted: result });
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { from: req.params.from, to: req.params.to, amount: req.params.amount });
res.status(500).json({ error: 'Failed to convert amount' });
}
});

Expand All @@ -285,7 +307,8 @@ router.get('/network/status', async (req, res) => {
const status = await StellarService.getNetworkStatus();
res.json(status);
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error);
res.status(500).json({ error: 'Failed to retrieve network status' });
}
});

Expand Down Expand Up @@ -375,7 +398,8 @@ router.post('/trustline', rules.createTrustline, validate, async (req, res) => {
const result = await StellarService.createTrustline(sourceSecret, assetCode);
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { assetCode: req.body.assetCode });
res.status(500).json({ error: 'Failed to create trustline' });
}
});

Expand All @@ -387,7 +411,8 @@ router.get('/account/:publicKey/label', rules.publicKeyParam, validate, async (r
});
res.json({ accountLabel: user?.settings?.accountLabel ?? null });
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { publicKey: req.params.publicKey });
res.status(500).json({ error: 'Failed to retrieve account label' });
}
});

Expand All @@ -409,7 +434,8 @@ router.put('/account/:publicKey/label', rules.publicKeyParam, validate,
});
res.json({ accountLabel: accountLabel || null });
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { publicKey: req.params.publicKey });
res.status(500).json({ error: 'Failed to update account label' });
}
}
);
Expand All @@ -429,7 +455,8 @@ router.get('/account/:publicKey/settings', rules.publicKeyParam, validate, async
kycSubmittedAt: user?.kycRecord?.submittedAt ?? null,
});
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { publicKey: req.params.publicKey });
res.status(500).json({ error: 'Failed to retrieve account settings' });
}
});

Expand Down Expand Up @@ -457,7 +484,8 @@ router.put('/account/:publicKey/settings',
});
res.json({ defaultAsset: settings.defaultAsset, notificationsOn: settings.notificationsOn });
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { publicKey: req.params.publicKey });
res.status(500).json({ error: 'Failed to update account settings' });
}
}
);
Expand Down
33 changes: 26 additions & 7 deletions backend/src/routes/transactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,22 @@ import express from 'express';
import { transactionService } from '../services/transactions.js';
import { validate, rules } from '../middleware/validate.js';
import { broadcastToAccount } from '../services/websocket.js';
import logger from '../config/logger.js';

const router = express.Router();

function logError(req, error, context = {}) {
logger.error('route.error', {
requestId: req.id,
correlationId: req.correlationId,
method: req.method,
path: req.path,
...context,
error: error.message,
stack: error.stack,
});
}

/**
* @swagger
* /api/transactions/{accountId}:
Expand Down Expand Up @@ -89,7 +102,8 @@ router.get('/:accountId', rules.accountIdParam, validate, async (req, res) => {
const transactions = await transactionService.getTransactions(accountId, options);
res.json(transactions);
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { accountId: req.params.accountId });
res.status(500).json({ error: 'Failed to retrieve transactions' });
}
});

Expand Down Expand Up @@ -146,7 +160,8 @@ router.get('/:accountId/search', rules.accountIdParam, validate, async (req, res
const results = await transactionService.searchTransactions(accountId, searchTerm, { limit });
res.json(results);
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { accountId: req.params.accountId });
res.status(500).json({ error: 'Failed to search transactions' });
}
});

Expand Down Expand Up @@ -188,7 +203,8 @@ router.get('/:accountId/analytics', rules.accountIdParam, validate, async (req,
const analytics = await transactionService.getTransactionAnalytics(accountId, timeframe);
res.json(analytics);
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { accountId: req.params.accountId });
res.status(500).json({ error: 'Failed to retrieve transaction analytics' });
}
});

Expand Down Expand Up @@ -229,7 +245,8 @@ router.get('/:accountId/latest', rules.accountIdParam, validate, async (req, res

res.json(transaction);
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { accountId: req.params.accountId });
res.status(500).json({ error: 'Failed to retrieve latest transaction' });
}
});

Expand Down Expand Up @@ -259,7 +276,8 @@ router.post('/:accountId/monitor', rules.accountIdParam, validate, async (req, r
transactionService.startMonitoring(accountId);
res.json({ message: 'Transaction monitoring started' });
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { accountId: req.params.accountId });
res.status(500).json({ error: 'Failed to start transaction monitoring' });
}
});

Expand Down Expand Up @@ -289,8 +307,9 @@ router.delete('/:accountId/monitor', rules.accountIdParam, validate, async (req,
transactionService.stopMonitoring(req.params.accountId);
res.json({ message: 'Transaction monitoring stopped' });
} catch (error) {
res.status(500).json({ error: error.message });
logError(req, error, { accountId: req.params.accountId });
res.status(500).json({ error: 'Failed to stop transaction monitoring' });
}
});

export default router;
export default router;
Loading