fix error handling and more swagger info

This commit is contained in:
Fran Jurmanović
2023-10-05 19:06:01 +02:00
parent 71c9d1635b
commit d3dd15350c
7 changed files with 139 additions and 65 deletions

BIN
bun.lockb

Binary file not shown.

View File

@@ -1,6 +1,6 @@
{
"name": "legica-dana",
"version": "2.0.0",
"version": "2.0.1",
"main": "src/app.ts",
"scripts": {
"start": "bun src/app.ts"
@@ -8,12 +8,12 @@
"author": "Fran Jurmanović <fjurma12@outlook.com>",
"license": "MIT",
"dependencies": {
"@elysiajs/cron": "^0.7.0",
"@elysiajs/static": "^0.7.1",
"@elysiajs/swagger": "^0.7.3",
"axios": "^0.26.0",
"body-parser": "^1.20.2",
"cheerio": "^1.0.0-rc.10",
"cron": "^3.0.0",
"discord.js": "^12.5.1",
"dotenv": "^8.2.0",
"elysia": "^0.7.15",

1
process-env.d.ts vendored
View File

@@ -5,6 +5,7 @@ declare global {
PORT: string;
CRON_LEGICA: string;
PASSWORD: string;
TIMEZONE: string;
}
}
}

View File

@@ -1,12 +1,12 @@
import { Client } from "discord.js";
import { config } from "@constants";
import { CronJob } from "cron";
import { sendDiscordMessage, sendNextMessage } from "@common";
import { Elysia, t } from "elysia";
import { swagger } from "@elysiajs/swagger";
import { basicAuth } from "@core";
import { basicAuth, BasicAuthError } from "@core";
import pino from "pino";
import staticPlugin from "@elysiajs/static";
import cron from "@elysiajs/cron";
const client: Client = new Client();
@@ -23,27 +23,39 @@ const logger = pino(
);
const taskPlugin = new Elysia({ prefix: "/job" })
.state("job", null as CronJob | null)
.onStart(({ store }) => {
client.on("ready", (): void => {
if (store.job) {
store.job.stop();
}
store.job = new CronJob(
config.CRON_LEGICA,
() => sendNextMessage(client),
null,
true,
"utc"
);
});
})
.onBeforeHandle(({ store: { job }, set }) => {
if (!job) {
set.status = 400;
return "Job is not running.";
.use(
cron({
name: "job",
pattern: config.CRON_LEGICA,
run: () => sendNextMessage(client),
paused: true,
timezone: config.TIMEZONE,
})
)
.onStart(
({
store: {
cron: { job },
},
}) => {
client.on("ready", (): void => {
job.resume();
});
}
})
)
.onBeforeHandle(
({
store: {
cron: { job },
},
set,
}) => {
if (job.isStopped()) {
set.status = 400;
return "Job is not running.";
}
}
)
.use(
basicAuth({
users: [
@@ -55,26 +67,65 @@ const taskPlugin = new Elysia({ prefix: "/job" })
errorMessage: "Unauthorized",
})
)
.get("/", ({ store: { job } }) => ({
running: job?.running ?? false,
next: job?.nextDate().toISO(),
}))
.post("/", ({ store: { job }, set }) => {
if (job?.running) {
set.status = 400;
return "Task already running";
.get(
"/",
({
store: {
cron: { job },
},
}) => ({
running: job.isRunning() ?? false,
stopped: job.isStopped() ?? false,
next: job.nextRun()?.toISOString(),
}),
{
detail: {
summary: "Get CRON job status",
},
}
job?.start();
return "Task started";
})
.delete("/", ({ store: { job }, set }) => {
if (!job?.running) {
set.status = 400;
return "Task already stopped";
)
.post(
"/",
({
store: {
cron: { job },
},
set,
}) => {
if (job.isRunning()) {
set.status = 400;
return "Job already running";
}
job.resume();
return "Job started";
},
{
detail: {
summary: "Start CRON job if it is not running",
},
}
job?.stop();
return "Task stopped";
})
)
.delete(
"/",
({
store: {
cron: { job },
},
set,
}) => {
if (!job.isRunning()) {
set.status = 400;
return "Job already paused";
}
job.pause();
return "Job paused";
},
{
detail: {
summary: "Pause CRON job if it is not paused",
},
}
)
.post(
"/send",
async ({ set, body }) => {
@@ -95,18 +146,42 @@ const taskPlugin = new Elysia({ prefix: "/job" })
body: t.Object({
url: t.String(),
}),
detail: {
summary: "Send legica-dana post to discord channels",
},
}
)
.get("/log", () => Bun.file("app.log"));
client.login(config.TOKEN);
.get("/log", () => Bun.file("app.log"), {
detail: {
summary: "Get the error log",
},
});
const app = new Elysia()
.onError(({ error }) => {
logger.error(error);
return new Response(error.toString());
.error({ BASIC_AUTH_ERROR: BasicAuthError })
.onError(({ error, code }) => {
switch (code) {
case "BASIC_AUTH_ERROR":
return new Response(error.message, {
status: 401,
headers: {
"WWW-Authenticate": `Basic${
config.realm ? ` realm="${config.realm}"` : ""
}`,
},
});
case "NOT_FOUND":
return new Response(error.message, {
status: 404,
});
default:
logger.error(error);
}
})
.get("/", () => config.APP_VERSION, {
detail: {
summary: "Get current API version",
},
})
.get("/", () => config.APP_VERSION)
.use(
swagger({
documentation: {
@@ -114,6 +189,14 @@ const app = new Elysia()
title: "Legica Bot",
version: config.APP_VERSION,
},
security: [
{
type: ["basic"],
},
],
},
swaggerOptions: {
withCredentials: true,
},
})
)
@@ -121,6 +204,7 @@ const app = new Elysia()
.use(taskPlugin)
.listen(config.PORT);
client.login(config.TOKEN);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
`🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}`
);

View File

@@ -16,6 +16,7 @@ const config: ProjectConfig = {
CRON_LEGICA: process.env.CRON_LEGICA || "0 9 * * *",
APP_VERSION: version,
LEGICA_URL: "https://sib.net.hr/legica-dana",
TIMEZONE: process.env.TIMEZONE || "utc",
};
export { config };

View File

@@ -22,7 +22,6 @@ export interface BasicAuthConfig {
export const basicAuth = (config: BasicAuthConfig) =>
new Elysia({ name: "basic-auth", seed: config })
.error({ BASIC_AUTH_ERROR: BasicAuthError })
.derive((ctx) => {
const authorization = ctx.headers?.authorization;
if (!authorization) return { basicAuth: { isAuthed: false, username: "" } };
@@ -40,19 +39,8 @@ export const basicAuth = (config: BasicAuthConfig) =>
!isPathExcluded(ctx.path, config.exclude) &&
ctx.request &&
ctx.request.method !== "OPTIONS"
)
) {
throw new BasicAuthError(config.errorMessage ?? "Unauthorized");
})
.onError((ctx) => {
if (ctx.code === "BASIC_AUTH_ERROR") {
return new Response(ctx.error.message, {
status: 401,
headers: {
"WWW-Authenticate": `Basic${
config.realm ? ` realm="${config.realm}"` : ""
}`,
},
});
}
});

View File

@@ -1 +1 @@
export { basicAuth } from "./basicAuth";
export { basicAuth, BasicAuthError } from "./basicAuth";