use elysia instead of express
This commit is contained in:
17
package.json
17
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "legica-dana",
|
||||
"version": "0.8.0",
|
||||
"version": "2.0.0",
|
||||
"main": "src/app.ts",
|
||||
"scripts": {
|
||||
"start": "bun src/app.ts"
|
||||
@@ -8,25 +8,24 @@
|
||||
"author": "Fran Jurmanović <fjurma12@outlook.com>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "^14.14.31",
|
||||
"@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",
|
||||
"express": "^4.18.2",
|
||||
"express-basic-auth": "^1.2.1",
|
||||
"redoc-express": "^2.1.0",
|
||||
"elysia": "^0.7.15",
|
||||
"minimatch": "^9.0.3",
|
||||
"pino": "^8.15.4",
|
||||
"typescript": "^4.1.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.18",
|
||||
"@types/node-cron": "^3.0.1",
|
||||
"@types/pg": "^7.14.10",
|
||||
"@types/ws": "^7.4.0",
|
||||
"@types/node": "^20.8.2",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
||||
"@typescript-eslint/parser": "^6.7.4",
|
||||
"bun-types": "^1.0.4-canary.20231004T140131",
|
||||
"eslint": "^8.50.0",
|
||||
"prettier": "^2.2.1"
|
||||
}
|
||||
|
||||
160
src/app.ts
160
src/app.ts
@@ -1,60 +1,126 @@
|
||||
import { Client } from "discord.js";
|
||||
import { Chat } from "@common";
|
||||
import { Controller } from "@core";
|
||||
import { ClientController } from "@controllers";
|
||||
import express from "express";
|
||||
import { APP_VERSION, config } from "@constants";
|
||||
import bodyParser from "body-parser";
|
||||
import redoc from "redoc-express";
|
||||
import path from "path";
|
||||
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 pino from "pino";
|
||||
import staticPlugin from "@elysiajs/static";
|
||||
|
||||
const client: Client = new Client();
|
||||
const chat: Chat = new Chat(client);
|
||||
const app = express();
|
||||
|
||||
app.use(bodyParser.json());
|
||||
const fileTransport = pino.transport({
|
||||
target: "pino/file",
|
||||
|
||||
app.get("/docs/swagger.json", (req, res) => {
|
||||
res.sendFile("swagger.json", { root: path.join(__dirname, "..") });
|
||||
options: { destination: `app.log` },
|
||||
});
|
||||
app.get(
|
||||
"/docs",
|
||||
redoc({
|
||||
title: "API Docs",
|
||||
specUrl: "/docs/swagger.json",
|
||||
nonce: "",
|
||||
redocOptions: {
|
||||
theme: {
|
||||
colors: {
|
||||
primary: {
|
||||
main: "#6EC5AB",
|
||||
const logger = pino(
|
||||
{
|
||||
level: "error",
|
||||
},
|
||||
fileTransport
|
||||
);
|
||||
|
||||
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(
|
||||
basicAuth({
|
||||
users: [
|
||||
{
|
||||
username: "admin",
|
||||
password: config.PASSWORD,
|
||||
},
|
||||
typography: {
|
||||
fontFamily: `"museo-sans", 'Helvetica Neue', Helvetica, Arial, sans-serif`,
|
||||
fontSize: "15px",
|
||||
lineHeight: "1.5",
|
||||
code: {
|
||||
code: "#87E8C7",
|
||||
backgroundColor: "#4D4D4E",
|
||||
},
|
||||
},
|
||||
menu: {
|
||||
backgroundColor: "#ffffff",
|
||||
],
|
||||
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";
|
||||
}
|
||||
job?.start();
|
||||
return "Task started";
|
||||
})
|
||||
.delete("/", ({ store: { job }, set }) => {
|
||||
if (!job?.running) {
|
||||
set.status = 400;
|
||||
return "Task already stopped";
|
||||
}
|
||||
job?.stop();
|
||||
return "Task stopped";
|
||||
})
|
||||
.post(
|
||||
"/send",
|
||||
async ({ set, body }) => {
|
||||
try {
|
||||
const url = body.url;
|
||||
if (url) {
|
||||
await sendDiscordMessage(client, url);
|
||||
} else {
|
||||
await sendNextMessage(client);
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
set.status = 400;
|
||||
return err;
|
||||
}
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
url: t.String(),
|
||||
}),
|
||||
}
|
||||
)
|
||||
.get("/log", () => Bun.file("app.log"));
|
||||
|
||||
client.login(config.TOKEN);
|
||||
|
||||
const app = new Elysia()
|
||||
.onError(({ error }) => {
|
||||
logger.error(error);
|
||||
return new Response(error.toString());
|
||||
})
|
||||
.get("/", () => config.APP_VERSION)
|
||||
.use(
|
||||
swagger({
|
||||
documentation: {
|
||||
info: {
|
||||
title: "Legica Bot",
|
||||
version: config.APP_VERSION,
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
app.get("/version", (_, res) => {
|
||||
res.send(APP_VERSION);
|
||||
});
|
||||
|
||||
const controllers = new Controller(app, [new ClientController(client)]);
|
||||
|
||||
controllers.register();
|
||||
chat.register(config.TOKEN || "");
|
||||
app.listen(config.PORT, () =>
|
||||
console.log(`Legica bot API listening on port ${config.PORT}!`)
|
||||
)
|
||||
.use(staticPlugin())
|
||||
.use(taskPlugin)
|
||||
.listen(config.PORT);
|
||||
|
||||
console.log(
|
||||
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
|
||||
);
|
||||
|
||||
@@ -1,48 +0,0 @@
|
||||
import { CommandFunction, ICommand } from "@models";
|
||||
import type { Client, Message } from "discord.js";
|
||||
|
||||
export default class Chat {
|
||||
private prefix: string = "!";
|
||||
constructor(private client: Client, private commands: ICommand[] = []) {}
|
||||
|
||||
public registerPrefix = (prefix: string): void => {
|
||||
this.prefix = prefix;
|
||||
};
|
||||
|
||||
public register = (token: string): void => {
|
||||
if (!this.commands) return;
|
||||
this.client.on("message", (message: Message): void => {
|
||||
this.commands.forEach((command) => {
|
||||
if (message?.content === `${this.prefix}${command?.name}`) {
|
||||
command?.callback?.(message);
|
||||
} else if (
|
||||
message?.content?.split?.(/\s/g)?.[0] == `${this.prefix}${command?.name}`
|
||||
) {
|
||||
const args = message?.content
|
||||
?.replace?.(`${this.prefix}${command?.name}`, "")
|
||||
.trim?.()
|
||||
?.split?.(/\s(?=(?:[^'"`]*(['"`])[^'"`]*\1)*[^'"`]*$)/g)
|
||||
.map((d) => {
|
||||
if (d?.[0] == '"' && d?.[d?.length - 1] == '"') {
|
||||
return d?.substr?.(1)?.slice?.(0, -1);
|
||||
}
|
||||
return d;
|
||||
})
|
||||
.filter((d) => d);
|
||||
command?.callback?.(message, args);
|
||||
}
|
||||
});
|
||||
});
|
||||
this.client.login(token);
|
||||
};
|
||||
|
||||
public command = (name: string, callback: CommandFunction): void => {
|
||||
this.commands = [
|
||||
...this.commands,
|
||||
{
|
||||
name,
|
||||
callback,
|
||||
},
|
||||
];
|
||||
};
|
||||
}
|
||||
10
src/common/getFirstHtml.ts
Normal file
10
src/common/getFirstHtml.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import axios from "axios";
|
||||
import cheerio from "cheerio";
|
||||
|
||||
export async function getFirstHtml(): Promise<string> {
|
||||
const response = await axios.get("https://sib.net.hr/legica-dana");
|
||||
const html = response.data;
|
||||
const $ = cheerio.load(html);
|
||||
const { href } = $(".News-link.c-def")?.attr() || {};
|
||||
return href;
|
||||
}
|
||||
14
src/common/getImgTitle.ts
Normal file
14
src/common/getImgTitle.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { Legica } from "@models";
|
||||
import axios from "axios";
|
||||
import cheerio from "cheerio";
|
||||
|
||||
export async function getImgTitle(href: string): Promise<Legica> {
|
||||
const response = await axios.get(href);
|
||||
const html = response.data;
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
const title = $(".Article-inner > h1").text();
|
||||
const { src: img } = $(".Article-media > img").attr() || {};
|
||||
|
||||
return { title, img };
|
||||
}
|
||||
@@ -1 +1,3 @@
|
||||
export { default as Chat } from "./chat";
|
||||
export { getFirstHtml } from "./getFirstHtml";
|
||||
export { getImgTitle } from "./getImgTitle";
|
||||
export { sendDiscordMessage, sendNextMessage } from "./sendDiscordMessage";
|
||||
|
||||
48
src/common/sendDiscordMessage.ts
Normal file
48
src/common/sendDiscordMessage.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { getFirstHtml, getImgTitle } from "@common";
|
||||
import { Client, MessageEmbed, TextChannel } from "discord.js";
|
||||
|
||||
export async function sendDiscordMessage(
|
||||
client: Client,
|
||||
url: string
|
||||
): Promise<void> {
|
||||
if (!url) return;
|
||||
const { img, title } = await getImgTitle(url);
|
||||
|
||||
client.channels.cache.forEach(async (channel) => {
|
||||
try {
|
||||
if (channel.type !== "text") return null;
|
||||
const embeddedMessage = new MessageEmbed().setTitle(title).setImage(img);
|
||||
const msg = await (channel as TextChannel).send(embeddedMessage);
|
||||
const reactions = [
|
||||
"1️⃣",
|
||||
"2️⃣",
|
||||
"3️⃣",
|
||||
"4️⃣",
|
||||
"5️⃣",
|
||||
"6️⃣",
|
||||
"7️⃣",
|
||||
"8️⃣",
|
||||
"9️⃣",
|
||||
"🔟",
|
||||
];
|
||||
for (const reaction of reactions) {
|
||||
try {
|
||||
await msg.react(reaction);
|
||||
} catch {
|
||||
console.error(`Reaction ${reaction} to channel ${channel.id} failed.`);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
console.error(`Message to channel ${channel.id} failed.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function sendNextMessage(client: Client): Promise<void> {
|
||||
try {
|
||||
const href = await getFirstHtml();
|
||||
await sendDiscordMessage(client, href);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,21 @@
|
||||
import { config as dotenv } from "dotenv";
|
||||
import { version } from "../../package.json";
|
||||
dotenv();
|
||||
|
||||
const config: NodeJS.ProcessEnv = {
|
||||
type Config = {
|
||||
APP_VERSION: string;
|
||||
LEGICA_URL: string;
|
||||
};
|
||||
|
||||
export type ProjectConfig = Config & NodeJS.ProcessEnv;
|
||||
|
||||
const config: ProjectConfig = {
|
||||
TOKEN: process.env.TOKEN,
|
||||
PASSWORD: process.env.PASSWORD,
|
||||
PORT: process.env.PORT || "3000",
|
||||
CRON_LEGICA: process.env.CRON_LEGICA || "0 9 * * *",
|
||||
APP_VERSION: version,
|
||||
LEGICA_URL: "https://sib.net.hr/legica-dana",
|
||||
};
|
||||
|
||||
export { config };
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export * from "./version";
|
||||
export * from "./config";
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
import { version } from "../../package.json";
|
||||
|
||||
export const APP_VERSION = version;
|
||||
@@ -1,151 +0,0 @@
|
||||
import { Client, MessageEmbed, TextChannel } from "discord.js";
|
||||
import * as cron from "cron";
|
||||
import axios from "axios";
|
||||
import cheerio from "cheerio";
|
||||
import { Router } from "express";
|
||||
import { IController, Legica } from "@models";
|
||||
import { config } from "@constants";
|
||||
import basicAuth from "express-basic-auth";
|
||||
|
||||
class ClientController implements IController {
|
||||
private legicaTask: cron.CronJob | null = null;
|
||||
public path: string = "/task";
|
||||
constructor(private client: Client) {}
|
||||
|
||||
public register = (): void => {
|
||||
this.client.on("ready", (): void => {
|
||||
this.legicaTask = new cron.CronJob(
|
||||
config.CRON_LEGICA,
|
||||
this.sendNextMessage,
|
||||
null,
|
||||
true,
|
||||
"utc"
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
public registerRouter = (): Router => {
|
||||
const router = Router();
|
||||
|
||||
router.use(
|
||||
basicAuth({
|
||||
users: {
|
||||
admin: config.PASSWORD,
|
||||
},
|
||||
})
|
||||
);
|
||||
router.get("/", (_, res) => {
|
||||
res.send(this.legicaTask?.running);
|
||||
});
|
||||
|
||||
router.post("/", (_, res) => {
|
||||
if (this.legicaTask?.running) {
|
||||
res.status(400).send("Task already running.");
|
||||
} else {
|
||||
this.legicaTask?.start();
|
||||
res.send("Task started.");
|
||||
}
|
||||
});
|
||||
|
||||
router.delete("/", (_, res) => {
|
||||
if (!this.legicaTask?.running) {
|
||||
res.status(400).send("Task already stopped.");
|
||||
} else {
|
||||
this.legicaTask.stop();
|
||||
res.send("Task stopped.");
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/next", (_, res) => {
|
||||
if (!this.legicaTask?.running) {
|
||||
res.status(400).send("Task is not running.");
|
||||
} else {
|
||||
res.send(this.legicaTask.nextDate().toISO());
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/send-latest", async (_, res) => {
|
||||
try {
|
||||
await this.sendNextMessage();
|
||||
res.send(true);
|
||||
} catch (err) {
|
||||
res.status(400).send(err);
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/send", async (req, res) => {
|
||||
try {
|
||||
const url = req.body.url;
|
||||
await this.sendMessage(url);
|
||||
res.send(true);
|
||||
} catch (err) {
|
||||
res.status(400).send(err);
|
||||
}
|
||||
});
|
||||
return router;
|
||||
};
|
||||
|
||||
private sendNextMessage = async (): Promise<void> => {
|
||||
try {
|
||||
const href = await getFirstHtml();
|
||||
await this.sendMessage(href);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
};
|
||||
|
||||
private sendMessage = async (url: string): Promise<void> => {
|
||||
if (!url) return;
|
||||
const { img, title } = await getImgTitle(url);
|
||||
|
||||
this.client.channels.cache.forEach(async (channel) => {
|
||||
try {
|
||||
if (channel.type !== "text") return null;
|
||||
const embeddedMessage = new MessageEmbed().setTitle(title).setImage(img);
|
||||
const msg = await (channel as TextChannel).send(embeddedMessage);
|
||||
const reactions = [
|
||||
"1️⃣",
|
||||
"2️⃣",
|
||||
"3️⃣",
|
||||
"4️⃣",
|
||||
"5️⃣",
|
||||
"6️⃣",
|
||||
"7️⃣",
|
||||
"8️⃣",
|
||||
"9️⃣",
|
||||
"🔟",
|
||||
];
|
||||
for (const reaction of reactions) {
|
||||
try {
|
||||
await msg.react(reaction);
|
||||
} catch {
|
||||
console.error(`Reaction ${reaction} to channel ${channel.id} failed.`);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
console.error(`Message to channel ${channel.id} failed.`);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
async function getImgTitle(href: string): Promise<Legica> {
|
||||
const response = await axios.get(href);
|
||||
const html = response.data;
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
const title = $(".Article-inner > h1").text();
|
||||
const { src: img } = $(".Article-media > img").attr() || {};
|
||||
|
||||
return { title, img };
|
||||
}
|
||||
|
||||
async function getFirstHtml(): Promise<string> {
|
||||
const response = await axios.get("https://sib.net.hr/legica-dana");
|
||||
const html = response.data;
|
||||
const $ = cheerio.load(html);
|
||||
const { href } = $(".News-link.c-def")?.attr() || {};
|
||||
return href;
|
||||
}
|
||||
|
||||
export default ClientController;
|
||||
@@ -1 +0,0 @@
|
||||
export { default as ClientController } from "./Client.controller";
|
||||
@@ -1,15 +0,0 @@
|
||||
import { IController } from "models";
|
||||
import { Express } from "express";
|
||||
|
||||
class Controller {
|
||||
constructor(private app: Express, private controllers: IController[]) {}
|
||||
|
||||
public register = (): void => {
|
||||
this.controllers?.forEach((controller) => {
|
||||
controller.register();
|
||||
this.app.use(controller.path || "", controller.registerRouter());
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default Controller;
|
||||
65
src/core/basicAuth.ts
Normal file
65
src/core/basicAuth.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import Elysia from "elysia";
|
||||
import { minimatch } from "minimatch";
|
||||
|
||||
export class BasicAuthError extends Error {
|
||||
constructor(public message: string) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
export interface BasicAuthUser {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface BasicAuthConfig {
|
||||
users: BasicAuthUser[];
|
||||
realm?: string;
|
||||
errorMessage?: string;
|
||||
exclude?: string[];
|
||||
noErrorThrown?: boolean;
|
||||
}
|
||||
|
||||
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: "" } };
|
||||
const [username, password] = atob(authorization.split(" ")[1]).split(":");
|
||||
const user = config.users.find(
|
||||
(user) => user.username === username && user.password === password
|
||||
);
|
||||
if (!user) return { basicAuth: { isAuthed: false, username: "" } };
|
||||
return { basicAuth: { isAuthed: true, username: user.username } };
|
||||
})
|
||||
.onTransform((ctx) => {
|
||||
if (
|
||||
!ctx.basicAuth.isAuthed &&
|
||||
!config.noErrorThrown &&
|
||||
!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}"` : ""
|
||||
}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export const isPathExcluded = (path: string, excludedPatterns?: string[]) => {
|
||||
if (!excludedPatterns) return false;
|
||||
for (const pattern of excludedPatterns) {
|
||||
if (minimatch(path, pattern)) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
@@ -1 +1 @@
|
||||
export { default as Controller } from "./Controller";
|
||||
export { basicAuth } from "./basicAuth";
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import { Router } from "express";
|
||||
|
||||
export interface IController {
|
||||
register(): void;
|
||||
registerRouter(): Router;
|
||||
path: string;
|
||||
}
|
||||
@@ -1,3 +1,2 @@
|
||||
export * from "./Controller";
|
||||
export * from "./Command";
|
||||
export * from "./Legica";
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
import { Message } from "discord.js";
|
||||
import { APP_VERSION } from "../constants";
|
||||
|
||||
class CommonModule {
|
||||
constructor() {}
|
||||
public showVersion = (message: Message): void => {
|
||||
message?.channel?.send?.(`Current version of the Monke BOT is ${APP_VERSION}.`);
|
||||
};
|
||||
}
|
||||
|
||||
export default CommonModule;
|
||||
@@ -1 +0,0 @@
|
||||
export { default as CommonModule } from "./Common.module";
|
||||
154
swagger.json
154
swagger.json
@@ -1,154 +0,0 @@
|
||||
{
|
||||
"openapi": "3.0.3",
|
||||
"info": {
|
||||
"title": "Legica Bot API",
|
||||
"license": {
|
||||
"name": "Apache 2.0",
|
||||
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
|
||||
},
|
||||
"version": "0.8.0"
|
||||
},
|
||||
"tags": [
|
||||
{
|
||||
"name": "api",
|
||||
"description": "API information"
|
||||
},
|
||||
{
|
||||
"name": "task",
|
||||
"description": "Everything about the task"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/version": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"api"
|
||||
],
|
||||
"summary": "Display current API version.",
|
||||
"description": "Displays the current API version defined in package.json.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful operation"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/task": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"task"
|
||||
],
|
||||
"summary": "Check if task is running.",
|
||||
"description": "Retrieve the current state of scheduled task.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful operation"
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"tags": [
|
||||
"task"
|
||||
],
|
||||
"summary": "Start task if it is not running.",
|
||||
"description": "Starts the task if it is not currently running.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Task started."
|
||||
},
|
||||
"400": {
|
||||
"description": "Task already running."
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"tags": [
|
||||
"task"
|
||||
],
|
||||
"summary": "Stop task if it is running.",
|
||||
"description": "Stops the task if it is currently running.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Task stopped."
|
||||
},
|
||||
"400": {
|
||||
"description": "Task already stopped."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/task/next": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"task"
|
||||
],
|
||||
"summary": "Check when the task is scheduled due next.",
|
||||
"description": "Retrieve the datetime when task is scheduled to execute.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Next datetime"
|
||||
},
|
||||
"400": {
|
||||
"description": "Task is not running."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/task/send-latest": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"task"
|
||||
],
|
||||
"summary": "Send latest post of legica dana.",
|
||||
"description": "Sends latest post of legica dana to all discord channels.",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Confirmation."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/task/send": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"task"
|
||||
],
|
||||
"summary": "Send post of legica dana.",
|
||||
"description": "Sends provided post of legica dana to all discord channels.",
|
||||
"requestBody": {
|
||||
"description": "URL",
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Legica"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Confirmation."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"Legica": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"url": {
|
||||
"type": "string",
|
||||
"format": "string",
|
||||
"example": "https://sib.net.hr/legica-dana/4390659/legica-dana-2992023/"
|
||||
}
|
||||
},
|
||||
"xml": {
|
||||
"name": "order"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user