Remove local database connections and use Supabase exclusively
Updates bot.js to remove PostgreSQL pool setup and pg dependency, switching all command logging and analytics to use Supabase. Replit-Commit-Author: Agent Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: a88f9025-8fdd-4882-8793-e15b62c35cda Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/7aeQEfK Replit-Helium-Checkpoint-Created: true
This commit is contained in:
parent
734e2f251f
commit
cf175b87e9
2 changed files with 95 additions and 117 deletions
4
.replit
4
.replit
|
|
@ -22,10 +22,6 @@ externalPort = 80
|
||||||
localPort = 8080
|
localPort = 8080
|
||||||
externalPort = 8080
|
externalPort = 8080
|
||||||
|
|
||||||
[[ports]]
|
|
||||||
localPort = 33401
|
|
||||||
externalPort = 3000
|
|
||||||
|
|
||||||
[workflows]
|
[workflows]
|
||||||
runButton = "Project"
|
runButton = "Project"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ const { createClient } = require("@supabase/supabase-js");
|
||||||
const http = require("http");
|
const http = require("http");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { Pool } = require("pg");
|
|
||||||
const WebSocket = require("ws");
|
const WebSocket = require("ws");
|
||||||
|
|
||||||
// Dashboard HTML path
|
// Dashboard HTML path
|
||||||
|
|
@ -69,97 +68,80 @@ if (process.env.SUPABASE_URL && process.env.SUPABASE_SERVICE_ROLE) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// POSTGRESQL DATABASE SETUP (Local database for analytics & config)
|
// COMMAND LOGGING SYSTEM (Supabase-based)
|
||||||
// =============================================================================
|
|
||||||
|
|
||||||
let pgPool = null;
|
|
||||||
if (process.env.DATABASE_URL) {
|
|
||||||
pgPool = new Pool({
|
|
||||||
connectionString: process.env.DATABASE_URL,
|
|
||||||
max: 10,
|
|
||||||
idleTimeoutMillis: 30000,
|
|
||||||
connectionTimeoutMillis: 2000,
|
|
||||||
});
|
|
||||||
pgPool.on('error', (err) => {
|
|
||||||
console.error('PostgreSQL pool error:', err.message);
|
|
||||||
});
|
|
||||||
console.log("PostgreSQL connected");
|
|
||||||
} else {
|
|
||||||
console.log("PostgreSQL not configured - using in-memory storage");
|
|
||||||
}
|
|
||||||
|
|
||||||
// =============================================================================
|
|
||||||
// COMMAND LOGGING SYSTEM
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
async function logCommand(data) {
|
async function logCommand(data) {
|
||||||
if (!pgPool) return;
|
if (!supabase) return;
|
||||||
try {
|
try {
|
||||||
await pgPool.query(
|
await supabase.from('command_logs').insert({
|
||||||
`INSERT INTO command_logs (command_name, user_id, user_tag, guild_id, guild_name, channel_id, success, error_message, execution_time_ms)
|
command_name: data.commandName,
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
user_id: data.userId,
|
||||||
[
|
user_tag: data.userTag,
|
||||||
data.commandName,
|
guild_id: data.guildId,
|
||||||
data.userId,
|
guild_name: data.guildName,
|
||||||
data.userTag,
|
channel_id: data.channelId,
|
||||||
data.guildId,
|
success: data.success,
|
||||||
data.guildName,
|
error_message: data.errorMessage || null,
|
||||||
data.channelId,
|
execution_time_ms: data.executionTime || null
|
||||||
data.success,
|
});
|
||||||
data.errorMessage || null,
|
|
||||||
data.executionTime || null
|
|
||||||
]
|
|
||||||
);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to log command:', err.message);
|
console.error('Failed to log command:', err.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getCommandAnalytics(days = 7) {
|
async function getCommandAnalytics(days = 7) {
|
||||||
if (!pgPool) return { commands: [], hourly: [], daily: [], topUsers: [] };
|
if (!supabase) return { commands: [], hourly: [], daily: [], topUsers: [] };
|
||||||
try {
|
try {
|
||||||
const commandsResult = await pgPool.query(
|
const cutoffDate = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString();
|
||||||
`SELECT command_name, COUNT(*) as count,
|
|
||||||
SUM(CASE WHEN success THEN 1 ELSE 0 END) as success_count,
|
|
||||||
AVG(execution_time_ms) as avg_time
|
|
||||||
FROM command_logs
|
|
||||||
WHERE created_at > NOW() - INTERVAL '${days} days'
|
|
||||||
GROUP BY command_name
|
|
||||||
ORDER BY count DESC
|
|
||||||
LIMIT 20`
|
|
||||||
);
|
|
||||||
|
|
||||||
const hourlyResult = await pgPool.query(
|
const { data: logs } = await supabase
|
||||||
`SELECT EXTRACT(HOUR FROM created_at) as hour, COUNT(*) as count
|
.from('command_logs')
|
||||||
FROM command_logs
|
.select('*')
|
||||||
WHERE created_at > NOW() - INTERVAL '24 hours'
|
.gte('created_at', cutoffDate);
|
||||||
GROUP BY hour
|
|
||||||
ORDER BY hour`
|
|
||||||
);
|
|
||||||
|
|
||||||
const dailyResult = await pgPool.query(
|
if (!logs) return { commands: [], hourly: [], daily: [], topUsers: [] };
|
||||||
`SELECT DATE(created_at) as date, COUNT(*) as count
|
|
||||||
FROM command_logs
|
|
||||||
WHERE created_at > NOW() - INTERVAL '${days} days'
|
|
||||||
GROUP BY date
|
|
||||||
ORDER BY date`
|
|
||||||
);
|
|
||||||
|
|
||||||
const topUsersResult = await pgPool.query(
|
// Calculate command usage
|
||||||
`SELECT user_id, user_tag, COUNT(*) as command_count
|
const commandCounts = {};
|
||||||
FROM command_logs
|
const userCounts = {};
|
||||||
WHERE created_at > NOW() - INTERVAL '${days} days'
|
const hourlyActivity = Array(24).fill(0);
|
||||||
GROUP BY user_id, user_tag
|
const dailyActivity = {};
|
||||||
ORDER BY command_count DESC
|
|
||||||
LIMIT 10`
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
for (const log of logs) {
|
||||||
commands: commandsResult.rows,
|
// Command counts
|
||||||
hourly: hourlyResult.rows,
|
commandCounts[log.command_name] = (commandCounts[log.command_name] || 0) + 1;
|
||||||
daily: dailyResult.rows,
|
|
||||||
topUsers: topUsersResult.rows
|
// User counts
|
||||||
};
|
const userKey = `${log.user_id}|${log.user_tag}`;
|
||||||
|
userCounts[userKey] = (userCounts[userKey] || 0) + 1;
|
||||||
|
|
||||||
|
// Hourly
|
||||||
|
const hour = new Date(log.created_at).getHours();
|
||||||
|
hourlyActivity[hour]++;
|
||||||
|
|
||||||
|
// Daily
|
||||||
|
const dateKey = new Date(log.created_at).toISOString().split('T')[0];
|
||||||
|
dailyActivity[dateKey] = (dailyActivity[dateKey] || 0) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const commands = Object.entries(commandCounts)
|
||||||
|
.map(([name, count]) => ({ command_name: name, count }))
|
||||||
|
.sort((a, b) => b.count - a.count)
|
||||||
|
.slice(0, 20);
|
||||||
|
|
||||||
|
const topUsers = Object.entries(userCounts)
|
||||||
|
.map(([key, count]) => {
|
||||||
|
const [user_id, user_tag] = key.split('|');
|
||||||
|
return { user_id, user_tag, command_count: count };
|
||||||
|
})
|
||||||
|
.sort((a, b) => b.command_count - a.command_count)
|
||||||
|
.slice(0, 10);
|
||||||
|
|
||||||
|
const hourly = hourlyActivity.map((count, hour) => ({ hour, count }));
|
||||||
|
const daily = Object.entries(dailyActivity).map(([date, count]) => ({ date, count }));
|
||||||
|
|
||||||
|
return { commands, hourly, daily, topUsers };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to get command analytics:', err.message);
|
console.error('Failed to get command analytics:', err.message);
|
||||||
return { commands: [], hourly: [], daily: [], topUsers: [] };
|
return { commands: [], hourly: [], daily: [], topUsers: [] };
|
||||||
|
|
@ -167,33 +149,29 @@ async function getCommandAnalytics(days = 7) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getTotalCommandCount() {
|
async function getTotalCommandCount() {
|
||||||
if (!pgPool) return 0;
|
if (!supabase) return 0;
|
||||||
try {
|
try {
|
||||||
const result = await pgPool.query('SELECT COUNT(*) as count FROM command_logs');
|
const { count } = await supabase.from('command_logs').select('*', { count: 'exact', head: true });
|
||||||
return parseInt(result.rows[0].count) || 0;
|
return count || 0;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostgreSQL-based server config functions
|
// Supabase-based server config functions
|
||||||
async function saveServerConfigToDB(guildId, config) {
|
async function saveServerConfigToDB(guildId, config) {
|
||||||
if (!pgPool) return false;
|
if (!supabase) return false;
|
||||||
try {
|
try {
|
||||||
await pgPool.query(
|
await supabase.from('server_config').upsert({
|
||||||
`INSERT INTO server_config (guild_id, welcome_channel, goodbye_channel, modlog_channel, level_up_channel, auto_role, verified_role, updated_at)
|
guild_id: guildId,
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())
|
welcome_channel: config.welcome_channel,
|
||||||
ON CONFLICT (guild_id) DO UPDATE SET
|
goodbye_channel: config.goodbye_channel,
|
||||||
welcome_channel = EXCLUDED.welcome_channel,
|
modlog_channel: config.modlog_channel,
|
||||||
goodbye_channel = EXCLUDED.goodbye_channel,
|
level_up_channel: config.level_up_channel,
|
||||||
modlog_channel = EXCLUDED.modlog_channel,
|
auto_role: config.auto_role,
|
||||||
level_up_channel = EXCLUDED.level_up_channel,
|
verified_role: config.verified_role,
|
||||||
auto_role = EXCLUDED.auto_role,
|
updated_at: new Date().toISOString()
|
||||||
verified_role = EXCLUDED.verified_role,
|
});
|
||||||
updated_at = NOW()`,
|
|
||||||
[guildId, config.welcome_channel, config.goodbye_channel, config.modlog_channel,
|
|
||||||
config.level_up_channel, config.auto_role, config.verified_role]
|
|
||||||
);
|
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to save server config:', err.message);
|
console.error('Failed to save server config:', err.message);
|
||||||
|
|
@ -202,29 +180,30 @@ async function saveServerConfigToDB(guildId, config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getServerConfigFromDB(guildId) {
|
async function getServerConfigFromDB(guildId) {
|
||||||
if (!pgPool) return null;
|
if (!supabase) return null;
|
||||||
try {
|
try {
|
||||||
const result = await pgPool.query(
|
const { data } = await supabase
|
||||||
'SELECT * FROM server_config WHERE guild_id = $1',
|
.from('server_config')
|
||||||
[guildId]
|
.select('*')
|
||||||
);
|
.eq('guild_id', guildId)
|
||||||
return result.rows[0] || null;
|
.single();
|
||||||
|
return data || null;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to get server config:', err.message);
|
console.error('Failed to get server config:', err.message);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Federation mappings with PostgreSQL
|
// Federation mappings with Supabase
|
||||||
async function saveFederationMappingToDB(guildId, roleId, roleName) {
|
async function saveFederationMappingToDB(guildId, roleId, roleName) {
|
||||||
if (!pgPool) return false;
|
if (!supabase) return false;
|
||||||
try {
|
try {
|
||||||
await pgPool.query(
|
await supabase.from('federation_mappings').upsert({
|
||||||
`INSERT INTO federation_mappings (guild_id, role_id, role_name, linked_at)
|
guild_id: guildId,
|
||||||
VALUES ($1, $2, $3, NOW())
|
role_id: roleId,
|
||||||
ON CONFLICT (guild_id, role_id) DO UPDATE SET role_name = EXCLUDED.role_name`,
|
role_name: roleName,
|
||||||
[guildId, roleId, roleName]
|
linked_at: new Date().toISOString()
|
||||||
);
|
});
|
||||||
return true;
|
return true;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to save federation mapping:', err.message);
|
console.error('Failed to save federation mapping:', err.message);
|
||||||
|
|
@ -233,10 +212,13 @@ async function saveFederationMappingToDB(guildId, roleId, roleName) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getFederationMappingsFromDB() {
|
async function getFederationMappingsFromDB() {
|
||||||
if (!pgPool) return [];
|
if (!supabase) return [];
|
||||||
try {
|
try {
|
||||||
const result = await pgPool.query('SELECT * FROM federation_mappings ORDER BY linked_at DESC');
|
const { data } = await supabase
|
||||||
return result.rows;
|
.from('federation_mappings')
|
||||||
|
.select('*')
|
||||||
|
.order('linked_at', { ascending: false });
|
||||||
|
return data || [];
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to get federation mappings:', err.message);
|
console.error('Failed to get federation mappings:', err.message);
|
||||||
return [];
|
return [];
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue