Files
discord-rpg-concept/shared/lib/config.ts
syntaxbullet 6eb4a32a12 refactor: consolidate config types and remove file-based config
Tickets: #2, #3

- Remove duplicate type definitions from shared/lib/config.ts
- Import types from schema files (game-settings.ts, guild-settings.ts)
- Add GuildConfig interface to guild-settings.ts schema
- Rename ModerationConfig to ModerationCaseConfig in moderation.service.ts
- Delete shared/config/config.json and shared/scripts/migrate-config-to-db.ts
- Update settings API to use gameSettingsService exclusively
- Return DB format (strings) from API instead of runtime BigInts
- Fix moderation service tests to pass config as parameter

Breaking Changes:
- Removes legacy file-based configuration system
- API now returns database format with string values for BigInt fields
2026-02-13 13:24:02 +01:00

208 lines
6.8 KiB
TypeScript

import type {
LevelingConfig,
EconomyConfig as EconomyConfigDB,
InventoryConfig as InventoryConfigDB,
LootdropConfig,
TriviaConfig as TriviaConfigDB,
ModerationConfig
} from "@db/schema/game-settings";
import type { GuildConfig } from "@db/schema/guild-settings";
const guildConfigCache = new Map<string, { config: GuildConfig; timestamp: number }>();
const CACHE_TTL_MS = 60000;
export async function getGuildConfig(guildId: string): Promise<GuildConfig> {
const cached = guildConfigCache.get(guildId);
if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
return cached.config;
}
try {
const { guildSettingsService } = await import('@shared/modules/guild-settings/guild-settings.service');
const dbSettings = await guildSettingsService.getSettings(guildId);
if (dbSettings) {
const config: GuildConfig = {
studentRole: dbSettings.studentRoleId,
visitorRole: dbSettings.visitorRoleId,
colorRoles: dbSettings.colorRoleIds ?? [],
welcomeChannelId: dbSettings.welcomeChannelId,
welcomeMessage: dbSettings.welcomeMessage,
feedbackChannelId: dbSettings.feedbackChannelId,
terminal: dbSettings.terminalChannelId ? {
channelId: dbSettings.terminalChannelId,
messageId: dbSettings.terminalMessageId ?? "",
} : undefined,
moderation: {
cases: {
dmOnWarn: dbSettings.moderationDmOnWarn ?? true,
logChannelId: dbSettings.moderationLogChannelId,
autoTimeoutThreshold: dbSettings.moderationAutoTimeoutThreshold,
},
},
};
guildConfigCache.set(guildId, { config, timestamp: Date.now() });
return config;
}
} catch (error) {
console.error("Failed to load guild config from database:", error);
}
return {
studentRole: undefined,
visitorRole: undefined,
colorRoles: [],
welcomeChannelId: undefined,
welcomeMessage: undefined,
feedbackChannelId: undefined,
terminal: undefined,
moderation: { cases: { dmOnWarn: true } },
};
}
export function invalidateGuildConfigCache(guildId: string) {
guildConfigCache.delete(guildId);
}
// Re-export DB types
export type { LevelingConfig, LootdropConfig, ModerationConfig };
// Runtime config types with BigInt for numeric fields
export interface EconomyConfig {
daily: {
amount: bigint;
streakBonus: bigint;
weeklyBonus: bigint;
cooldownMs: number;
};
transfers: {
allowSelfTransfer: boolean;
minAmount: bigint;
};
exam: {
multMin: number;
multMax: number;
};
}
export interface InventoryConfig {
maxStackSize: bigint;
maxSlots: number;
}
export interface TriviaConfig {
entryFee: bigint;
rewardMultiplier: number;
timeoutSeconds: number;
cooldownMs: number;
categories: number[];
difficulty: 'easy' | 'medium' | 'hard' | 'random';
}
export interface GameConfigType {
leveling: LevelingConfig;
economy: EconomyConfig;
inventory: InventoryConfig;
commands: Record<string, boolean>;
lootdrop: LootdropConfig;
trivia: TriviaConfig;
moderation: ModerationConfig;
system: Record<string, unknown>;
}
export const config: GameConfigType = {} as GameConfigType;
export const GameConfig = config;
async function loadFromDatabase(): Promise<boolean> {
try {
const { gameSettingsService } = await import('@shared/modules/game-settings/game-settings.service');
const dbSettings = await gameSettingsService.getSettings();
if (dbSettings) {
Object.assign(config, {
leveling: {
...dbSettings.leveling,
},
economy: {
daily: {
...dbSettings.economy.daily,
amount: BigInt(dbSettings.economy.daily.amount),
streakBonus: BigInt(dbSettings.economy.daily.streakBonus),
weeklyBonus: BigInt(dbSettings.economy.daily.weeklyBonus),
},
transfers: {
...dbSettings.economy.transfers,
minAmount: BigInt(dbSettings.economy.transfers.minAmount),
},
exam: dbSettings.economy.exam,
},
inventory: {
...dbSettings.inventory,
maxStackSize: BigInt(dbSettings.inventory.maxStackSize),
},
commands: dbSettings.commands,
lootdrop: dbSettings.lootdrop,
trivia: {
...dbSettings.trivia,
entryFee: BigInt(dbSettings.trivia.entryFee),
},
moderation: dbSettings.moderation,
system: dbSettings.system,
});
console.log("🎮 Game config loaded from database.");
return true;
}
} catch (error) {
console.error("Failed to load game config from database:", error);
}
return false;
}
async function loadDefaults(): Promise<void> {
console.warn("⚠️ No game config found in database. Using defaults.");
const { gameSettingsService } = await import('@shared/modules/game-settings/game-settings.service');
const defaults = gameSettingsService.getDefaults();
Object.assign(config, {
leveling: defaults.leveling,
economy: {
...defaults.economy,
daily: {
...defaults.economy.daily,
amount: BigInt(defaults.economy.daily.amount),
streakBonus: BigInt(defaults.economy.daily.streakBonus),
weeklyBonus: BigInt(defaults.economy.daily.weeklyBonus),
},
transfers: {
...defaults.economy.transfers,
minAmount: BigInt(defaults.economy.transfers.minAmount),
},
},
inventory: {
...defaults.inventory,
maxStackSize: BigInt(defaults.inventory.maxStackSize),
},
commands: defaults.commands,
lootdrop: defaults.lootdrop,
trivia: {
...defaults.trivia,
entryFee: BigInt(defaults.trivia.entryFee),
},
moderation: defaults.moderation,
system: defaults.system,
});
}
export async function reloadConfig(): Promise<void> {
const dbLoaded = await loadFromDatabase();
if (!dbLoaded) {
await loadDefaults();
}
}
export async function initializeConfig(): Promise<void> {
await reloadConfig();
}