feat: make HTTP API optional (default false) and add /dev system command for in-bot telemetry

swadhinbiswas committed 1 months ago
3eb23ce
1 parent 6bdafc3
commit 3eb23ce
5 files changed
+117 -18
β–Ό.env.example
+4-0
4
4
5
5
NODE_ENV=development
6
6
LOG_LEVEL=info
7
+
8
+# Optional HTTP API server (Disabled by default: pure Discord bot mode with zero open ports)
9
+# Set to 'true' only if deploying to hosting platforms (Render/Fly.io) requiring an HTTP health check port
10
+ENABLE_HTTP_API=false
7
11
PORT=3000
8
12
HOST=0.0.0.0
9
13
10
14
β–Όapps/bot/src/commands/dev.ts
+84-0
1
1
import { analyticsService } from "@devpulse/analytics";
2
+import { formatBytes } from "@devpulse/core";
2
3
import { db, users } from "@devpulse/database";
3
4
import { githubClient } from "@devpulse/github";
4
5
import { SlashCommandBuilder } from "discord.js";
18
19
"View your personal developer productivity dashboard (activity, focus, cycle time)",
19
20
),
20
21
)
22
+ .addSubcommand((sub) =>
23
+ sub
24
+ .setName("system")
25
+ .setDescription(
26
+ "View DevPulse bot system health, database status, memory stats, and gateway latency",
27
+ ),
28
+ )
21
29
.addSubcommand((sub) =>
22
30
sub
23
31
.setName("profile")
79
87
return;
80
88
}
81
89
90
+ if (subcommand === "system") {
91
+ const memory = process.memoryUsage();
92
+ const allServers = await db.query.servers.findMany();
93
+ const allRepos = await db.query.repositories.findMany();
94
+ const allMonitors = await db.query.monitors.findMany();
95
+
96
+ const uptimeSec = Math.floor(process.uptime());
97
+ const days = Math.floor(uptimeSec / 86400);
98
+ const hours = Math.floor((uptimeSec % 86400) / 3600);
99
+ const minutes = Math.floor((uptimeSec % 3600) / 60);
100
+ const seconds = uptimeSec % 60;
101
+ const uptimeParts: string[] = [];
102
+ if (days > 0) uptimeParts.push(`${days}d`);
103
+ if (hours > 0) uptimeParts.push(`${hours}h`);
104
+ if (minutes > 0) uptimeParts.push(`${minutes}m`);
105
+ uptimeParts.push(`${seconds}s`);
106
+ const uptimeStr = uptimeParts.join(" ");
107
+
108
+ const ping = interaction.client.ws.ping;
109
+ const pingStatus = ping < 150 ? "🟒" : ping < 300 ? "🟑" : "πŸ”΄";
110
+
111
+ const embed = createBaseEmbed("⚑ DevPulse Bot System Telemetry")
112
+ .setColor(BrandColors.primary)
113
+ .setDescription("Live operational status, database health, and memory metrics:")
114
+ .addFields(
115
+ {
116
+ name: "πŸ“Ά Gateway Latency",
117
+ value: `${pingStatus} \`${ping >= 0 ? `${ping}ms` : "Connecting..."}\``,
118
+ inline: true,
119
+ },
120
+ {
121
+ name: "⏱️ Bot Uptime",
122
+ value: `\`${uptimeStr}\``,
123
+ inline: true,
124
+ },
125
+ {
126
+ name: "πŸ—„οΈ Database (libSQL)",
127
+ value: "🟒 Connected",
128
+ inline: true,
129
+ },
130
+ {
131
+ name: "🧠 Memory (RSS)",
132
+ value: `\`${formatBytes(memory.rss)}\``,
133
+ inline: true,
134
+ },
135
+ {
136
+ name: "πŸ“¦ Heap (Used / Total)",
137
+ value: `\`${formatBytes(memory.heapUsed)} / ${formatBytes(memory.heapTotal)}\``,
138
+ inline: true,
139
+ },
140
+ {
141
+ name: "⚑ Runtime",
142
+ value: `\`Bun ${typeof Bun !== "undefined" ? (Bun as any).version : process.version}\``,
143
+ inline: true,
144
+ },
145
+ {
146
+ name: "🌐 Discord Servers",
147
+ value: `\`${interaction.client.guilds.cache.size}\` Connected (\`${allServers.length}\` Registered)`,
148
+ inline: true,
149
+ },
150
+ {
151
+ name: "πŸ“‘ Tracked Repos",
152
+ value: `\`${allRepos.length}\` Repositories`,
153
+ inline: true,
154
+ },
155
+ {
156
+ name: "🚦 Active Monitors",
157
+ value: `\`${allMonitors.filter((m) => m.isActive).length} / ${allMonitors.length}\` Active`,
158
+ inline: true,
159
+ },
160
+ );
161
+
162
+ await interaction.editReply({ embeds: [embed] });
163
+ return;
164
+ }
165
+
82
166
if (subcommand === "dashboard") {
83
167
const stats = await analyticsService.getPersonalDashboard(targetUser.id, 7);
84
168
const embed = createPersonalDashboardEmbed(stats);
85
169
β–Όapps/bot/src/commands/help.ts
+2-2
41
41
inline: false,
42
42
},
43
43
{
44
- name: "πŸ‘¨β€πŸ’» Developer Telemetry",
45
- value: "`/dev dashboard`, `/dev profile`, `/dev link-github`",
44
+ name: "πŸ‘¨β€πŸ’» Developer Telemetry & System",
45
+ value: "`/dev dashboard`, `/dev profile`, `/dev link-github`, `/dev system`",
46
46
inline: false,
47
47
},
48
48
{
49
49
β–Όapps/bot/src/index.ts
+26-16
34
34
// 2. Initialize Discord Client
35
35
const client = createBotClient();
36
36
37
- // 3. Initialize & start Fastify HTTP API Server (exposes /health, /ready, /metrics)
38
- const apiServer = buildServer({ discordClient: client });
39
- try {
40
- const address = await apiServer.listen({
41
- port: config.PORT,
42
- host: config.HOST,
43
- });
44
- logger.info({ address }, `DevPulse HTTP API server listening on ${address}`);
45
- } catch (err) {
46
- logger.error({ err }, "Failed to bind DevPulse HTTP API server");
47
- process.exit(1);
37
+ // 3. Conditionally initialize Fastify HTTP API Server (pure Discord bot mode by default)
38
+ let apiServer: any = null;
39
+ if (config.ENABLE_HTTP_API) {
40
+ apiServer = buildServer({ discordClient: client });
41
+ try {
42
+ const address = await apiServer.listen({
43
+ port: config.PORT,
44
+ host: config.HOST,
45
+ });
46
+ logger.info({ address }, `DevPulse HTTP API server listening on ${address}`);
47
+ } catch (err) {
48
+ logger.error({ err }, "Failed to bind DevPulse HTTP API server");
49
+ }
50
+ } else {
51
+ logger.info("Running in pure Discord bot mode (HTTP API disabled, zero open ports needed)");
48
52
}
49
53
50
54
// 4. Graceful shutdown handler
52
56
const shutdown = async (signal: string) => {
53
57
if (isShuttingDown) return;
54
58
isShuttingDown = true;
55
- logger.info({ signal }, "Gracefully shutting down DevPulse unified platform...");
59
+ logger.info({ signal }, "Gracefully shutting down DevPulse platform...");
56
60
scheduler.stop();
57
61
try {
58
- await apiServer.close();
62
+ if (apiServer) {
63
+ await apiServer.close();
64
+ }
59
65
client.destroy();
60
66
logger.info("DevPulse shutdown completed successfully.");
61
67
} catch (err) {
75
81
config.DISCORD_TOKEN.startsWith("your_discord_bot_token");
76
82
77
83
if (isDefaultOrPlaceholderToken) {
78
- logger.warn(
79
- `⚠️ DevPulse HTTP API is running on port ${config.PORT}, but DISCORD_TOKEN in .env is not configured.`,
80
- );
84
+ if (config.ENABLE_HTTP_API) {
85
+ logger.warn(
86
+ `⚠️ DevPulse HTTP API is running on port ${config.PORT}, but DISCORD_TOKEN in .env is not configured.`,
87
+ );
88
+ } else {
89
+ logger.warn("⚠️ DevPulse is ready, but DISCORD_TOKEN in .env is not configured.");
90
+ }
81
91
logger.warn(
82
92
"πŸ‘‰ Please edit /home/swadhin/discordbot/.env and set your DISCORD_TOKEN and DISCORD_CLIENT_ID to connect to Discord.",
83
93
);
84
94
β–Όpackages/config/src/index.ts
+1-0
3
3
export const ConfigSchema = z.object({
4
4
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
5
5
LOG_LEVEL: z.enum(["fatal", "error", "warn", "info", "debug", "trace"]).default("info"),
6
+ ENABLE_HTTP_API: z.coerce.boolean().default(false),
6
7
PORT: z.coerce.number().int().positive().default(3000),
7
8
HOST: z.string().default("0.0.0.0"),
8
9
9
10