24 Commits

Author SHA1 Message Date
syntaxbullet
39e405afde chore: polish analytics API logging and typing 2026-01-08 21:39:53 +01:00
syntaxbullet
6763e3c543 fix: address code review findings for analytics and security 2026-01-08 21:39:01 +01:00
syntaxbullet
11e07a0068 feat: implement visual analytics and activity charts 2026-01-08 21:36:19 +01:00
syntaxbullet
5d2d4bb0c6 refactor: improve type safety and remove forced casts in dashboard service 2026-01-08 21:31:40 +01:00
syntaxbullet
19206b5cc7 fix: address security review findings, implement real cache clearing, and fix lifecycle promises 2026-01-08 21:29:09 +01:00
syntaxbullet
0f6cce9b6e feat: implement administrative control panel with real-time bot actions 2026-01-08 21:19:16 +01:00
syntaxbullet
3f3a6c88e8 fix(dash): resolve test regressions, await promises, and improve TypeScript strictness 2026-01-08 21:12:41 +01:00
syntaxbullet
8253de9f73 fix(dash): address safety constraints, validation, and test quality issues 2026-01-08 21:08:47 +01:00
syntaxbullet
1251df286e feat: implement real-time dashboard updates via WebSockets 2026-01-08 21:01:33 +01:00
syntaxbullet
fff90804c0 feat(dash): Revamp dashboard UI with glassmorphism and real bot data 2026-01-08 20:58:57 +01:00
syntaxbullet
8ebaf7b4ee docs: update ticket status to In Review with implementation notes 2026-01-08 18:51:58 +01:00
syntaxbullet
17cb70ec00 feat: integrate real data into dashboard
- Created dashboard service with DB queries for users, economy, events
- Added client stats provider with 30s caching for Discord metrics
- Implemented /api/stats endpoint aggregating all dashboard data
- Created useDashboardStats React hook with auto-refresh
- Updated Dashboard.tsx to display real data with loading/error states
- Added comprehensive test coverage (11 tests passing)
- Replaced all mock values with live Discord and database metrics
2026-01-08 18:50:44 +01:00
syntaxbullet
a207d511be docs: clarify drizzle studio access via proxy URL 2026-01-08 18:20:27 +01:00
syntaxbullet
cf4f180124 fix: add web network to studio for port publishing 2026-01-08 18:17:27 +01:00
syntaxbullet
5df1396b3f chore: update docker compose 2026-01-08 18:12:39 +01:00
syntaxbullet
daad7be01c chore: attempt fixing drizzle studio 2026-01-08 18:04:40 +01:00
syntaxbullet
05f27ca604 refactor: fix frontend 2026-01-08 17:01:36 +01:00
syntaxbullet
d37059d50f chore: remove tickets from future commits 2026-01-08 16:45:49 +01:00
syntaxbullet
caafe6b34d refactor: update graphics paths 2026-01-08 16:42:14 +01:00
syntaxbullet
017f5ad818 refactor: fix stale imports 2026-01-08 16:39:34 +01:00
syntaxbullet
f92415b89c refactor: move drizzle to shared 2026-01-08 16:29:31 +01:00
syntaxbullet
3f028eb76a refactor: consolidate config loading 2026-01-08 16:21:25 +01:00
syntaxbullet
2b641c952d refactor: move config loading to shared directory 2026-01-08 16:15:55 +01:00
syntaxbullet
88b266f81b refactor: initial moves 2026-01-08 16:09:26 +01:00
204 changed files with 3401 additions and 841 deletions

View File

@@ -7,6 +7,7 @@ DISCORD_BOT_TOKEN=your-discord-bot-token
DISCORD_CLIENT_ID=your-discord-client-id
DISCORD_GUILD_ID=your-discord-guild-id
DATABASE_URL=postgres://aurora:aurora@db:5432/aurora
ADMIN_TOKEN=Ffeg4hgsdfvsnyms,kmeuy64sy5y
VPS_USER=your-vps-user
VPS_HOST=your-vps-ip

8
.gitignore vendored
View File

@@ -1,7 +1,8 @@
.env
node_modules
db-logs
db-data
shared/db-logs
shared/db/data
shared/db/loga
.cursor
# dependencies (bun install)
@@ -43,5 +44,4 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
src/db/data
src/db/log
scratchpad/
scratchpad/

View File

@@ -9,8 +9,8 @@ COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
# Install web project dependencies
COPY src/web/package.json src/web/bun.lock ./src/web/
RUN cd src/web && bun install --frozen-lockfile
COPY web/package.json web/bun.lock ./web/
RUN cd web && bun install --frozen-lockfile
# Copy source code
COPY . .

View File

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

