Add health check endpoint and missing REST API endpoints.

This commit is contained in:
jwinterm
2025-12-20 10:34:05 -05:00
parent deca4a4685
commit be2ba473ff
3 changed files with 162 additions and 12 deletions
+37 -8
View File
@@ -44,15 +44,44 @@ If the wallet RPC service or configuration is unavailable the backend automatica
## API Surface
- `POST /api/payment/create` – Request a payment for the active mode.
- `GET /api/payment/status/:paymentId` – Poll payment status.
- `POST /api/payment/callback` – Wallet RPC webhook to confirm payments.
- `GET /api/game/modes` – Current game modes and pricing.
- `GET /api/user/credits/:userId` – Remaining credit balance.
- `GET /api/user/mode/:userId` – User game mode.
- `POST /api/user/address/:userId` – Store payout address after confirmation.
### Health & Status
Only the payment routes require wallet RPC access; all other gameplay functions work without it.
- `GET /health` – Server health check with uptime, memory usage, game counts, and wallet status.
- `GET /api/game-modes` – Current game modes and pricing configuration.
### User Endpoints
- `GET /api/user/:socketId/credits` – Remaining credit balance for a user.
- `GET /api/user/:socketId/mode` – User payment mode preferences and enabled features.
- `POST /api/user/:socketId/address` – Store payout address (body: `{ "address": "<XMR/WOW address>" }`).
### Payment Endpoints (Socket.IO Preferred)
The following REST endpoints exist but return `501 Not Implemented`. Payment flows are handled via Socket.IO events for real-time feedback:
- `POST /api/payment/create` – Use socket event `request_payment` instead.
- `GET /api/payment/status/:paymentId` – Use socket event `payment_status` instead.
- `POST /api/payment/callback` – Reserved for future webhook integration.
### Socket.IO Events (Primary Payment Flow)
| Client → Server | Description |
|-----------------|-------------|
| `request_payment` | Request a payment address for game entry |
| `confirm_address` | Confirm a detected payout address |
| `cancel_address` | Cancel pending address confirmation |
| `join_queue` | Join the game queue after payment |
| Server → Client | Description |
|-----------------|-------------|
| `payment_created` | Payment address and QR code ready |
| `payment_confirmed` | Payment received and confirmed |
| `address_detected` | Payout address detected in chat |
| `address_confirmed` | Payout address saved |
| `game_start` | Game session beginning |
| `credits_update` | Credit balance changed |
All gameplay functions work without wallet RPC access; only paid modes require it.
## Installation
+12 -2
View File
@@ -1,7 +1,7 @@
# Wownerogue Development Todo
## High Priority
- [ ] **Verify Server Startup**: Ensure `npm run dev` starts cleanly without errors after recent refactors.
- [x] **Verify Server Startup**: Server starts cleanly with `npm run dev` ✅
- [ ] **Test Payment Flow**: Verify the full payment lifecycle (Request -> QR -> Payment -> Confirmation -> Game Start).
- [ ] **Test Payouts**: Confirm payouts are triggered correctly upon winning in paid modes.
- [ ] **Address Persistence**: Verify that payout addresses persist across server restarts via session tokens.
@@ -13,5 +13,15 @@
- [ ] **Frontend Cleanup**: Remove any remaining dead code or unused assets from the `html/` directory.
## Documentation
- [ ] **API Documentation**: Expand API documentation in `README.md` with request/response examples.
- [x] **API Documentation**: README updated with accurate API surface including health check and Socket.IO events ✅
- [ ] **Deployment Guide**: Create a detailed deployment guide including reverse proxy setup (Nginx/Caddy).
## Pre-Beta Checklist
- [x] All 21 tests pass ✅
- [x] Health check endpoint available at `/health` ✅
- [x] REST endpoints implemented: `/api/user/:socketId/credits`, `/api/user/:socketId/mode`, `/api/user/:socketId/address` ✅
- [ ] **TLS/HTTPS**: Configure reverse proxy (Nginx/Caddy) with SSL certificates before exposing to real users.
- [ ] **Database Backups**: Implement automated backup strategy for PostgreSQL.
- [ ] **Manual Integration Test**: Run full payment flow with real `wownero-wallet-rpc` (not mocked).
- [ ] **Rate Limit Tuning**: Review rate limits for production traffic patterns.
- [ ] **Log Rotation**: Ensure logs don't fill disk in production.
+113 -2
View File
@@ -92,10 +92,121 @@ app.get('/api/payment/status/:paymentId', asyncHandler(async (req, res) => {
throw restNotImplemented();
}));
app.get('/api/user/:userId/credits', asyncHandler(async (req, res) => {
throw restNotImplemented();
app.get('/api/user/:socketId/credits', asyncHandler(async (req, res) => {
const { socketId } = req.params;
if (!socketId) {
throw new ValidationError('Missing socketId', {
safeMessage: 'socketId parameter is required.'
});
}
try {
const user = await gameModeManager.getOrCreateUser(socketId);
res.json({
socketId,
credits: user.credits || 0,
totalCreditsPurchased: user.total_credits_purchased || 0
});
} catch (error) {
throw new AppError('Failed to retrieve credits', {
statusCode: 500,
safeMessage: 'Unable to retrieve credit balance.',
cause: error
});
}
}));
app.get('/api/user/:socketId/mode', asyncHandler(async (req, res) => {
const { socketId } = req.params;
if (!socketId) {
throw new ValidationError('Missing socketId', {
safeMessage: 'socketId parameter is required.'
});
}
try {
const user = await gameModeManager.getOrCreateUser(socketId);
res.json({
socketId,
preferredPaymentMode: user.preferred_payment_mode || 'direct',
hasPayoutAddress: !!user.payout_address,
paymentsEnabled: gameModeManager.paymentsEnabled,
directModeEnabled: gameModeManager.directModeEnabled,
creditsModeEnabled: gameModeManager.creditsModeEnabled
});
} catch (error) {
throw new AppError('Failed to retrieve user mode', {
statusCode: 500,
safeMessage: 'Unable to retrieve user mode.',
cause: error
});
}
}));
app.post('/api/user/:socketId/address', asyncHandler(async (req, res) => {
const { socketId } = req.params;
const { address } = req.body || {};
if (!socketId) {
throw new ValidationError('Missing socketId', {
safeMessage: 'socketId parameter is required.'
});
}
if (!address || typeof address !== 'string') {
throw new ValidationError('Missing or invalid address', {
safeMessage: 'A valid payout address is required.'
});
}
// Basic address validation (XMR/WOW addresses)
const ADDRESS_REGEX = /((?:4|8)[1-9A-HJ-NP-Za-km-z]{90,110}|(?:Wo|WO|ww|WW)[0-9A-Za-z]{88,112}|W[0-9A-Za-z]{90,112})/;
if (!ADDRESS_REGEX.test(address.trim())) {
throw new ValidationError('Invalid address format', {
safeMessage: 'The provided address does not appear to be a valid XMR/WOW address.'
});
}
try {
const success = await gameModeManager.setUserPayoutAddress(socketId, address.trim());
if (!success) {
throw new AppError('Failed to save address', {
statusCode: 500,
safeMessage: 'Unable to save payout address.'
});
}
res.json({
success: true,
message: 'Payout address saved successfully.'
});
} catch (error) {
if (error instanceof AppError || error instanceof ValidationError) {
throw error;
}
throw new AppError('Failed to save address', {
statusCode: 500,
safeMessage: 'Unable to save payout address.',
cause: error
});
}
}));
// Health check endpoint
app.get('/health', (req, res) => {
const health = {
status: 'ok',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
memoryUsage: process.memoryUsage(),
gameMode: gameModeManager.gameMode,
paymentsEnabled: gameModeManager.paymentsEnabled,
walletHealthy: walletRPCService.isHealthy,
activeGames: activeGames.size,
debugMode: debugManager.getDebugStatus().debugMode
};
res.json(health);
});
app.get('/api/game-modes', (req, res) => {
const config = paymentConfigManager.getConfig();
const decimals = Number(config.currency?.decimals ?? 12);