refactor: move config loading to shared directory

This commit is contained in:
syntaxbullet
2026-01-08 16:15:55 +01:00
parent 88b266f81b
commit 2b641c952d
23 changed files with 15 additions and 219 deletions

View File

@@ -1,7 +1,7 @@
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,6 +1,6 @@
import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder, PermissionFlagsBits, AttachmentBuilder } from "discord.js";
import { config, saveConfig } from "@/lib/config";
import { config, saveConfig } from "@shared/lib/config";
import { DrizzleClient } from "@shared/db/DrizzleClient";
import { items } from "@db/schema";
import { createSuccessEmbed, createErrorEmbed } from "@lib/embeds";

View File

@@ -1,8 +1,8 @@
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 { configManager } from "@shared/lib/configManager";
import { config, reloadConfig } from "@shared/lib/config";
import { AuroraClient } from "@/lib/BotClient";
export const features = createCommand({

View File

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

View File

@@ -6,7 +6,7 @@ import {
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

@@ -6,7 +6,7 @@ import { UserError } from "@/lib/errors";
import { userTimers, users } from "@db/schema";
import { eq, and, sql } from "drizzle-orm";
import { DrizzleClient } from "@shared/db/DrizzleClient";
import { config } from "@lib/config";
import { config } from "@shared/lib/config";
import { TimerType } from "@shared/lib/constants";
const EXAM_TIMER_TYPE = TimerType.EXAM_SYSTEM;

View File

@@ -3,7 +3,7 @@ import { createCommand } from "@shared/lib/utils";
import { SlashCommandBuilder, MessageFlags } from "discord.js";
import { economyService } from "@shared/modules/economy/economy.service";
import { userService } from "@shared/modules/user/user.service";
import { config } from "@/lib/config";
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 "@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

@@ -6,7 +6,7 @@ import { createErrorEmbed } from "@lib/embeds";
import { getItemUseResultEmbed } from "@/modules/inventory/inventory.view";
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,6 +1,6 @@
import { Events } from "discord.js";
import type { Event } from "@shared/lib/types";
import { config } from "@lib/config";
import { config } from "@shared/lib/config";
import { userService } from "@shared/modules/user/user.service";
// Visitor role

View File

@@ -1,204 +0,0 @@
import { readFileSync, existsSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { z } from 'zod';
const configPath = join(import.meta.dir, '..', '..', 'config', 'config.json');
export interface GameConfigType {
leveling: {
base: number;
exponent: number;
chat: {
cooldownMs: number;
minXp: number;
maxXp: number;
}
},
economy: {
daily: {
amount: bigint;
streakBonus: bigint;
weeklyBonus: bigint;
cooldownMs: number;
},
transfers: {
allowSelfTransfer: boolean;
minAmount: bigint;
},
exam: {
multMin: number;
multMax: number;
}
},
inventory: {
maxStackSize: bigint;
maxSlots: number;
},
commands: Record<string, boolean>;
lootdrop: {
activityWindowMs: number;
minMessages: number;
spawnChance: number;
cooldownMs: number;
reward: {
min: number;
max: number;
currency: string;
}
};
studentRole: string;
visitorRole: string;
colorRoles: string[];
welcomeChannelId?: string;
welcomeMessage?: string;
feedbackChannelId?: string;
terminal?: {
channelId: string;
messageId: string;
};
moderation: {
prune: {
maxAmount: number;
confirmThreshold: number;
batchSize: number;
batchDelayMs: number;
};
cases: {
dmOnWarn: boolean;
logChannelId?: string;
autoTimeoutThreshold?: number;
};
};
system: Record<string, any>;
}
// Initial default config state
export const config: GameConfigType = {} as GameConfigType;
const bigIntSchema = z.union([z.string(), z.number(), z.bigint()])
.refine((val) => {
try {
BigInt(val);
return true;
} catch {
return false;
}
}, { message: "Must be a valid integer" })
.transform((val) => BigInt(val));
const configSchema = z.object({
leveling: z.object({
base: z.number(),
exponent: z.number(),
chat: z.object({
cooldownMs: z.number(),
minXp: z.number(),
maxXp: z.number(),
})
}),
economy: z.object({
daily: z.object({
amount: bigIntSchema,
streakBonus: bigIntSchema,
weeklyBonus: bigIntSchema.default(50n),
cooldownMs: z.number(),
}),
transfers: z.object({
allowSelfTransfer: z.boolean(),
minAmount: bigIntSchema,
}),
exam: z.object({
multMin: z.number(),
multMax: z.number(),
})
}),
inventory: z.object({
maxStackSize: bigIntSchema,
maxSlots: z.number(),
}),
commands: z.record(z.string(), z.boolean()),
lootdrop: z.object({
activityWindowMs: z.number(),
minMessages: z.number(),
spawnChance: z.number(),
cooldownMs: z.number(),
reward: z.object({
min: z.number(),
max: z.number(),
currency: z.string(),
})
}),
studentRole: z.string(),
visitorRole: z.string(),
colorRoles: z.array(z.string()).default([]),
welcomeChannelId: z.string().optional(),
welcomeMessage: z.string().optional(),
feedbackChannelId: z.string().optional(),
terminal: z.object({
channelId: z.string(),
messageId: z.string()
}).optional(),
moderation: z.object({
prune: z.object({
maxAmount: z.number().default(100),
confirmThreshold: z.number().default(50),
batchSize: z.number().default(100),
batchDelayMs: z.number().default(1000)
}),
cases: z.object({
dmOnWarn: z.boolean().default(true),
logChannelId: z.string().optional(),
autoTimeoutThreshold: z.number().optional()
})
}).default({
prune: {
maxAmount: 100,
confirmThreshold: 50,
batchSize: 100,
batchDelayMs: 1000
},
cases: {
dmOnWarn: true
}
}),
system: z.record(z.string(), z.any()).default({}),
});
export function reloadConfig() {
if (!existsSync(configPath)) {
throw new Error(`Config file not found at ${configPath}`);
}
const raw = readFileSync(configPath, 'utf-8');
const rawConfig = JSON.parse(raw);
// Update config object in place
// We use Object.assign to keep the reference to the exported 'config' object same
const validatedConfig = configSchema.parse(rawConfig);
Object.assign(config, validatedConfig);
console.log("🔄 Config reloaded from disk.");
}
// Initial load
reloadConfig();
// Backwards compatibility alias
export const GameConfig = config;
export function saveConfig(newConfig: unknown) {
// Validate and transform input
const validatedConfig = configSchema.parse(newConfig);
const replacer = (key: string, value: any) => {
if (typeof value === 'bigint') {
return value.toString();
}
return value;
};
const jsonString = JSON.stringify(validatedConfig, replacer, 4);
writeFileSync(configPath, jsonString, 'utf-8');
reloadConfig();
}

View File

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

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,5 +1,5 @@
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 "@shared/modules/class/class.service";
import { userService } from "@shared/modules/user/user.service";