View File

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@@ -1,6 +1,6 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js";
import { ModerationService } from "@/modules/moderation/moderation.service";
import { ModerationService } from "@shared/modules/moderation/moderation.service";
import { getCaseEmbed, getModerationErrorEmbed } from "@/modules/moderation/moderation.view";
export const moderationCase = createCommand({

View File

@@ -1,6 +1,6 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js";
import { ModerationService } from "@/modules/moderation/moderation.service";
import { ModerationService } from "@shared/modules/moderation/moderation.service";
import { getCasesListEmbed, getModerationErrorEmbed } from "@/modules/moderation/moderation.view";
export const cases = createCommand({

View File

@@ -1,6 +1,6 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js";
import { ModerationService } from "@/modules/moderation/moderation.service";
import { ModerationService } from "@shared/modules/moderation/moderation.service";
import { getClearSuccessEmbed, getModerationErrorEmbed } from "@/modules/moderation/moderation.view";
export const clearwarning = createCommand({

View File

@@ -1,7 +1,7 @@
import { createCommand } from "@lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder, PermissionFlagsBits, ModalBuilder, TextInputBuilder, TextInputStyle, ActionRowBuilder, ModalSubmitInteraction } from "discord.js";
import { config, saveConfig } from "@lib/config";
import type { GameConfigType } from "@lib/config";
import { config, saveConfig } from "@shared/lib/config";
import type { GameConfigType } from "@shared/lib/config";
import { createSuccessEmbed, createErrorEmbed } from "@lib/embeds";
export const configCommand = createCommand({

View File

@@ -1,8 +1,8 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder, PermissionFlagsBits, AttachmentBuilder } from "discord.js";
import { config, saveConfig } from "@/lib/config";
import { DrizzleClient } from "@/lib/DrizzleClient";
import { items } from "@/db/schema";
import { config, saveConfig } from "@shared/lib/config";
import { DrizzleClient } from "@shared/db/DrizzleClient";
import { items } from "@db/schema";
import { createSuccessEmbed, createErrorEmbed } from "@lib/embeds";
export const createColor = createCommand({

View File

@@ -1,4 +1,4 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js";
import { renderWizard } from "@/modules/admin/item_wizard";

View File

@@ -1,8 +1,7 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js";
import { createBaseEmbed } from "@lib/embeds";
import { configManager } from "@/lib/configManager";
import { config, reloadConfig } from "@/lib/config";
import { config, reloadConfig, toggleCommand } from "@shared/lib/config";
import { AuroraClient } from "@/lib/BotClient";
export const features = createCommand({
@@ -79,11 +78,11 @@ export const features = createCommand({
await interaction.deferReply({ flags: MessageFlags.Ephemeral });
configManager.toggleCommand(commandName, enabled);
toggleCommand(commandName, enabled);
await interaction.editReply({ content: `✅ Command **${commandName}** has been ${enabled ? "enabled" : "disabled"}. Reloading configuration...` });
// Reload config from disk (which was updated by configManager)
// Reload config from disk (which was updated by toggleCommand)
reloadConfig();
await AuroraClient.loadCommands(true);

View File

@@ -5,7 +5,7 @@ import { AuroraClient } from "@/lib/BotClient";
// Mock DrizzleClient
const executeMock = mock(() => Promise.resolve());
mock.module("@/lib/DrizzleClient", () => ({
mock.module("@shared/db/DrizzleClient", () => ({
DrizzleClient: {
execute: executeMock
}

View File

@@ -1,7 +1,7 @@
import { createCommand } from "@lib/utils";
import { createCommand } from "@shared/lib/utils";
import { AuroraClient } from "@/lib/BotClient";
import { SlashCommandBuilder, PermissionFlagsBits, EmbedBuilder, Colors } from "discord.js";
import { DrizzleClient } from "@/lib/DrizzleClient";
import { DrizzleClient } from "@shared/db/DrizzleClient";
import { sql } from "drizzle-orm";
import { createBaseEmbed } from "@lib/embeds";

View File

@@ -1,4 +1,4 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import {
SlashCommandBuilder,
ActionRowBuilder,
@@ -8,12 +8,12 @@ import {
PermissionFlagsBits,
MessageFlags
} from "discord.js";
import { inventoryService } from "@/modules/inventory/inventory.service";
import { inventoryService } from "@shared/modules/inventory/inventory.service";
import { createSuccessEmbed, createErrorEmbed, createBaseEmbed } from "@lib/embeds";
import { UserError } from "@/lib/errors";
import { items } from "@/db/schema";
import { items } from "@db/schema";
import { ilike, isNotNull, and } from "drizzle-orm";
import { DrizzleClient } from "@/lib/DrizzleClient";
import { DrizzleClient } from "@shared/db/DrizzleClient";
import { getShopListingMessage } from "@/modules/economy/shop.view";
export const listing = createCommand({

View File

@@ -1,7 +1,7 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js";
import { ModerationService } from "@/modules/moderation/moderation.service";
import { CaseType } from "@/lib/constants";
import { ModerationService } from "@shared/modules/moderation/moderation.service";
import { CaseType } from "@shared/lib/constants";
import { getNoteSuccessEmbed, getModerationErrorEmbed } from "@/modules/moderation/moderation.view";
export const note = createCommand({

View File

@@ -1,6 +1,6 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js";
import { ModerationService } from "@/modules/moderation/moderation.service";
import { ModerationService } from "@shared/modules/moderation/moderation.service";
import { getCasesListEmbed, getModerationErrorEmbed } from "@/modules/moderation/moderation.view";
export const notes = createCommand({

View File

@@ -1,7 +1,7 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags, ComponentType } from "discord.js";
import { config } from "@/lib/config";
import { PruneService } from "@/modules/moderation/prune.service";
import { config } from "@shared/lib/config";
import { PruneService } from "@shared/modules/moderation/prune.service";
import {
getConfirmationMessage,
getProgressEmbed,

View File

@@ -1,4 +1,4 @@
import { createCommand } from "@lib/utils";
import { createCommand } from "@shared/lib/utils";
import { AuroraClient } from "@/lib/BotClient";
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js";
import { createErrorEmbed, createSuccessEmbed } from "@lib/embeds";

View File

@@ -1,7 +1,7 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder, PermissionFlagsBits, ChannelType, TextChannel } from "discord.js";
import { terminalService } from "@/modules/terminal/terminal.service";
import { terminalService } from "@shared/modules/terminal/terminal.service";
import { createBaseEmbed, createErrorEmbed } from "@/lib/embeds";
export const terminal = createCommand({

View File

@@ -1,6 +1,6 @@
import { createCommand } from "@lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags, ComponentType } from "discord.js";
import { UpdateService } from "@/modules/admin/update.service";
import { UpdateService } from "@shared/modules/admin/update.service";
import {
getCheckingEmbed,
getNoUpdatesEmbed,

View File

@@ -1,12 +1,12 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js";
import { ModerationService } from "@/modules/moderation/moderation.service";
import { ModerationService } from "@shared/modules/moderation/moderation.service";
import {
getWarnSuccessEmbed,
getModerationErrorEmbed,
getUserWarningEmbed
} from "@/modules/moderation/moderation.view";
import { config } from "@/lib/config";
import { config } from "@shared/lib/config";
export const warn = createCommand({
data: new SlashCommandBuilder()

View File

@@ -1,6 +1,6 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js";
import { ModerationService } from "@/modules/moderation/moderation.service";
import { ModerationService } from "@shared/modules/moderation/moderation.service";
import { getWarningsEmbed, getModerationErrorEmbed } from "@/modules/moderation/moderation.view";
export const warnings = createCommand({

View File

@@ -1,4 +1,4 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder, PermissionFlagsBits, MessageFlags } from "discord.js";
import { createErrorEmbed } from "@/lib/embeds";
import { sendWebhookMessage } from "@/lib/webhookUtils";

View File

@@ -1,6 +1,6 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder } from "discord.js";
import { userService } from "@/modules/user/user.service";
import { userService } from "@shared/modules/user/user.service";
import { createBaseEmbed } from "@lib/embeds";
export const balance = createCommand({

View File

@@ -1,7 +1,7 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder } from "discord.js";
import { economyService } from "@/modules/economy/economy.service";
import { economyService } from "@shared/modules/economy/economy.service";
import { createErrorEmbed, createSuccessEmbed } from "@lib/embeds";
import { UserError } from "@/lib/errors";

View File

@@ -1,13 +1,13 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder } from "discord.js";
import { userService } from "@/modules/user/user.service";
import { userService } from "@shared/modules/user/user.service";
import { createErrorEmbed, createSuccessEmbed } from "@lib/embeds";
import { UserError } from "@/lib/errors";
import { userTimers, users } from "@/db/schema";
import { userTimers, users } from "@db/schema";
import { eq, and, sql } from "drizzle-orm";
import { DrizzleClient } from "@/lib/DrizzleClient";
import { config } from "@lib/config";
import { TimerType } from "@/lib/constants";
import { DrizzleClient } from "@shared/db/DrizzleClient";
import { config } from "@shared/lib/config";
import { TimerType } from "@shared/lib/constants";
const EXAM_TIMER_TYPE = TimerType.EXAM_SYSTEM;
const EXAM_TIMER_KEY = 'default';

View File

@@ -1,9 +1,9 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder, MessageFlags } from "discord.js";
import { economyService } from "@/modules/economy/economy.service";
import { userService } from "@/modules/user/user.service";
import { config } from "@/lib/config";
import { economyService } from "@shared/modules/economy/economy.service";
import { userService } from "@shared/modules/user/user.service";
import { config } from "@shared/lib/config";
import { createErrorEmbed, createSuccessEmbed } from "@lib/embeds";
import { UserError } from "@/lib/errors";

View File

@@ -1,6 +1,6 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder, ChannelType, ThreadAutoArchiveDuration, MessageFlags } from "discord.js";
import { tradeService } from "@/modules/trade/trade.service";
import { tradeService } from "@shared/modules/trade/trade.service";
import { getTradeDashboard } from "@/modules/trade/trade.view";
import { createErrorEmbed, createWarningEmbed } from "@lib/embeds";

View File

@@ -1,6 +1,6 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder } from "discord.js";
import { config } from "@/lib/config";
import { config } from "@shared/lib/config";
import { createErrorEmbed } from "@/lib/embeds";
import { getFeedbackTypeMenu } from "@/modules/feedback/feedback.view";

View File

@@ -1,7 +1,7 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder } from "discord.js";
import { inventoryService } from "@/modules/inventory/inventory.service";
import { userService } from "@/modules/user/user.service";
import { inventoryService } from "@shared/modules/inventory/inventory.service";
import { userService } from "@shared/modules/user/user.service";
import { createWarningEmbed } from "@lib/embeds";
import { getInventoryEmbed } from "@/modules/inventory/inventory.view";

View File

@@ -1,12 +1,12 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder } from "discord.js";
import { inventoryService } from "@/modules/inventory/inventory.service";
import { userService } from "@/modules/user/user.service";
import { inventoryService } from "@shared/modules/inventory/inventory.service";
import { userService } from "@shared/modules/user/user.service";
import { createErrorEmbed } from "@lib/embeds";
import { getItemUseResultEmbed } from "@/modules/inventory/inventory.view";
import type { ItemUsageData } from "@/lib/types";
import type { ItemUsageData } from "@shared/lib/types";
import { UserError } from "@/lib/errors";
import { config } from "@/lib/config";
import { config } from "@shared/lib/config";
export const use = createCommand({
data: new SlashCommandBuilder()

View File

@@ -1,7 +1,7 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder } from "discord.js";
import { DrizzleClient } from "@/lib/DrizzleClient";
import { users, items, inventory } from "@/db/schema";
import { DrizzleClient } from "@shared/db/DrizzleClient";
import { users, items, inventory } from "@db/schema";
import { desc, sql, eq } from "drizzle-orm";
import { createWarningEmbed } from "@lib/embeds";
import { getLeaderboardEmbed } from "@/modules/leveling/leveling.view";

View File

@@ -1,6 +1,6 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder, MessageFlags } from "discord.js";
import { questService } from "@/modules/quest/quest.service";
import { questService } from "@shared/modules/quest/quest.service";
import { createWarningEmbed } from "@lib/embeds";
import { getQuestListEmbed } from "@/modules/quest/quest.view";

View File

@@ -1,6 +1,6 @@
import { createCommand } from "@/lib/utils";
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder, AttachmentBuilder } from "discord.js";
import { userService } from "@/modules/user/user.service";
import { userService } from "@shared/modules/user/user.service";
import { generateStudentIdCard } from "@/graphics/studentID";
import { createWarningEmbed } from "@/lib/embeds";

View File

@@ -1,7 +1,7 @@
import { Events } from "discord.js";
import type { Event } from "@lib/types";
import { config } from "@lib/config";
import { userService } from "@modules/user/user.service";
import type { Event } from "@shared/lib/types";
import { config } from "@shared/lib/config";
import { userService } from "@shared/modules/user/user.service";
// Visitor role
const event: Event<Events.GuildMemberAdd> = {

View File

@@ -1,6 +1,6 @@
import { Events } from "discord.js";
import { ComponentInteractionHandler, AutocompleteHandler, CommandHandler } from "@/lib/handlers";
import type { Event } from "@lib/types";
import type { Event } from "@shared/lib/types";
const event: Event<Events.InteractionCreate> = {
name: Events.InteractionCreate,

View File

@@ -1,7 +1,7 @@
import { Events } from "discord.js";
import { userService } from "@/modules/user/user.service";
import { levelingService } from "@/modules/leveling/leveling.service";
import type { Event } from "@lib/types";
import { userService } from "@shared/modules/user/user.service";
import { levelingService } from "@shared/modules/leveling/leveling.service";
import type { Event } from "@shared/lib/types";
const event: Event<Events.MessageCreate> = {
name: Events.MessageCreate,
@@ -15,7 +15,7 @@ const event: Event<Events.MessageCreate> = {
levelingService.processChatXp(message.author.id);
// Activity Tracking for Lootdrops
import("@/modules/economy/lootdrop.service").then(m => m.lootdropService.processMessage(message));
import("@shared/modules/economy/lootdrop.service").then(m => m.lootdropService.processMessage(message));
},
};

View File

@@ -1,6 +1,6 @@
import { Events } from "discord.js";
import { schedulerService } from "@/modules/system/scheduler";
import type { Event } from "@lib/types";
import type { Event } from "@shared/lib/types";
const event: Event<Events.ClientReady> = {
name: Events.ClientReady,
@@ -10,7 +10,7 @@ const event: Event<Events.ClientReady> = {
schedulerService.start();
// Handle post-update tasks
const { UpdateService } = await import("@/modules/admin/update.service");
const { UpdateService } = await import("@shared/modules/admin/update.service");
await UpdateService.handlePostRestart(c);
},
};

View File

@@ -2,12 +2,12 @@ import { GlobalFonts, createCanvas, loadImage } from '@napi-rs/canvas';
import path from 'path';
// Register Fonts (same as studentID.ts)
const fontDir = path.join(process.cwd(), 'src', 'assets', 'fonts');
const fontDir = path.join(process.cwd(), 'bot', 'assets', 'fonts');
GlobalFonts.registerFromPath(path.join(fontDir, 'IBMPlexSansCondensed-SemiBold.ttf'), 'IBMPlexSansCondensed-SemiBold');
GlobalFonts.registerFromPath(path.join(fontDir, 'IBMPlexMono-Bold.ttf'), 'IBMPlexMono-Bold');
export async function generateLootdropCard(amount: number, currency: string): Promise<Buffer> {
const templatePath = path.join(process.cwd(), 'src', 'assets', 'graphics', 'lootdrop', 'template.png');
const templatePath = path.join(process.cwd(), 'bot', 'assets', 'graphics', 'lootdrop', 'template.png');
const template = await loadImage(templatePath);
const canvas = createCanvas(template.width, template.height);
@@ -50,7 +50,7 @@ export async function generateLootdropCard(amount: number, currency: string): Pr
}
export async function generateClaimedLootdropCard(amount: number, currency: string, username: string, avatarUrl: string): Promise<Buffer> {
const templatePath = path.join(process.cwd(), 'src', 'assets', 'graphics', 'lootdrop', 'template.png');
const templatePath = path.join(process.cwd(), 'bot', 'assets', 'graphics', 'lootdrop', 'template.png');
const template = await loadImage(templatePath);
const canvas = createCanvas(template.width, template.height);

View File

@@ -1,9 +1,9 @@
import { GlobalFonts, createCanvas, loadImage } from '@napi-rs/canvas';
import { levelingService } from '@/modules/leveling/leveling.service';
import { levelingService } from '@shared/modules/leveling/leveling.service';
import path from 'path';
// Register Fonts
const fontDir = path.join(process.cwd(), 'src', 'assets', 'fonts');
const fontDir = path.join(process.cwd(), 'bot', 'assets', 'fonts');
GlobalFonts.registerFromPath(path.join(fontDir, 'IBMPlexSansCondensed-SemiBold.ttf'), 'IBMPlexSansCondensed-SemiBold');
GlobalFonts.registerFromPath(path.join(fontDir, 'IBMPlexMono-Bold.ttf'), 'IBMPlexMono-Bold');
@@ -18,8 +18,8 @@ interface StudentCardData {
}
export async function generateStudentIdCard(data: StudentCardData): Promise<Buffer> {
const templatePath = path.join(process.cwd(), 'src', 'assets', 'graphics', 'studentID', 'template.png');
const classTemplatePath = path.join(process.cwd(), 'src', 'assets', 'graphics', 'studentID', `Constellation-${data.className}.png`);
const templatePath = path.join(process.cwd(), 'bot', 'assets', 'graphics', 'studentID', 'template.png');
const classTemplatePath = path.join(process.cwd(), 'bot', 'assets', 'graphics', 'studentID', `Constellation-${data.className}.png`);
const template = await loadImage(templatePath);
const classTemplate = await loadImage(classTemplatePath);

View File

@@ -1,19 +1,20 @@
import { AuroraClient } from "@/lib/BotClient";
import { env } from "@lib/env";
import { env } from "@shared/lib/env";
import { join } from "node:path";
import { startWebServerFromRoot } from "./web/src/server";
import { startWebServerFromRoot } from "../web/src/server";
// Load commands & events
await AuroraClient.loadCommands();
await AuroraClient.loadEvents();
await AuroraClient.deployCommands();
await AuroraClient.setupSystemEvents();
console.log("🌐 Starting web server...");
let shuttingDown = false;
const webProjectPath = join(import.meta.dir, "web");
const webProjectPath = join(import.meta.dir, "../web");
const webPort = Number(process.env.WEB_PORT) || 3000;
const webHost = process.env.HOST || "0.0.0.0";

111
bot/lib/BotClient.test.ts Normal file
View File

@@ -0,0 +1,111 @@
import { describe, expect, test, mock, beforeEach, spyOn } from "bun:test";
import { systemEvents, EVENTS } from "@shared/lib/events";
// Mock Discord.js Client and related classes
mock.module("discord.js", () => ({
Client: class {
constructor() { }
on() { }
once() { }
login() { }
destroy() { }
removeAllListeners() { }
},
Collection: Map,
GatewayIntentBits: { Guilds: 1, MessageContent: 1, GuildMessages: 1, GuildMembers: 1 },
REST: class {
setToken() { return this; }
put() { return Promise.resolve([]); }
},
Routes: {
applicationGuildCommands: () => 'guild_route',
applicationCommands: () => 'global_route'
}
}));
// Mock loaders to avoid filesystem access during client init
mock.module("../lib/loaders/CommandLoader", () => ({
CommandLoader: class {
constructor() { }
loadFromDirectory() { return Promise.resolve({ loaded: 0, skipped: 0, errors: [] }); }
}
}));
mock.module("../lib/loaders/EventLoader", () => ({
EventLoader: class {
constructor() { }
loadFromDirectory() { return Promise.resolve({ loaded: 0, skipped: 0, errors: [] }); }
}
}));
// Mock dashboard service to prevent network/db calls during event handling
mock.module("@shared/modules/economy/lootdrop.service", () => ({
lootdropService: { clearCaches: mock(async () => { }) }
}));
mock.module("@shared/modules/trade/trade.service", () => ({
tradeService: { clearSessions: mock(() => { }) }
}));
mock.module("@/modules/admin/item_wizard", () => ({
clearDraftSessions: mock(() => { })
}));
mock.module("@shared/modules/dashboard/dashboard.service", () => ({
dashboardService: {
recordEvent: mock(() => Promise.resolve())
}
}));
describe("AuroraClient System Events", () => {
let AuroraClient: any;
beforeEach(async () => {
systemEvents.removeAllListeners();
const module = await import("./BotClient");
AuroraClient = module.AuroraClient;
AuroraClient.maintenanceMode = false;
// MUST call explicitly now
await AuroraClient.setupSystemEvents();
});
/**
* Test Case: Maintenance Mode Toggle
* Requirement: Client state should update when event is received
*/
test("should toggle maintenanceMode when MAINTENANCE_MODE event is received", async () => {
systemEvents.emit(EVENTS.ACTIONS.MAINTENANCE_MODE, { enabled: true, reason: "Testing" });
await new Promise(resolve => setTimeout(resolve, 30));
expect(AuroraClient.maintenanceMode).toBe(true);
systemEvents.emit(EVENTS.ACTIONS.MAINTENANCE_MODE, { enabled: false });
await new Promise(resolve => setTimeout(resolve, 30));
expect(AuroraClient.maintenanceMode).toBe(false);
});
/**
* Test Case: Command Reload
* Requirement: loadCommands and deployCommands should be called
*/
test("should reload commands when RELOAD_COMMANDS event is received", async () => {
const loadSpy = spyOn(AuroraClient, "loadCommands").mockImplementation(() => Promise.resolve());
const deploySpy = spyOn(AuroraClient, "deployCommands").mockImplementation(() => Promise.resolve());
systemEvents.emit(EVENTS.ACTIONS.RELOAD_COMMANDS);
await new Promise(resolve => setTimeout(resolve, 50));
expect(loadSpy).toHaveBeenCalled();
expect(deploySpy).toHaveBeenCalled();
});
/**
* Test Case: Cache Clearance
* Requirement: Service clear methods should be triggered
*/
test("should trigger service cache clearance when CLEAR_CACHE is received", async () => {
const { lootdropService } = await import("@shared/modules/economy/lootdrop.service");
const { tradeService } = await import("@shared/modules/trade/trade.service");
systemEvents.emit(EVENTS.ACTIONS.CLEAR_CACHE);
await new Promise(resolve => setTimeout(resolve, 50));
expect(lootdropService.clearCaches).toHaveBeenCalled();
expect(tradeService.clearSessions).toHaveBeenCalled();
});
});

View File

@@ -1,7 +1,7 @@
import { Client as DiscordClient, Collection, GatewayIntentBits, REST, Routes } from "discord.js";
import { join } from "node:path";
import type { Command } from "@lib/types";
import { env } from "@lib/env";
import type { Command } from "@shared/lib/types";
import { env } from "@shared/lib/env";
import { CommandLoader } from "@lib/loaders/CommandLoader";
import { EventLoader } from "@lib/loaders/EventLoader";
@@ -9,6 +9,7 @@ export class Client extends DiscordClient {
commands: Collection<string, Command>;
lastCommandTimestamp: number | null = null;
maintenanceMode: boolean = false;
private commandLoader: CommandLoader;
private eventLoader: EventLoader;
@@ -19,6 +20,60 @@ export class Client extends DiscordClient {
this.eventLoader = new EventLoader(this);
}
public async setupSystemEvents() {
const { systemEvents, EVENTS } = await import("@shared/lib/events");
systemEvents.on(EVENTS.ACTIONS.RELOAD_COMMANDS, async () => {
console.log("🔄 System Action: Reloading commands...");
try {
await this.loadCommands(true);
await this.deployCommands();
const { dashboardService } = await import("@shared/modules/dashboard/dashboard.service");
await dashboardService.recordEvent({
type: "success",
message: "Bot: Commands reloaded and redeployed",
icon: "✅"
});
} catch (error) {
console.error("Failed to reload commands:", error);
}
});
systemEvents.on(EVENTS.ACTIONS.CLEAR_CACHE, async () => {
console.log("<22> System Action: Clearing all internal caches...");
try {
// 1. Lootdrop Service
const { lootdropService } = await import("@shared/modules/economy/lootdrop.service");
await lootdropService.clearCaches();
// 2. Trade Service
const { tradeService } = await import("@shared/modules/trade/trade.service");
tradeService.clearSessions();
// 3. Item Wizard
const { clearDraftSessions } = await import("@/modules/admin/item_wizard");
clearDraftSessions();
const { dashboardService } = await import("@shared/modules/dashboard/dashboard.service");
await dashboardService.recordEvent({
type: "success",
message: "Bot: All internal caches and sessions cleared",
icon: "🧼"
});
} catch (error) {
console.error("Failed to clear caches:", error);
}
});
systemEvents.on(EVENTS.ACTIONS.MAINTENANCE_MODE, async (data: { enabled: boolean, reason?: string }) => {
const { enabled, reason } = data;
console.log(`🛠️ System Action: Maintenance mode ${enabled ? "ON" : "OFF"}${reason ? ` (${reason})` : ""}`);
this.maintenanceMode = enabled;
});
}
async loadCommands(reload: boolean = false) {
if (reload) {
this.commands.clear();
@@ -96,7 +151,7 @@ export class Client extends DiscordClient {
async shutdown() {
const { setShuttingDown, waitForTransactions } = await import("./shutdown");
const { closeDatabase } = await import("./DrizzleClient");
const { closeDatabase } = await import("@shared/db/DrizzleClient");
console.log("🛑 Shutdown signal received. Starting graceful shutdown...");
setShuttingDown(true);

View File

@@ -0,0 +1,74 @@
import { describe, test, expect, beforeEach, mock, afterEach } from "bun:test";
import { getClientStats, clearStatsCache } from "./clientStats";
// Mock AuroraClient
mock.module("./BotClient", () => ({
AuroraClient: {
guilds: {
cache: {
size: 5,
},
},
ws: {
ping: 42,
},
users: {
cache: {
size: 100,
},
},
commands: {
size: 20,
},
lastCommandTimestamp: 1641481200000,
},
}));
describe("clientStats", () => {
beforeEach(() => {
clearStatsCache();
});
test("should return client stats", () => {
const stats = getClientStats();
expect(stats.guilds).toBe(5);
expect(stats.ping).toBe(42);
expect(stats.cachedUsers).toBe(100);
expect(stats.commandsRegistered).toBe(20);
expect(typeof stats.uptime).toBe("number"); // Can't mock process.uptime easily
expect(stats.lastCommandTimestamp).toBe(1641481200000);
});
test("should cache stats for 30 seconds", () => {
const stats1 = getClientStats();
const stats2 = getClientStats();
// Should return same object (cached)
expect(stats1).toBe(stats2);
});
test("should refresh cache after TTL expires", async () => {
const stats1 = getClientStats();
// Wait for cache to expire (simulate by clearing and waiting)
await new Promise(resolve => setTimeout(resolve, 35));
clearStatsCache();
const stats2 = getClientStats();
// Should be different objects (new fetch)
expect(stats1).not.toBe(stats2);
// But values should be the same (mocked client)
expect(stats1.guilds).toBe(stats2.guilds);
});
test("clearStatsCache should invalidate cache", () => {
const stats1 = getClientStats();
clearStatsCache();
const stats2 = getClientStats();
// Should be different objects
expect(stats1).not.toBe(stats2);
});
});

48
bot/lib/clientStats.ts Normal file
View File

@@ -0,0 +1,48 @@
import { AuroraClient } from "./BotClient";
import type { ClientStats } from "@shared/modules/dashboard/dashboard.types";
// Cache for client stats (30 second TTL)
let cachedStats: ClientStats | null = null;
let lastFetchTime: number = 0;
const CACHE_TTL_MS = 30 * 1000; // 30 seconds
/**
* Get Discord client statistics with caching
* Respects rate limits by caching for 30 seconds
*/
export function getClientStats(): ClientStats {
const now = Date.now();
// Return cached stats if still valid
if (cachedStats && (now - lastFetchTime) < CACHE_TTL_MS) {
return cachedStats;
}
// Fetch fresh stats
const stats: ClientStats = {
bot: {
name: AuroraClient.user?.username || "Aurora",
avatarUrl: AuroraClient.user?.displayAvatarURL() || null,
},
guilds: AuroraClient.guilds.cache.size,
ping: AuroraClient.ws.ping,
cachedUsers: AuroraClient.users.cache.size,
commandsRegistered: AuroraClient.commands.size,
uptime: process.uptime(),
lastCommandTimestamp: AuroraClient.lastCommandTimestamp,
};
// Update cache
cachedStats = stats;
lastFetchTime = now;
return stats;
}
/**
* Clear the stats cache (useful for testing)
*/
export function clearStatsCache(): void {
cachedStats = null;
lastFetchTime = 0;
}

View File

@@ -1,5 +1,5 @@
import { DrizzleClient } from "./DrizzleClient";
import type { Transaction } from "./types";
import { DrizzleClient } from "@shared/db/DrizzleClient";
import type { Transaction } from "@shared/lib/types";
import { isShuttingDown, incrementTransactions, decrementTransactions } from "./shutdown";
export const withTransaction = async <T>(

View File

@@ -4,7 +4,7 @@ import { AuroraClient } from "@/lib/BotClient";
import { ChatInputCommandInteraction } from "discord.js";
// Mock UserService
mock.module("@/modules/user/user.service", () => ({
mock.module("@shared/modules/user/user.service", () => ({
userService: {
getOrCreateUser: mock(() => Promise.resolve())
}
@@ -56,4 +56,28 @@ describe("CommandHandler", () => {
expect(executeError).toHaveBeenCalled();
expect(AuroraClient.lastCommandTimestamp).toBeNull();
});
test("should block execution when maintenance mode is active", async () => {
AuroraClient.maintenanceMode = true;
const executeSpy = mock(() => Promise.resolve());
AuroraClient.commands.set("maint-test", {
data: { name: "maint-test" } as any,
execute: executeSpy
} as any);
const interaction = {
commandName: "maint-test",
user: { id: "123", username: "testuser" },
reply: mock(() => Promise.resolve())
} as unknown as ChatInputCommandInteraction;
await CommandHandler.handle(interaction);
expect(executeSpy).not.toHaveBeenCalled();
expect(interaction.reply).toHaveBeenCalledWith(expect.objectContaining({
flags: expect.anything()
}));
AuroraClient.maintenanceMode = false; // Reset for other tests
});
});

View File

@@ -1,8 +1,8 @@
import { ChatInputCommandInteraction, MessageFlags } from "discord.js";
import { AuroraClient } from "@/lib/BotClient";
import { userService } from "@/modules/user/user.service";
import { userService } from "@shared/modules/user/user.service";
import { createErrorEmbed } from "@lib/embeds";
/**
* Handles slash command execution
@@ -17,6 +17,13 @@ export class CommandHandler {
return;
}
// Check maintenance mode
if (AuroraClient.maintenanceMode) {
const errorEmbed = createErrorEmbed('The bot is currently undergoing maintenance. Please try again later.');
await interaction.reply({ embeds: [errorEmbed], flags: MessageFlags.Ephemeral });
return;
}
// Ensure user exists in database
try {
await userService.getOrCreateUser(interaction.user.id, interaction.user.username);

View File

@@ -1,7 +1,7 @@
import { readdir } from "node:fs/promises";
import { join } from "node:path";
import type { Command } from "@lib/types";
import { config } from "@lib/config";
import type { Command } from "@shared/lib/types";
import { config } from "@shared/lib/config";
import type { LoadResult, LoadError } from "./types";
import type { Client } from "../BotClient";

View File

@@ -1,6 +1,6 @@
import { readdir } from "node:fs/promises";
import { join } from "node:path";
import type { Event } from "@lib/types";
import type { Event } from "@shared/lib/types";
import type { LoadResult } from "./types";
import type { Client } from "../BotClient";

View File

@@ -6,13 +6,13 @@ import { ButtonInteraction, ModalSubmitInteraction, StringSelectMenuInteraction
const valuesMock = mock((_args: any) => Promise.resolve());
const insertMock = mock(() => ({ values: valuesMock }));
mock.module("@/lib/DrizzleClient", () => ({
mock.module("@shared/db/DrizzleClient", () => ({
DrizzleClient: {
insert: insertMock
}
}));
mock.module("@/db/schema", () => ({
mock.module("@db/schema", () => ({
items: "items_schema"
}));

View File

@@ -1,10 +1,10 @@
import { type Interaction } from "discord.js";
import { items } from "@/db/schema";
import { DrizzleClient } from "@/lib/DrizzleClient";
import type { ItemUsageData, ItemEffect } from "@/lib/types";
import { items } from "@db/schema";
import { DrizzleClient } from "@shared/db/DrizzleClient";
import type { ItemUsageData, ItemEffect } from "@shared/lib/types";
import { getItemWizardEmbed, getItemTypeSelection, getEffectTypeSelection, getDetailsModal, getEconomyModal, getVisualsModal, getEffectConfigModal } from "./item_wizard.view";
import type { DraftItem } from "./item_wizard.types";
import { ItemType, EffectType } from "@/lib/constants";
import { ItemType, EffectType } from "@shared/lib/constants";
// --- Types ---
@@ -241,3 +241,8 @@ export const handleItemWizardInteraction = async (interaction: Interaction) => {
}
};
export const clearDraftSessions = () => {
draftSession.clear();
console.log("[ItemWizard] All draft item creation sessions cleared.");
};

View File

@@ -1,4 +1,4 @@
import type { ItemUsageData } from "@/lib/types";
import type { ItemUsageData } from "@shared/lib/types";
export interface DraftItem {
name: string;

View File

@@ -10,7 +10,7 @@ import {
} from "discord.js";
import { createBaseEmbed } from "@lib/embeds";
import type { DraftItem } from "./item_wizard.types";
import { ItemType } from "@/lib/constants";
import { ItemType } from "@shared/lib/constants";
const getItemTypeOptions = () => [
{ label: "Material", value: ItemType.MATERIAL, description: "Used for crafting or trading" },

View File

@@ -0,0 +1,33 @@
export interface RestartContext {
channelId: string;
userId: string;
timestamp: number;
runMigrations: boolean;
installDependencies: boolean;
previousCommit: string;
newCommit: string;
}
export interface UpdateCheckResult {
needsRootInstall: boolean;
needsWebInstall: boolean;
needsMigrations: boolean;
changedFiles: string[];
error?: Error;
}
export interface UpdateInfo {
hasUpdates: boolean;
branch: string;
currentCommit: string;
latestCommit: string;
commitCount: number;
commits: CommitInfo[];
}
export interface CommitInfo {
hash: string;
message: string;
author: string;
}

View File

@@ -1,6 +1,6 @@
import { ActionRowBuilder, ButtonBuilder, ButtonStyle, EmbedBuilder } from "discord.js";
import { createInfoEmbed, createSuccessEmbed, createWarningEmbed, createErrorEmbed } from "@lib/embeds";
import type { UpdateInfo, UpdateCheckResult } from "./update.service";
import type { UpdateInfo, UpdateCheckResult } from "./update.types";
// Constants for UI
const LOG_TRUNCATE_LENGTH = 800;

View File

@@ -1,5 +1,5 @@
import { ButtonInteraction } from "discord.js";
import { lootdropService } from "./lootdrop.service";
import { lootdropService } from "@shared/modules/economy/lootdrop.service";
import { UserError } from "@/lib/errors";
import { getLootdropClaimedMessage } from "./lootdrop.view";

View File

@@ -1,6 +1,6 @@
import { ButtonInteraction, MessageFlags } from "discord.js";
import { inventoryService } from "@/modules/inventory/inventory.service";
import { userService } from "@/modules/user/user.service";
import { inventoryService } from "@shared/modules/inventory/inventory.service";
import { userService } from "@shared/modules/user/user.service";
import { UserError } from "@/lib/errors";
export async function handleShopInteraction(interaction: ButtonInteraction) {

View File

@@ -1,6 +1,6 @@
import type { Interaction } from "discord.js";
import { TextChannel, MessageFlags } from "discord.js";
import { config } from "@/lib/config";
import { config } from "@shared/lib/config";
import { AuroraClient } from "@/lib/BotClient";
import { buildFeedbackMessage, getFeedbackModal } from "./feedback.view";
import { FEEDBACK_CUSTOM_IDS, type FeedbackType, type FeedbackData } from "./feedback.types";

View File

@@ -1,11 +1,11 @@
import { levelingService } from "@/modules/leveling/leveling.service";
import { economyService } from "@/modules/economy/economy.service";
import { userTimers } from "@/db/schema";
import { levelingService } from "@shared/modules/leveling/leveling.service";
import { economyService } from "@shared/modules/economy/economy.service";
import { userTimers } from "@db/schema";
import type { EffectHandler } from "./types";
import type { LootTableItem } from "@/lib/types";
import { inventoryService } from "@/modules/inventory/inventory.service";
import { inventory, items } from "@/db/schema";
import { TimerType, TransactionType, LootType } from "@/lib/constants";
import type { LootTableItem } from "@shared/lib/types";
import { inventoryService } from "@shared/modules/inventory/inventory.service";
import { inventory, items } from "@db/schema";
import { TimerType, TransactionType, LootType } from "@shared/lib/constants";
// Helper to extract duration in seconds
@@ -120,7 +120,7 @@ export const handleLootbox: EffectHandler = async (userId, effect, txFn) => {
// Try to fetch item name for the message
try {
const item = await txFn.query.items.findFirst({
where: (items, { eq }) => eq(items.id, winner.itemId!)
where: (items: any, { eq }: any) => eq(items.id, winner.itemId!)
});
if (item) {
return winner.message || `You found ${quantity > 1 ? quantity + 'x ' : ''}**${item.name}**!`;

View File

@@ -1,4 +1,4 @@
import type { Transaction } from "@/lib/types";
import type { Transaction } from "@shared/lib/types";
export type EffectHandler = (userId: string, effect: any, txFn: Transaction) => Promise<string>;

View File

@@ -1,6 +1,6 @@
import { EmbedBuilder } from "discord.js";
import type { ItemUsageData } from "@/lib/types";
import { EffectType } from "@/lib/constants";
import type { ItemUsageData } from "@shared/lib/types";
import { EffectType } from "@shared/lib/constants";
/**
* Inventory entry with item details

View File

@@ -1,4 +1,4 @@
import { CaseType } from "@/lib/constants";
import { CaseType } from "@shared/lib/constants";
export { CaseType };

View File

@@ -1,4 +1,4 @@
import { temporaryRoleService } from "./temp-role.service";
import { temporaryRoleService } from "@shared/modules/system/temp-role.service";
export const schedulerService = {
start: () => {
@@ -10,7 +10,7 @@ export const schedulerService = {
}, 60 * 1000);
// 2. Terminal Update Loop (every 60s)
const { terminalService } = require("@/modules/terminal/terminal.service");
const { terminalService } = require("@shared/modules/terminal/terminal.service");
setInterval(() => {
terminalService.update();
}, 60 * 1000);

View File

@@ -7,8 +7,8 @@ import {
TextChannel,
EmbedBuilder
} from "discord.js";
import { tradeService } from "./trade.service";
import { inventoryService } from "@/modules/inventory/inventory.service";
import { tradeService } from "@shared/modules/trade/trade.service";
import { inventoryService } from "@shared/modules/inventory/inventory.service";
import { createErrorEmbed, createWarningEmbed, createSuccessEmbed, createInfoEmbed } from "@lib/embeds";
import { UserError } from "@lib/errors";
import { getTradeDashboard, getTradeMoneyModal, getItemSelectMenu, getTradeCompletedEmbed } from "./trade.view";
@@ -101,7 +101,7 @@ async function handleAddItemClick(interaction: ButtonInteraction, threadId: stri
}
// Slice top 25 for select menu
const options = inventory.slice(0, 25).map(entry => ({
const options = inventory.slice(0, 25).map((entry: any) => ({
label: `${entry.item.name} (${entry.quantity})`,
value: entry.item.id.toString(),
description: `Rarity: ${entry.item.rarity} `

View File

@@ -1,8 +1,8 @@
import { ButtonInteraction, MessageFlags } from "discord.js";
import { config } from "@/lib/config";
import { config } from "@shared/lib/config";
import { getEnrollmentSuccessMessage } from "./enrollment.view";
import { classService } from "@modules/class/class.service";
import { userService } from "@modules/user/user.service";
import { classService } from "@shared/modules/class/class.service";
import { userService } from "@shared/modules/user/user.service";
import { UserError } from "@/lib/errors";
import { sendWebhookMessage } from "@/lib/webhookUtils";
@@ -37,7 +37,7 @@ export async function handleEnrollmentInteraction(interaction: ButtonInteraction
// 2. Get available classes
const allClasses = await classService.getAllClasses();
const validClasses = allClasses.filter(c => c.roleId);
const validClasses = allClasses.filter((c: any) => c.roleId);
if (validClasses.length === 0) {
throw new UserError("No classes with specified roles found in database.");

View File

@@ -1,7 +1,7 @@
import { userTimers } from "@/db/schema";
import { userTimers } from "@db/schema";
import { eq, and } from "drizzle-orm";
import { DrizzleClient } from "@/lib/DrizzleClient";
import { TimerType } from "@/lib/constants";
import { DrizzleClient } from "@shared/db/DrizzleClient";
import { TimerType } from "@shared/lib/constants";
export { TimerType };
@@ -33,7 +33,7 @@ export const userTimerService = {
if (tx) {
return await execute(tx);
} else {
return await DrizzleClient.transaction(async (t) => {
return await DrizzleClient.transaction(async (t: any) => {
return await execute(t);
});
}
@@ -89,7 +89,7 @@ export const userTimerService = {
if (tx) {
return await execute(tx);
} else {
return await DrizzleClient.transaction(async (t) => {
return await DrizzleClient.transaction(async (t: any) => {
return await execute(t);
});
}

View File

@@ -10,8 +10,8 @@ services:
# ports:
# - "127.0.0.1:${DB_PORT}:5432"
volumes:
- ./src/db/data:/var/lib/postgresql/data
- ./src/db/log:/var/log/postgresql
- ./shared/db/data:/var/lib/postgresql/data
- ./shared/db/log:/var/log/postgresql
networks:
- internal
healthcheck:
@@ -33,7 +33,7 @@ services:
volumes:
- .:/app
- /app/node_modules
- /app/src/web/node_modules
- /app/web/node_modules
environment:
- HOST=0.0.0.0
- DB_USER=${DB_USER}
@@ -71,7 +71,7 @@ services:
volumes:
- .:/app
- /app/node_modules
- /app/src/web/node_modules
- /app/web/node_modules
environment:
- DB_USER=${DB_USER}
- DB_PASSWORD=${DB_PASSWORD}
@@ -84,7 +84,8 @@ services:
condition: service_healthy
networks:
- internal
command: bun run db:studio
- web
command: [ "bun", "x", "drizzle-kit", "studio", "--port", "4983", "--host", "0.0.0.0" ]
networks:
internal:

Some files were not shown because too many files have changed in this diff Show More