Skip to content

Commit 2d74067

Browse files
authored
feat: implement cron job oberservation routes (#48)
* refractor: replace `cron` with `croner` * refractor: rename cron job file * feat: add cron job observation routes * feat: implement cron observation routes * refractor: optimize json property names
1 parent 367bf77 commit 2d74067

File tree

7 files changed

+140
-63
lines changed

7 files changed

+140
-63
lines changed

bun.lockb

-699 Bytes
Binary file not shown.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
"@sentry/bun": "^7.110.1",
5151
"@sentry/cli": "^2.31.0",
5252
"chalk": "^5.3.0",
53-
"cron": "^3.1.6",
53+
"croner": "^8.0.2",
5454
"hono": "^4.1.0",
5555
"node-cache": "^5.1.2",
5656
"qs": "^6.12.0"

src/controllers/crons.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import type { Cron } from "croner";
2+
import type { Context } from "hono";
3+
4+
import * as crons from "jobs";
5+
6+
function cronJSON(defaultName: string, cron: Cron) {
7+
const previous = cron.currentRun()?.getTime() ?? null;
8+
const current = cron.previousRun()?.getTime() ?? null;
9+
const next = cron.msToNext();
10+
return {
11+
name: cron.name ?? defaultName,
12+
pattern: cron.getPattern(),
13+
status: cron.isRunning() ? "ok" : "stopped",
14+
busy: cron.isBusy(),
15+
runs: {
16+
current,
17+
previous,
18+
next: next ? Date.now() + next : null,
19+
},
20+
};
21+
}
22+
23+
export const getAllCrons = async (ctx: Context) => {
24+
try {
25+
const jobs = [];
26+
27+
for (const key in crons) {
28+
const job = cronJSON(key, crons[key as keyof typeof crons] as Cron);
29+
jobs.push(job);
30+
}
31+
32+
return ctx.json({
33+
data: jobs,
34+
error: null,
35+
pagination: {
36+
page: 1,
37+
pageSize: jobs.length,
38+
pageCount: 1,
39+
total: jobs.length,
40+
},
41+
});
42+
} catch (error: any) {
43+
ctx.get("sentry")?.captureException?.(error);
44+
ctx.status(500);
45+
return ctx.json({
46+
data: null,
47+
error: { details: [error.message] },
48+
});
49+
}
50+
};
51+
52+
export const getCronByName = async (ctx: Context) => {
53+
try {
54+
const name = ctx.req.param("name");
55+
56+
for (const key in crons) {
57+
if (key !== name) continue;
58+
const job = cronJSON(name, crons[key as keyof typeof crons] as Cron);
59+
ctx.status(200);
60+
return ctx.json({ data: job, error: null });
61+
}
62+
63+
ctx.status(404);
64+
return ctx.json({
65+
data: null,
66+
error: { details: [`Cron job with name (${name}) not found`] },
67+
});
68+
} catch (error: any) {
69+
ctx.get("sentry")?.captureException?.(error);
70+
ctx.status(500);
71+
return ctx.json({
72+
data: null,
73+
error: { details: [error.message] },
74+
});
75+
}
76+
};

src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import "jobs/refresh";
1+
import "jobs";
22
import "polyfills/BigInt";
33

44
import { Hono } from "hono";
@@ -13,6 +13,7 @@ import rateLimit from "middleware/rate-limit";
1313
import { initSentry, sentryOptions } from "utils/sentry";
1414

1515
import wars from "routes/war";
16+
import crons from "routes/crons";
1617
import biomes from "routes/biomes";
1718
import orders from "routes/orders";
1819
import events from "routes/events";
@@ -45,6 +46,7 @@ app.use(cache);
4546
// routes for the api
4647
const routes = [
4748
wars,
49+
crons,
4850
biomes,
4951
events,
5052
orders,

src/jobs/index.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import chalk from "chalk";
2+
import { Cron } from "croner";
3+
import * as Sentry from "@sentry/bun";
4+
5+
import RequestCache from "classes/request-cache";
6+
import { refreshAndStoreSourceData } from "utils/refresh";
7+
8+
export const refresh_from_source = Cron("0 */1 * * * *", async () => {
9+
const startDate = Date.now();
10+
11+
// start a check-in with sentry
12+
let checkInId = (() => {
13+
if (process.env.NODE_ENV !== "production") return;
14+
if (!process.env.SENTRY_DSN) return;
15+
return Sentry.captureCheckIn({
16+
monitorSlug: "refresh-from-source-data",
17+
status: "in_progress",
18+
});
19+
})();
20+
21+
try {
22+
await refreshAndStoreSourceData();
23+
RequestCache.flushAll();
24+
25+
console.log(
26+
`${chalk.bold(chalk.magenta("CRON"))} Refreshed source data ${`(${Date.now() - startDate}ms)`}`,
27+
);
28+
29+
// send the cron check-in result to sentry
30+
if (!checkInId) return;
31+
Sentry.captureCheckIn({
32+
checkInId,
33+
status: "ok",
34+
monitorSlug: "refresh-from-source-data",
35+
});
36+
} catch (err: any) {
37+
Sentry.captureException(err);
38+
39+
console.error(
40+
`${chalk.bold(chalk.red("ERROR"))} Error refreshing source data ${`(${Date.now() - startDate}ms)`}`,
41+
`\n${err}`,
42+
);
43+
44+
// send the cron check-in result to sentry
45+
if (!checkInId) return;
46+
Sentry.captureCheckIn({
47+
checkInId,
48+
status: "error",
49+
monitorSlug: "refresh-from-source-data",
50+
});
51+
}
52+
});

src/jobs/refresh.ts

Lines changed: 0 additions & 61 deletions
This file was deleted.

src/routes/crons.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import type { Hono } from "hono";
2+
3+
import * as Cron from "controllers/crons";
4+
5+
export default async function crons(app: Hono) {
6+
app.get("/crons", Cron.getAllCrons);
7+
app.get("/crons/:name", Cron.getCronByName);
8+
}

0 commit comments

Comments
 (0)