From 6b10aaa009d7125d07c70d726aa36291c97407a2 Mon Sep 17 00:00:00 2001 From: CL TheDreWen Date: Fri, 31 Oct 2025 10:02:44 +0100 Subject: [PATCH] feat: refactor command imports, enhance status service with logging and notification features, and update entity definitions --- src/commands/utility/ping.command.ts | 2 +- src/commands/utility/statut.command.ts | 6 +- src/entity/follow.entity.ts | 2 +- .../{guilds.entity.ts => guild.entity.ts} | 4 +- src/entity/hostslog.entity.ts | 16 ++ src/index.ts | 2 +- src/services/status.service.ts | 236 ++++++++++-------- src/type.d.ts | 3 +- tsconfig.json | 3 +- 9 files changed, 165 insertions(+), 109 deletions(-) rename src/entity/{guilds.entity.ts => guild.entity.ts} (87%) create mode 100644 src/entity/hostslog.entity.ts diff --git a/src/commands/utility/ping.command.ts b/src/commands/utility/ping.command.ts index 67a7edd..bcac13d 100644 --- a/src/commands/utility/ping.command.ts +++ b/src/commands/utility/ping.command.ts @@ -1,4 +1,4 @@ -import { ApplicationIntegrationType, ButtonInteraction, ButtonStyle, ChatInputCommandInteraction, CommandInteraction, ComponentType, ContainerBuilder, InteractionContextType, MessageFlags, SlashCommandBuilder } from "discord.js"; +import { ApplicationIntegrationType, ButtonInteraction, ButtonStyle, ChatInputCommandInteraction, ContainerBuilder, InteractionContextType, MessageFlags, SlashCommandBuilder } from "discord.js"; import { CommandDefinition } from "../../type"; const cmd : CommandDefinition = { diff --git a/src/commands/utility/statut.command.ts b/src/commands/utility/statut.command.ts index f098265..0693bcf 100644 --- a/src/commands/utility/statut.command.ts +++ b/src/commands/utility/statut.command.ts @@ -1,8 +1,6 @@ -import { ApplicationIntegrationType, ChatInputCommandInteraction, CommandInteraction, ContainerBuilder, EmbedBuilder, InteractionContextType, MessageFlags, SlashCommandBuilder } from "discord.js"; -import ping from "ping"; +import { ApplicationIntegrationType, ChatInputCommandInteraction, InteractionContextType, MessageFlags, SlashCommandBuilder } from "discord.js"; import statusService from "../../services/status.service"; -import { CommandDefinition, InfraType } from "../../type"; -import { secureHeapUsed } from "crypto"; +import { CommandDefinition} from "../../type"; const cmd : CommandDefinition = { data: new SlashCommandBuilder() diff --git a/src/entity/follow.entity.ts b/src/entity/follow.entity.ts index 2e62f3e..6e83ea3 100644 --- a/src/entity/follow.entity.ts +++ b/src/entity/follow.entity.ts @@ -1,6 +1,6 @@ import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; -@Entity() +@Entity({name: 'follows'}) export class Follow { @PrimaryGeneratedColumn() id: number; diff --git a/src/entity/guilds.entity.ts b/src/entity/guild.entity.ts similarity index 87% rename from src/entity/guilds.entity.ts rename to src/entity/guild.entity.ts index 75776f0..0de662e 100644 --- a/src/entity/guilds.entity.ts +++ b/src/entity/guild.entity.ts @@ -1,10 +1,10 @@ import { Column, Entity, PrimaryGeneratedColumn } from "typeorm"; -@Entity() +@Entity({name: 'guilds'}) export class Guild { @PrimaryGeneratedColumn() id: number; - + @Column() guild_id: string; diff --git a/src/entity/hostslog.entity.ts b/src/entity/hostslog.entity.ts new file mode 100644 index 0000000..d816209 --- /dev/null +++ b/src/entity/hostslog.entity.ts @@ -0,0 +1,16 @@ +import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from "typeorm"; + +@Entity({name: 'hosts_logs'}) +export class HostsLog { + @PrimaryGeneratedColumn() + id: number; + + @Column() + host: string; + + @Column() + status: boolean; + + @CreateDateColumn() + created_at: Date; +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 6b12402..bc0e185 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import { ButtonInteraction, ChatInputCommandInteraction, Client, Collection, Events, GatewayIntentBits, MessageFlags, SlashCommandBuilder } from "discord.js"; +import { Client, Collection, Events, GatewayIntentBits, MessageFlags } from "discord.js"; import { configDotenv } from "dotenv"; import path from "path"; import fs from "fs"; diff --git a/src/services/status.service.ts b/src/services/status.service.ts index a0b1620..83961aa 100644 --- a/src/services/status.service.ts +++ b/src/services/status.service.ts @@ -2,93 +2,118 @@ import ping from "ping"; import * as cron from 'cron'; import { ActivityType, Client, ContainerBuilder } from "discord.js"; import { Host, InfraType } from "../type"; -import { loadEnvFile } from "process"; -import { configDotenv } from "dotenv"; +import { AppDataSource } from "../data-source"; +import { HostsLog } from "../entity/hostslog.entity"; +import { Repository } from "typeorm"; export class StatusService { public hosts: Host[] = [ - { - 'host': 'https://protojx.com', - 'name': 'Protojx Website', - alive: false, - ping_type: 'website', - type: 'website' - }, - { - 'host': 'https://manager.protojx.com', - 'name': 'Espace Client', - alive: false, - ping_type: 'website', - type: 'website' - }, - { - host: '5.178.99.4', - name: 'RYZEN 01', - alive: false, - ping_type: 'ping', - type: 'ryzen' - }, - { - host: '5.178.99.6', - name: 'RYZEN 02', - alive: false, - ping_type: 'ping', - type: 'ryzen' - }, - { - host: '5.178.99.5', - name: 'RYZEN 03', - alive: false, - ping_type: 'ping', - type: 'ryzen' - }, - { - host: '154.16.254.10', - name: 'RYZEN7 04', - alive: false, - ping_type: 'ping', - type: 'ryzen' - }, - { - host: '5.178.99.177', - name: 'XEON 01 (2697A V4)', - alive: false, - ping_type: 'ping', - type: 'xeon' - }, - { - host: '5.178.99.248', - name: 'XEON 02 (2687W V4)', - alive: false, - ping_type: 'ping', - type: 'xeon' - }, - { - host: '5.178.99.53', - name: 'RYZEN-GAME 01', - alive: false, - ping_type: 'ping', - type: 'games' - }, - { - host: '5.178.99.63', - name: 'XEON-GAME 01', - alive: false, - ping_type: 'ping', - type: 'games' - } - ]; + { + 'host': 'https://protojx.com', + 'name': 'Protojx Website', + alive: false, + ping_type: 'website', + type: 'website', + notify: false + }, + { + 'host': 'https://manager.protojx.com', + 'name': 'Espace Client', + alive: false, + ping_type: 'website', + type: 'website', + notify: false + }, + { + host: '5.178.99.4', + name: 'RYZEN 01', + alive: false, + ping_type: 'ping', + type: 'ryzen', + notify: true + }, + { + host: '5.178.99.6', + name: 'RYZEN 02', + alive: false, + ping_type: 'ping', + type: 'ryzen', + notify: true + }, + { + host: '5.178.99.5', + name: 'RYZEN 03', + alive: false, + ping_type: 'ping', + type: 'ryzen', + notify: true + }, + { + host: '154.16.254.10', + name: 'RYZEN7 04', + alive: false, + ping_type: 'ping', + type: 'ryzen', + notify: true + }, + { + host: '5.178.99.177', + name: 'XEON 01 (2697A V4)', + alive: false, + ping_type: 'ping', + type: 'xeon', + notify: true + }, + { + host: '5.178.99.248', + name: 'XEON 02 (2687W V4)', + alive: false, + ping_type: 'ping', + type: 'xeon', + notify: true + }, + { + host: '5.178.99.53', + name: 'RYZEN-GAME 01', + alive: false, + ping_type: 'ping', + type: 'games', + notify: true + }, + { + host: '5.178.99.63', + name: 'XEON-GAME 01', + alive: false, + ping_type: 'ping', + type: 'games', + notify: true + } + ]; private client: Client | null = null; + private hostsLogRepo: Repository; constructor() { + this.hostsLogRepo = AppDataSource.getRepository(HostsLog); + setTimeout(async () => { await this.fetch() this.updateClientStatus(); }, 3000); const cronJob = new cron.CronJob('*/2 * * * *', async () => { + + const oneMonthAgo = new Date(); + oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1); + + this.hostsLogRepo. + createQueryBuilder() + .delete() + .from(HostsLog) + .where("created_at < :date", { date: oneMonthAgo }) + .execute(); + try { await this.fetch(); await this.updateClientStatus(); @@ -119,27 +144,42 @@ export class StatusService { } } + private async fetchAlive(host: Host) { + + const latestLog = await this.hostsLogRepo.findOne({ where: { host: host.host }, order: { created_at: 'DESC' } }); + + // ? Ping and Request Hosts + if (host.ping_type === 'ping') { + let res = await ping.promise.probe(host.host, { timeout: 10 }); + host.alive = res.alive; + } else if (host.ping_type === 'website') { + try { + const response = await fetch(host.host, { method: 'HEAD', signal: AbortSignal.timeout(10000) }); + host.alive = response.ok; + } catch (error) { + host.alive = false; + } + } + + // ? Notification System : + if (!latestLog || latestLog.status != host.alive) { + const log = new HostsLog(); + log.host = host.host; + log.status = host.alive; + + this.hostsLogRepo.save(log); + } + + return host; + } + private async fetch(max = 1): Promise { const max_ping = 3; const hosts = this.hosts.filter((value, index) => index < max * max_ping && index >= (max - 1) * max_ping); - async function fetchAlive(host: Host) { - if (host.ping_type === 'ping') { - let res = await ping.promise.probe(host.host, { timeout: 10 }); - host.alive = res.alive; - } else if (host.ping_type === 'website') { - try { - const response = await fetch(host.host, { method: 'HEAD', signal: AbortSignal.timeout(10000) }); - host.alive = response.ok; - } catch (error) { - host.alive = false; - } - } - return host; - } - const fetchPromises = hosts.map(host => fetchAlive(host)); + const fetchPromises = hosts.map(host => this.fetchAlive(host)); const updatedHosts = await Promise.all(fetchPromises); updatedHosts.forEach((updatedHost, index) => { @@ -156,16 +196,16 @@ export class StatusService { return this.hosts; } - public getUpdatedContainer() : ContainerBuilder { + public getUpdatedContainer(): ContainerBuilder { const hostTexts = this.hosts.map((s) => { - return {type: s.type, value: `- ${s.name} : ${s.alive ? `${process.env.EMOJI_STATUS_ONLINE} Online` : `${process.env.EMOJI_STATUS_OFFLINE} Offline`}`}; + return { type: s.type, value: `- ${s.name} : ${s.alive ? `${process.env.EMOJI_STATUS_ONLINE} Online` : `${process.env.EMOJI_STATUS_OFFLINE} Offline`}` }; }); const container = new ContainerBuilder() .setAccentColor(0x0000ed) .addTextDisplayComponents((text) => text.setContent('# Status of protojx services')); - - const sections : {title: string, type: InfraType, thumbnail: string}[] = [ + + const sections: { title: string, type: InfraType, thumbnail: string }[] = [ { title: 'Websites', type: 'website', @@ -194,17 +234,17 @@ export class StatusService { (section) => section.addTextDisplayComponents( (text) => - text.setContent('## '+sectionData.title+'\n'+hostTexts.filter((v) => v.type == sectionData.type).map((v) => v.value).join('\n')) - ) - .setThumbnailAccessory( - (acc) => - acc.setURL(sectionData.thumbnail) + text.setContent('## ' + sectionData.title + '\n' + hostTexts.filter((v) => v.type == sectionData.type).map((v) => v.value).join('\n')) ) + .setThumbnailAccessory( + (acc) => + acc.setURL(sectionData.thumbnail) + ) ) }); const now = new Date(); - container.addTextDisplayComponents((text) => text.setContent(`${now.getDate()}-${now.getMonth() + 1}-${now.getFullYear()} ${(now.getHours()+'').padStart(2, "0")}:${(now.getMinutes()+'').padStart(2, "0")}`)); + container.addTextDisplayComponents((text) => text.setContent(`${now.getDate()}-${now.getMonth() + 1}-${now.getFullYear()} ${(now.getHours() + '').padStart(2, "0")}:${(now.getMinutes() + '').padStart(2, "0")}`)); return container; } diff --git a/src/type.d.ts b/src/type.d.ts index 4e70cc2..1d86eab 100644 --- a/src/type.d.ts +++ b/src/type.d.ts @@ -6,6 +6,7 @@ export type Host = { name: string, alive: boolean, ping_type: 'ping' | 'website', - type: InfraType + type: InfraType, + notify: boolean; }; export type CommandDefinition = { data: SlashCommandBuilder, execute: (interaction: ChatInputCommandInteraction) => void, buttons?: { id: string, handle: (interaction: ButtonInteraction) => void}[]}; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 0fa1162..299207b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,7 +25,8 @@ "emitDecoratorMetadata": true, "removeComments": false, "preserveConstEnums": true, - "strictPropertyInitialization": false + "strictPropertyInitialization": false, + "noUnusedLocals": true }, "include": [ "src/**/*"