Skip to content

Commit ca3ec3f

Browse files
committed
feat: add Railway cron worker service for periodic jobs
- scripts/cron-worker.ts — standalone worker that calls API cron endpoints on intervals - monitor-payments runs every 60s via internal fetch - Deploy as separate Railway service: npx tsx scripts/cron-worker.ts - v0.5.3
1 parent 2d543a5 commit ca3ec3f

File tree

3 files changed

+88
-2
lines changed

3 files changed

+88
-2
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "coinpayportal",
3-
"version": "0.5.2",
3+
"version": "0.5.3",
44
"private": true,
55
"type": "module",
66
"bin": {

packages/sdk/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@profullstack/coinpay",
3-
"version": "0.5.2",
3+
"version": "0.5.3",
44
"description": "CoinPay SDK & CLI — Accept cryptocurrency payments (BTC, ETH, SOL, POL, BCH, USDC) with wallet and swap support",
55
"type": "module",
66
"main": "./src/index.js",

scripts/cron-worker.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#!/usr/bin/env tsx
2+
/**
3+
* Cron Worker Service for Railway
4+
*
5+
* Runs periodic jobs internally without external triggers.
6+
* Deploy as a separate Railway service with: npx tsx scripts/cron-worker.ts
7+
*/
8+
9+
const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || process.env.RAILWAY_PUBLIC_DOMAIN
10+
? `https://${process.env.RAILWAY_PUBLIC_DOMAIN}`
11+
: 'http://localhost:3000';
12+
13+
const CRON_SECRET = process.env.CRON_SECRET || '';
14+
15+
interface CronJob {
16+
name: string;
17+
path: string;
18+
intervalMs: number;
19+
method?: 'GET' | 'POST';
20+
}
21+
22+
const jobs: CronJob[] = [
23+
{
24+
name: 'monitor-payments',
25+
path: '/api/cron/monitor-payments',
26+
intervalMs: 60 * 1000, // every 1 minute
27+
method: 'GET',
28+
},
29+
];
30+
31+
async function runJob(job: CronJob): Promise<void> {
32+
const url = `${BASE_URL}${job.path}`;
33+
const method = job.method || 'GET';
34+
35+
try {
36+
const headers: Record<string, string> = {
37+
'Content-Type': 'application/json',
38+
};
39+
40+
// Try Vercel-style cron auth first, then Bearer
41+
if (CRON_SECRET) {
42+
headers['authorization'] = `Bearer ${CRON_SECRET}`;
43+
}
44+
45+
const res = await fetch(url, { method, headers, signal: AbortSignal.timeout(30_000) });
46+
const body = await res.text();
47+
48+
const timestamp = new Date().toISOString();
49+
if (res.ok) {
50+
console.log(`[${timestamp}] ✓ ${job.name} (${res.status})`);
51+
} else {
52+
console.error(`[${timestamp}] ✗ ${job.name} (${res.status}): ${body.slice(0, 200)}`);
53+
}
54+
} catch (err) {
55+
console.error(`[${new Date().toISOString()}] ✗ ${job.name}: ${(err as Error).message}`);
56+
}
57+
}
58+
59+
function startJob(job: CronJob): void {
60+
console.log(`[cron-worker] Scheduling "${job.name}" every ${job.intervalMs / 1000}s → ${BASE_URL}${job.path}`);
61+
62+
// Run immediately on startup
63+
runJob(job);
64+
65+
// Then on interval
66+
setInterval(() => runJob(job), job.intervalMs);
67+
}
68+
69+
// --- Main ---
70+
console.log(`[cron-worker] Starting with BASE_URL=${BASE_URL}`);
71+
console.log(`[cron-worker] ${jobs.length} job(s) configured`);
72+
73+
for (const job of jobs) {
74+
startJob(job);
75+
}
76+
77+
// Keep alive
78+
process.on('SIGTERM', () => {
79+
console.log('[cron-worker] SIGTERM received, shutting down');
80+
process.exit(0);
81+
});
82+
83+
process.on('SIGINT', () => {
84+
console.log('[cron-worker] SIGINT received, shutting down');
85+
process.exit(0);
86+
});

0 commit comments

Comments
 (0)